diff --git a/.gitattributes b/.gitattributes index 0a157dc5..9391404e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -*.i linguist-detectable=false \ No newline at end of file +*.i linguist-detectable=false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3b229f5b..d0df47fb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,24 @@ +# submodules are bumped manually (XAD and pybind11 must match xad) +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + version: 2 updates: - + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + ignore: + # pinned to the XAD in lib/xad + - dependency-name: "xad" + groups: + python: + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates to GitHub Actions every weekday interval: "weekly" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/quantlib-risks.yml b/.github/workflows/quantlib-risks.yml index c3eaa086..90fbf46b 100644 --- a/.github/workflows/quantlib-risks.yml +++ b/.github/workflows/quantlib-risks.yml @@ -3,15 +3,17 @@ # - OR pushes of tags starting with v* # - OR manual dispatch on repo # publishes to test pypi if: -# - in auto-differentiation/QuantLib-Risks repository -# - AND pushes of tags starting with v* -# - OR manual dispatch on repo +# - in auto-differentiation/QuantLib-Risks-Py repository AND +# - pushes of tags starting with v* +# - OR manual dispatch on repo # publishes to real PyPI if: # - publish to Test PyPI worked (with all build conditions above) # - and if it's a version tag (starting with v*) +# +# cibuildwheel config: [tool.cibuildwheel] in pyproject.toml name: QuantLib-Risks wheels -on: +on: pull_request: branches: - main @@ -19,131 +21,115 @@ on: tags: - v* workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build_wheels: - name: Wheels + name: Wheels ${{ matrix.platform.name }} ${{ matrix.python }} strategy: fail-fast: false matrix: - buildplat: ["win_amd64", "macosx_x86_64", "manylinux_x86_64", "musllinux_x86_64"] - python: ["cp38", "cp39", "cp310", "cp311", "cp312"] - include: - - buildplat: "manylinux_x86_64" - os: ubuntu-20.04 - platform: linux - ccache: ccache - preset: linux-xad-gcc-ninja-release - - buildplat: "musllinux_x86_64" - os: ubuntu-20.04 - platform: linux - ccache: ccache - preset: linux-xad-gcc-ninja-release - - buildplat: "macosx_x86_64" - os: "macos-12" - platform: macos - ccache: ccache - preset: linux-xad-gcc-ninja-release - - buildplat: "win_amd64" - os: windows-2022 - platform: windows - ccache: sccache - preset: windows-xad-msvc-release - exclude: - # gives "is not a supported wheel on this platform" for some reason - - buildplat: "macosx_x86_64" - python: "cp38" - runs-on: ${{ matrix.os }} - env: - QL_PRESET: ${{ matrix.preset }} - CCACHE_EXE: ${{ matrix.ccache }} + python: [cp310, cp311, cp312, cp313, cp314] + platform: + - { name: manylinux-x86_64, os: ubuntu-24.04, build: "manylinux*" } + - { name: musllinux-x86_64, os: ubuntu-24.04, build: "musllinux*" } + - { name: manylinux-aarch64, os: ubuntu-24.04-arm, build: "manylinux*" } + - { name: musllinux-aarch64, os: ubuntu-24.04-arm, build: "musllinux*" } + - { name: macos-x86_64, os: macos-15-intel, build: "*" } + - { name: macos-arm64, os: macos-15, build: "*" } + - { name: windows-amd64, os: windows-2022, build: "*" } + runs-on: ${{ matrix.platform.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: submodules: recursive - fetch-depth: 0 - - name: Setup Boost (Windows) - if: ${{ matrix.platform == 'windows' }} - run: | - choco install -y ninja - $Url = "https://downloads.sourceforge.net/project/boost/boost-binaries/1.84.0/boost_1_84_0-msvc-14.3-64.exe" - (New-Object System.Net.WebClient).DownloadFile($Url, "$RUNNER_TEMP\boost.exe") - Start-Process -Wait -FilePath "$RUNNER_TEMP\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=C:\local\boost" - - name: Setup Boost (Mac) - if: ${{ matrix.platform == 'macos' }} - run: | - brew install boost - brew install ninja - brew install ccache - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: ${{ matrix.buildplat }}-${{ matrix.python }} - variant: ${{ matrix.ccache }} - max-size: 650M - - name: Setup boost path (Win) - if: ${{ matrix.platform == 'windows' }} - shell: cmd - run: echo BOOST_ROOT=C:\local\boost >> %GITHUB_ENV% - - name: Store CCache dir (Linux) - if: ${{ matrix.platform == 'linux' }} - run: | - HOST_CCACHE_DIR="$(ccache -k cache_dir)" - mkdir -p $HOST_CCACHE_DIR - echo "HOST_CCACHE_DIR=${HOST_CCACHE_DIR}" >> $GITHUB_ENV - - name: Stub `setup.py` check (Linux/Mac) - if: ${{ matrix.platform != 'windows' }} - run: | - mkdir -p build/${{ matrix.preset }}/Python - touch build/${{ matrix.preset }}/Python/setup.py - - name: Stub `setup.py` check (Windows) - if: ${{ matrix.platform == 'windows' }} + persist-credentials: false + - name: Compiler cache setup shell: bash run: | - mkdir -p build/${{ matrix.preset }}/Python - touch build/${{ matrix.preset }}/Python/setup.py + # outside the checkout, which is copied into the Linux build containers + echo "CCACHE_DIR=$RUNNER_TEMP/ccache" >> "$GITHUB_ENV" + echo "SCCACHE_DIR=$RUNNER_TEMP/sccache" >> "$GITHUB_ENV" + case "$RUNNER_OS" in + macOS) + echo "CMAKE_CXX_COMPILER_LAUNCHER=ccache" >> "$GITHUB_ENV" ;; + Windows) + # sccache needs Ninja (bundled with Visual Studio), not the VS generator + echo "CMAKE_GENERATOR=Ninja" >> "$GITHUB_ENV" + echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" ;; + esac + - name: Compiler cache + uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: ${{ matrix.platform.name }} + variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} + max-size: 2G + - name: MSVC environment (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1.13.0 - name: Build wheels - uses: pypa/cibuildwheel@v2.17.0 + uses: pypa/cibuildwheel@v4.3.4 env: - CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat }} - CIBW_BEFORE_BUILD_LINUX: /bin/bash ./tools/prebuild_ql-risks.sh - CIBW_ENVIRONMENT: QLR_PYTHON_VERSION="${{ matrix.python }}" - CIBW_ENVIRONMENT_PASS_LINUX: QL_PREFIX HOST_CCACHE_DIR QLR_PYTHON_VERSION CIBUILDWHEEL=1 - CIBW_BEFORE_BUILD_WINDOWS: tools\prebuild_ql-risks.bat - CIBW_BEFORE_BUILD_MACOS: bash ./tools/prebuild_ql-risks.sh - CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}" - CIBW_BEFORE_TEST: pip install pandas numpy matplotlib jinja2 - CIBW_TEST_COMMAND_WINDOWS: ${{ github.workspace }}/Python/run_tests.bat - CIBW_TEST_COMMAND_MACOS: bash ${{ github.workspace }}/Python/run_tests.sh - CIBW_TEST_COMMAND_LINUX: bash /host${{ github.workspace }}/Python/run_tests.sh - CIBW_BUILD_VERBOSITY: 2 - with: - package-dir: build/${{ matrix.preset }}/Python + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.platform.build }} + # Linux builds run in containers: ccache installed there, cache on the host (/host) + CIBW_BEFORE_ALL_LINUX: dnf install -y ccache git || apk add --no-cache ccache git + CIBW_INHERIT: "before-all-linux: prepend" + CIBW_ENVIRONMENT_LINUX: >- + CMAKE_CXX_COMPILER_LAUNCHER=ccache + CCACHE_DIR=/host${{ runner.temp }}/ccache/$AUDITWHEEL_PLAT + CCACHE_MAXSIZE=1G - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: cibw-wheels-${{ matrix.python }}-${{ matrix.buildplat }} + name: cibw-wheels-${{ matrix.platform.name }}-${{ matrix.python }} path: ./wheelhouse/*.whl if-no-files-found: error + build_sdist: + name: SDist + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + persist-credentials: false + - name: Build sdist + run: pipx run build --sdist + - name: Check metadata + run: pipx run twine check --strict dist/* + - name: Upload sdist + uses: actions/upload-artifact@v7 + with: + name: cibw-sdist + path: dist/*.tar.gz + if-no-files-found: error + test-publish: - needs: build_wheels + needs: [build_wheels, build_sdist] if: >- - github.event_name == 'workflow_dispatch' || - (github.repository == 'auto-differentiation/QuantLib-Risks-Py' && + github.repository == 'auto-differentiation/QuantLib-Risks-Py' && + (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))) - environment: + environment: name: testpypi url: https://test.pypi.org/p/QuantLib-Risks permissions: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: pattern: cibw-* path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@v1.8.12 + - uses: pypa/gh-action-pypi-publish@v1.14.3 + name: Publish on Test PyPI with: verbose: true repository-url: https://test.pypi.org/legacy/ @@ -159,12 +145,12 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: pattern: cibw-* path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@v1.8.12 + - uses: pypa/gh-action-pypi-publish@v1.14.3 name: Publish on PyPI with: verbose: true diff --git a/.gitignore b/.gitignore index 2635a8c4..941dab3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ -# Compilation artifacts -Python/QuantLib/quantlib_wrap.cpp -Python/.cache/ -Python/QuantLib.egg-info -Python/QuantLib_Python.egg-info -Python/dist +# Notebooks generated from the examples Python/examples/*.ipynb Python/examples/.ipynb_checkpoints @@ -53,12 +48,13 @@ testCaseCollection.xml *.idb *.ipch -# Outputs -Python/QuantLib_Risks/QuantLib_Risks.py -Python/build +# Build outputs (scikit-build-core, uv build, cibuildwheel) +/build +/dist +/wheelhouse +.pytest_cache/ .vscode **/venv -/build **/.venv -__pycache__ \ No newline at end of file +__pycache__ diff --git a/.gitmodules b/.gitmodules index 8804e003..32309105 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,9 @@ [submodule "lib/xad"] path = lib/xad url = https://github.com/auto-differentiation/xad.git -[submodule "lib/QuantLib-Risks-Cpp"] - path = lib/QuantLib-Risks-Cpp - url = https://github.com/auto-differentiation/QuantLib-Risks-Cpp.git +[submodule "lib/QuantLibAAD"] + path = lib/QuantLibAAD + url = https://github.com/auto-differentiation/QuantLibAAD.git +[submodule "lib/pybind11"] + path = lib/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 28647d68..c87c9cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ the underlying QuantLib-SWIG changelog. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.41.0] - Unreleased + +- Matches QuantLib and QuantLib Python bindings version 1.41 +- Builds against XAD 2.1 and QuantLibAAD 1.41 (formerly QuantLib-Risks-Cpp), requires xad 2.1 +- Builds the unmodified QuantLib 1.41 release, no QuantLib patches +- One-step build with scikit-build-core; wheels for Python 3.10-3.14 on Linux, macOS and Windows +- `tools/sync-swig.sh` syncs `SWIG/` with a QuantLib-SWIG release, re-applying the XAD changes in `SWIG/patches/` + ## [1.33.3] - 2024-04-04 - Builds against renamed xad-autodiff -> xad Python package @@ -23,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Initial release, matching QuantLib Python bindings version 1.33 +[1.41.0]: https://github.com/auto-differentiation/QuantLib-Risks-Py/compare/v1.33.3...v1.41.0 + [1.33.3]: https://github.com/auto-differentiation/QuantLib-Risks-Py/compare/v1.33.2...v1.33.3 [1.33.2]: https://github.com/auto-differentiation/QuantLib-Risks-Py/compare/v1.33...v1.33.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ecf3ea9..56b0fc6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ ############################################################################## -# CMake file for QuantLib-Risks. The version set here is used for the Python -# package. +# CMake file for QuantLib-Risks, driven by scikit-build-core (pyproject.toml). +# Keep the version in sync with pyproject.toml. # # This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled # for risk computation using automatic differentiation. It uses XAD, @@ -20,26 +20,38 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# +# ############################################################################## -cmake_minimum_required(VERSION 3.15.0) -project(QuantLib_Risks LANGUAGES CXX VERSION 1.33.3) - -# For MSVC RUNTIME LIBRARY, need CMP0091=NEW and cmake 3.15+ -cmake_policy(SET CMP0091 NEW) -set(PACKAGE_VERSION "${PROJECT_VERSION}") -set(PACKAGE_VERSION_HEX "0x013303f0") -set(QLR_VERSION ${PACKAGE_VERSION}) -set(QLR_HEX_VERSION ${PACKAGE_VERSION_HEX}) - - -find_package(QuantLib-Risks REQUIRED) -find_package(SWIG REQUIRED) - -include(UseSWIG) - -enable_testing() - - -add_subdirectory(Python) +cmake_minimum_required(VERSION 3.18...4.2) +project(QuantLib_Risks LANGUAGES CXX VERSION 1.41.0) + +set(QLR_VERSION ${PROJECT_VERSION}) +set(QLR_HEX_VERSION "0x014100f0") + +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# static MSVC runtime everywhere (QuantLib, XAD, the module), as in xad-py +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +option(QLR_QUANTLIB_ONLY + "Only build and install QuantLib + XAD + QuantLibAAD, for reuse via QLR_QUANTLIB_PREFIX" + OFF) +set(QLR_QUANTLIB_PREFIX "" CACHE PATH + "Install prefix of a QLR_QUANTLIB_ONLY build to reuse (empty: build QuantLib from lib/)") + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +if(QLR_QUANTLIB_PREFIX AND NOT QLR_QUANTLIB_ONLY) + include(PrebuiltQuantLib) +else() + include(QuantLibFromSource) +endif() + +if(NOT QLR_QUANTLIB_ONLY) + add_subdirectory(Python) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef79229a..e9929364 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,14 +9,14 @@ release. Contributions to this repository should only be made if they are related to building the Python bindings for QuantLib-Risks (enabled with XAD). -1. Fork, then clone the repository: +1. Fork, then clone the repository with its submodules: ```bash -git clone https://github.com/yourusername/QuantLib-Risks.git +git clone --recurse-submodules https://github.com/yourusername/QuantLib-Risks-Py.git ``` -2. Follow the [Build Instructions](README.md) to setup the dependencies and - build the software. Make sure all tests pass. +2. Follow the [Build Instructions](#build-instructions) below to build the software. + Make sure all tests pass. 3. Create a feature branch, typically based on master, for your change @@ -48,6 +48,47 @@ accepted: - Write good commit messages (short one-liner, followed by a blank line, followed by a more detailed explanation) +## Build Instructions + +The package is built in one step by [scikit-build-core](https://scikit-build-core.readthedocs.io) +(see `pyproject.toml`): CMake builds QuantLib as a static library together with +[XAD](https://github.com/auto-differentiation/xad) and +[QuantLibAAD](https://github.com/auto-differentiation/QuantLibAAD) from the `lib/` submodules, +then the SWIG module from `SWIG/` and `Python/QuantLib_Risks/`. + +Requirements: Python 3.10+, and a C++17 compiler. +CMake, Ninja and SWIG are installed as build dependencies. +Boost headers are taken from the system if found (or `BOOST_ROOT`), otherwise Boost 1.90 is +downloaded. + +```bash +git submodule update --init --recursive +python -m venv .venv +source .venv/bin/activate +pip install -v . pytest +# run the tests from outside the source tree, against the installed package +cd /tmp && python -m pytest --import-mode=importlib /path/to/QuantLib-Risks-Py/Python/test +``` + +Notes: + +- The binary module shares XAD's tape and `Real` type with the + [xad](https://pypi.org/project/xad/) package, so `lib/xad` and `lib/pybind11` must be the + exact versions xad is built with, and the `xad` requirement is pinned to that XAD + major.minor series. Update them together. +- `lib/QuantLib` is an unmodified QuantLib release; AAD support comes from QuantLibAAD. +- Building QuantLib takes most of the time. To reuse it across builds (e.g. several Python + versions), install it once and point the package build at it: + + ```bash + cmake -DPREFIX=$PWD/build/quantlib-prefix -P tools/build-quantlib.cmake + pip install -v . -Ccmake.define.QLR_QUANTLIB_PREFIX=$PWD/build/quantlib-prefix + ``` + +- Wheels for all supported Python versions are built with + [cibuildwheel](https://cibuildwheel.pypa.io) (`pipx run cibuildwheel`, configured in + `pyproject.toml`), the source distribution with `pipx run build --sdist`. + [pr]: https://github.com/auto-differentiation/QuantLib-Risks/compare/ diff --git a/ChangeLog.txt b/ChangeLog.txt index b78fcb9b..990d2709 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,287 +1,640 @@ -commit d47205c9603c8539e58e3eccd96cae38f1cbe6d6 -Author: Luigi Ballabio -Date: Thu, 19 Oct 2023 11:41:57 +0200 +commit 47bc2a352c8dd74ba7cd2813fbf1086de7de5eea +Author: lballabio[bot] <224797326+lballabio-bot@users.noreply.github.com> +Date: Mon, 12 Jan 2026 13:34:28 +0000 - Set version to 1.33 final + Set version to 1.41 Python/setup.py | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) -commit f2ff537e22ba7388264fa114a7662b48bc9fe452 +commit 951d93657dbb5d0032bc5de0e773bfa1c377c90b +Merge: 1e9b8754 255681df Author: Luigi Ballabio -Date: Fri, 12 Jan 2024 13:36:29 +0100 - - Update test results +Date: Thu, 8 Jan 2026 10:57:04 +0100 - Python/test/test_bonds.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + Add missing constructor to a couple of pricers (#810) -commit a5618dd568ffb8bc69717ebf1bd7960d85186896 +commit 255681df2fe1f3812a9560ad7ec7113538aa1320 Author: Luigi Ballabio -Date: Wed, 4 Oct 2023 14:18:47 +0200 +Date: Thu, 8 Jan 2026 10:05:11 +0100 + + Add missing constructor to a couple of pricers + + SWIG won't generate a default constructor if the base class doesn't have one. + + SWIG/cashflows.i | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) - Set version to 1.33 rc +commit 1e9b8754fd040f1691a0f3738dbf986b81c26e07 +Author: lballabio[bot] <224797326+lballabio-bot@users.noreply.github.com> +Date: Mon, 5 Jan 2026 19:41:32 +0000 + + Update changelog + + ChangeLog.txt | 978 +++++++++++++++++++++++----------------------------------- + 1 file changed, 392 insertions(+), 586 deletions(-) + +commit f820895a2c7d4e962df77c232116bd09e1cee5d0 +Author: lballabio[bot] <224797326+lballabio-bot@users.noreply.github.com> +Date: Mon, 5 Jan 2026 19:41:32 +0000 + + Set version to 1.41-rc Python/setup.py | 2 +- + SWIG/ql.i | 4 ++-- configure.ac | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) + 3 files changed, 4 insertions(+), 4 deletions(-) -commit a0df937f61e03287a2ad59e7f36a296949aeb5b9 +commit 99086d5271d307aa2b58ad677d3aa31d337a0c5b Author: Luigi Ballabio -Date: Mon, 8 Jan 2024 13:13:07 +0100 +Date: Mon, 5 Jan 2026 12:37:53 +0100 - Update news and changelog + Update news for 1.41 - ChangeLog.txt | 366 +++++++++++++++++++++++++--------------------------------- - News.md | 29 +++-- - 2 files changed, 169 insertions(+), 226 deletions(-) + News.md | 23 ++++++++++++++--------- + 1 file changed, 14 insertions(+), 9 deletions(-) -commit 14d6885853693e60010c0cae983c5ae40172675b -Merge: 6b3550d a282dc1 +commit 9adba846e2d00c15239508cbb30197af3fee9f72 +Merge: 0e5f50cd eef65314 Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 21:45:52 +0100 +Date: Mon, 5 Jan 2026 10:10:51 +0100 - Export Burley 2020 Sobol generator (#604) + Fix typo in method mapping for `MakeSwaption` (#808) -commit a282dc1f08d22149569006d6e5532ad8f9fbeee8 +commit eef65314846d8068173a6d920dd8753a5324081a Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 19:40:33 +0100 +Date: Mon, 5 Jan 2026 09:19:16 +0100 - Export Burley 2020 Sobol generator + Fix typo in method mapping - SWIG/randomnumbers.i | 35 +++++++++++++++++++++++++++++++++-- - 1 file changed, 33 insertions(+), 2 deletions(-) + SWIG/swaption.i | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 0e5f50cdff3bb0bceeb421a36daa483cafe4766e +Author: Luigi Ballabio +Date: Mon, 29 Dec 2025 16:44:51 +0100 + + Restore kwargs for ultimate term-structure constructor + + SWIG/termstructures.i | 3 +++ + 1 file changed, 3 insertions(+) -commit 6b3550de89b02a2daf56e24545bea00ec95c57a5 -Merge: 51baf3b f456d98 +commit 85b621bc9296f62df7982ae33df2281c573c5be0 +Merge: b66e8eca 13f727a4 Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 12:39:37 +0100 +Date: Wed, 24 Dec 2025 12:19:04 +0100 - Allow different calendars and frequencies in OISRateHelper (#600) + Export new features of overnight-indexed coupons (#806) -commit f69bd4569c8b1de4b891fff89d352faecce60aca +commit 13f727a477996308938bc77f4db7f0c0cd7df11d Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 11:38:53 +0100 +Date: Wed, 24 Dec 2025 11:35:15 +0100 - Add typemap for optional + Export new features of overnight-indexed coupons - SWIG/date.i | 26 ++++++++++++++++++++++++++ - 1 file changed, 26 insertions(+) + SWIG/cashflows.i | 133 ++++++++++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 107 insertions(+), 26 deletions(-) -commit 51baf3b94d93b9212b969718b3c79a98276502d9 -Merge: 9b338b7 9e7187b +commit b66e8ecaeca075ebb96c03f33f0b52593ba0bd56 +Merge: 2afb7e19 f335ac11 Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 10:31:43 +0100 +Date: Tue, 23 Dec 2025 20:08:05 +0100 - Export convex-monotone forward-rate curve (#603) + Export new constructor for Asian option and engine (#805) -commit 9e7187b55654428d0f1ed42f0c682ff5893d4315 +commit f335ac11662a86f9cda939b7163f88ae995a5edb Author: Luigi Ballabio -Date: Fri, 5 Jan 2024 08:39:21 +0100 +Date: Tue, 23 Dec 2025 18:37:40 +0100 - Export convex-monotone forward-rate curve + Export new constructor for Asian option and engine - SWIG/piecewiseyieldcurve.i | 1 + - 1 file changed, 1 insertion(+) + SWIG/asianoptions.i | 7 +++++++ + 1 file changed, 7 insertions(+) -commit 9b338b79e914475fc2c8d10ba32576bb379ae59c -Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> -Date: Thu, 21 Dec 2023 10:36:26 +0000 +commit 2afb7e19bfc453f81f63ed1f271d09becccf459e +Merge: 7b6a8dbb ae9dba82 +Author: Luigi Ballabio +Date: Tue, 23 Dec 2025 18:10:55 +0100 - Update copyright list in license + Export const-notional cross-currency swap helper (#804) - LICENSE.TXT | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) +commit ae9dba82b1e007032bbd937c90d7409d766a2ae2 +Author: Luigi Ballabio +Date: Tue, 23 Dec 2025 16:53:37 +0100 + + Export const-notional cross-currency swap helper -commit 5a99cf17ab29fccff39f784cfb917ab7c7f6ec50 -Merge: f6a7d43 e96ecf0 + SWIG/ratehelpers.i | 26 ++++++++++++++++++++++---- + 1 file changed, 22 insertions(+), 4 deletions(-) + +commit 7b6a8dbb71ed1d42e32a8ba3ba0bfd201694f1ac +Merge: 965f8b46 35925cd7 Author: Luigi Ballabio -Date: Thu, 21 Dec 2023 11:36:13 +0100 +Date: Tue, 23 Dec 2025 15:08:02 +0100 - SWIG changes for PR#1826, Heston Model: Support for Angled Contour Shift Integrals (#596) + Export `zeroRate` and `yoyRate` methods without lag (#803) -commit e96ecf0519e96d82b19fcd517d33403d939a5c91 +commit 965f8b460a025fb7952122d2bf3a8eb8653d9de3 Author: Luigi Ballabio -Date: Thu, 21 Dec 2023 11:14:33 +0100 +Date: Tue, 23 Dec 2025 12:37:28 +0100 - Prevent generating non-existing constructor + No need for %extend - SWIG/options.i | 2 ++ - 1 file changed, 2 insertions(+) + SWIG/cashflows.i | 83 ++++++++++++++++++++++++-------------------------------- + 1 file changed, 36 insertions(+), 47 deletions(-) + +commit 35925cd7bc1e050ca8ba45a87e2a380f94062c9c +Author: Luigi Ballabio +Date: Tue, 23 Dec 2025 12:56:41 +0100 + + Export zeroRate and yoyRate methods without lag + + SWIG/inflation.i | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +commit 73402fff013652d83efd7eb30b04678964f54cbe +Merge: d795a2b0 e12869eb +Author: Luigi Ballabio +Date: Tue, 23 Dec 2025 11:40:11 +0100 + + Add `setInterpolation` method in `BlackVarianceCurve` (#802) + +commit e12869eb5c21c0263ca92153d7b188ffbb241716 +Author: paolodelia99 +Date: Tue, 23 Dec 2025 10:29:06 +0100 + + Fix error for missing interpolators + + SWIG/volatilities.i | 4 ---- + 1 file changed, 4 deletions(-) + +commit ed53ea0206131eb59e4b93478176a730fa025f4b +Author: paolodelia99 +Date: Tue, 23 Dec 2025 10:21:18 +0100 + + wrap new overidden methon in extend + + SWIG/volatilities.i | 29 +++++++++++++++-------------- + 1 file changed, 15 insertions(+), 14 deletions(-) + +commit e00cbf4860a9750df7ac0f999195e4a62f5756f8 +Author: paolodelia99 +Date: Tue, 23 Dec 2025 10:16:33 +0100 + + Add set interpolation method in BlackVarianceCurve class + + SWIG/volatilities.i | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +commit d795a2b011fe6cdd5cdc729d358c449c0159f889 +Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Date: Mon, 15 Dec 2025 18:02:20 +0000 + + Bump peter-evans/create-pull-request from 7 to 8 + + Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7 to 8. + - [Release notes](https://github.com/peter-evans/create-pull-request/releases) + - [Commits](https://github.com/peter-evans/create-pull-request/compare/v7...v8) + + --- + updated-dependencies: + - dependency-name: peter-evans/create-pull-request + dependency-version: '8' + dependency-type: direct:production + update-type: version-update:semver-major + ... + + Signed-off-by: dependabot[bot] -commit f00a4c602edaf7074e76d806ca38cd079797a395 + .github/workflows/copyrights.yml | 2 +- + .github/workflows/increase-version-number.yml | 2 +- + .github/workflows/license-url.yml | 2 +- + .github/workflows/misspell.yml | 2 +- + .github/workflows/namespaces.yml | 2 +- + .github/workflows/prepare-release-candidate.yml | 2 +- + .github/workflows/prepare-release.yml | 2 +- + 7 files changed, 7 insertions(+), 7 deletions(-) + +commit d41d093301825d1ab17c2b71fc75b654473e0b98 +Merge: 9b8a5500 784b3b11 Author: Luigi Ballabio -Date: Thu, 21 Dec 2023 10:08:28 +0100 +Date: Tue, 9 Dec 2025 16:08:13 +0100 - Fix error on Java + Re-enable Python 3.8 and 3.9 (#800) + +commit 784b3b11d79c567585950d68a91ff1ecb7339dd9 +Author: Luigi Ballabio +Date: Tue, 9 Dec 2025 13:17:27 +0100 + + Re-enable Python 3.8 and 3.9. - Inner classes need to be declared before they're used + SWIG 4.4.1 restored support for them. - SWIG/options.i | 134 ++++++++++++++++++++++++++------------------------------- - 1 file changed, 62 insertions(+), 72 deletions(-) + Python/setup.py | 4 ++-- + Python/setup.py.in | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) -commit d2d80f7ce5ac128448bbd5486a88701ba3af348e +commit 4cc52a73c6bc9743c13293d67cd1e70fd1179218 Author: Luigi Ballabio -Date: Thu, 21 Dec 2023 08:52:47 +0100 +Date: Tue, 9 Dec 2025 13:58:34 +0100 + + Upgrade to SWIG 4.4.1 + + .github/workflows/publish-release.yml | 2 +- + dockerfiles/ci.base.Dockerfile | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) - Add missing inclusions +commit 9b8a5500bbd62d75fc83946d48fa5bb0f641945b +Merge: 1eef94d6 17908e4d +Author: Luigi Ballabio +Date: Mon, 1 Dec 2025 10:33:17 +0100 - SWIG/asianoptions.i | 1 + - SWIG/options.i | 1 + - 2 files changed, 2 insertions(+) + added semi-analytic European engine for a single asset with cash (#799) -commit fb65c39dda32b54c2be790afb1cbafc70c576224 +commit 17908e4df0143b4432111c70bef185bd56d81c72 Author: klaus spanderen -Date: Sun, 17 Dec 2023 18:38:31 +0100 +Date: Sun, 30 Nov 2025 22:00:44 +0100 - eliminate separate enum + fixed potential leap year issue. - SWIG/options.i | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) + Python/test/test_options.py | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) -commit f6a7d43603afbcc47cf1cbb2ff2727c78bd3abb2 +commit 1eef94d6a83f54cd4f53f04282b5530328086ca3 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> -Date: Mon, 11 Dec 2023 18:46:45 +0000 +Date: Mon, 24 Nov 2025 18:36:44 +0000 - Bump actions/stale from 8 to 9 + Bump actions/checkout from 5 to 6 - Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9. - - [Release notes](https://github.com/actions/stale/releases) - - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - - [Commits](https://github.com/actions/stale/compare/v8...v9) + Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. + - [Release notes](https://github.com/actions/checkout/releases) + - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) + - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - - dependency-name: actions/stale + - dependency-name: actions/checkout + dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] - .github/workflows/stale.yml | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + .github/workflows/copyrights.yml | 2 +- + .github/workflows/devenv-images.yml | 10 +++++----- + .github/workflows/increase-version-number.yml | 2 +- + .github/workflows/license-url.yml | 2 +- + .github/workflows/linux.yml | 2 +- + .github/workflows/misspell.yml | 2 +- + .github/workflows/namespaces.yml | 2 +- + .github/workflows/prepare-release-candidate.yml | 2 +- + .github/workflows/prepare-release.yml | 2 +- + .github/workflows/publish-release.yml | 2 +- + 10 files changed, 14 insertions(+), 14 deletions(-) + +commit 2021282adf61d5a5f05d9d57c1ede2d9ad290704 +Merge: 847c5783 2481867d +Author: Luigi Ballabio +Date: Fri, 21 Nov 2025 10:33:54 +0100 -commit d08a20ee1d343e4063075953b73e4ae2103f04df + Expose data on piecewise inflation curves (#797) + +commit 2481867d84bbd6e7dd5e151496a35513acce5bb4 Author: Eugene Toder -Date: Sat, 2 Dec 2023 07:09:03 -0500 +Date: Thu, 20 Nov 2025 13:41:54 -0500 - Allow different calendars and frequencies in OISRateHelper - - Allow setting different calendars and payment frequencies for fixed and - overnight legs. + Expose data on piecewise inflation curves - SWIG/ratehelpers.i | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) + SWIG/inflation.i | 2 ++ + 1 file changed, 2 insertions(+) -commit 5babd27f2683dd798222b376f17edfe8ebf99596 -Merge: 0dba37c 403e8bd +commit 847c578397a275e994ce0a5149d35e1fbd067f4c +Merge: 0bf01839 69f63c7b Author: Luigi Ballabio -Date: Thu, 9 Nov 2023 17:29:04 +0100 +Date: Mon, 17 Nov 2025 09:22:12 +0100 + + Expose argument for enableExtrapolation (#795) - Change payment lag from Naturals to Ints (#598) +commit 69f63c7bcb8072fe9ae541930988e3af519a9016 +Author: Eugene Toder +Date: Fri, 14 Nov 2025 14:38:14 -0500 -commit 0dba37cf99ed25f5eff00979f684e75003ac69b0 -Merge: 84e1c52 995bdd4 + Expose argument for enableExtrapolation + + SWIG/termstructures.i | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +commit 0bf01839659042659273cd9b95ffc358bc7f8f93 Author: Luigi Ballabio -Date: Thu, 9 Nov 2023 15:23:27 +0100 +Date: Thu, 13 Nov 2025 10:12:08 +0100 + + Fix a few typos + + Java/examples/Bonds.java | 4 ++-- + Python/README.md | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) - Expose `reset` function in calendar base class (#597) +commit 37cf3e4ee5c88010d527e5b78dced94ba6027ced +Author: klaus spanderen +Date: Wed, 12 Nov 2025 20:35:35 +0100 -commit 995bdd4fe356ce559e80dc69d9880e76a08d5011 -Author: Fredrik Gerdin Börjesson -Date: Thu, 9 Nov 2023 12:21:50 +0100 + added semi-analytic European engine for a single asset with cash + dividends - Expose `resetAddedAndRemovedHolidays` for calendar + Python/test/test_options.py | 46 +++++++++++++++++++++++++++++++++++++++++++-- + SWIG/options.i | 17 +++++++++++++++++ + 2 files changed, 61 insertions(+), 2 deletions(-) - Python/test/test_calendars.py | 15 +++++++++++++++ - SWIG/calendars.i | 2 ++ - 2 files changed, 17 insertions(+) +commit 04d62c5aeb214e2e58c6b6835244bec4359ac9a5 +Merge: a48b4208 23f732c1 +Author: Luigi Ballabio +Date: Tue, 11 Nov 2025 17:22:05 +0100 -commit 403e8bd723f01988cf6b842350c79b603feb63bf -Author: Fredrik Gerdin Börjesson -Date: Thu, 9 Nov 2023 12:16:36 +0100 + Enable building wheels for free-threading Python; increase minimum supported Python version to 3.10 (#794) - Change payment lag from Naurals to Ints +commit 23f732c17939aa37f78421feec1d6b7a71978125 +Author: Luigi Ballabio +Date: Tue, 11 Nov 2025 14:58:21 +0100 - SWIG/bonds.i | 4 ++-- - SWIG/cashflows.i | 16 ++++++++-------- - SWIG/ratehelpers.i | 4 ++-- - SWIG/swap.i | 8 ++++---- - 4 files changed, 16 insertions(+), 16 deletions(-) + Upgrade to SWIG 4.4.0 in workflows -commit 4a1d510abefcf879eb8cab5f16fb4147e5169f82 -Author: klaus spanderen -Date: Mon, 6 Nov 2023 22:52:11 +0100 + .github/workflows/publish-release.yml | 2 +- + dockerfiles/ci.base.Dockerfile | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) - make constructor explicit +commit a48b4208777c931f3a13bea2efbf0f8a373fa82f +Merge: 70048a0f 39611e80 +Author: Luigi Ballabio +Date: Tue, 11 Nov 2025 13:52:57 +0100 - SWIG/integrals.i | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) + Export a few missing Bond inspectors (#793) + +commit 39611e80abf8d58338fee8b682abdeb317248ba6 +Author: Luigi Ballabio +Date: Tue, 11 Nov 2025 12:51:22 +0100 + + Export a few missing Bond inspectors -commit 84e1c52af3f726b16654ca19df1c20d7d41dd183 -Merge: 8eeed9b d96b955 + SWIG/bonds.i | 3 +++ + 1 file changed, 3 insertions(+) + +commit 70048a0f8300f21c77c76bf38fb73b48eb8138ed +Merge: 5bc9d5ca c3d9ad29 +Author: Luigi Ballabio +Date: Wed, 5 Nov 2025 15:26:12 +0100 + + Expose ZeroCouponInflationSwap::fixedLegBPS() (#791) + +commit 5bc9d5ca4197a76d52d0d3b354aca87896628565 +Merge: 0c92ed99 cdbcbe83 Author: Luigi Ballabio -Date: Fri, 20 Oct 2023 22:08:48 +0200 +Date: Mon, 3 Nov 2025 09:45:36 +0100 + + Enable rounding in UFR interpolation (#788) - Avoid features deprecated in version 1.28 and removed in 1.33 (#593) +commit cdbcbe833b41691b739d61be59263b36474ae70b +Author: Marcin Rybacki +Date: Thu, 30 Oct 2025 16:48:47 +0100 -commit 8eeed9b624f55b8192e89c292677840afc33cdaf -Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> -Date: Fri, 20 Oct 2023 13:24:21 +0000 + Implemented PR feedback. - Update copyright list in license + SWIG/common.i | 21 +++++++++++++++++++++ + SWIG/termstructures.i | 16 ---------------- + 2 files changed, 21 insertions(+), 16 deletions(-) - LICENSE.TXT | 1 + +commit c3d9ad291cbc8704d5ace90f904003337b756466 +Author: Eugene Toder +Date: Tue, 28 Oct 2025 13:45:30 -0400 + + Expose ZeroCouponInflationSwap::fixedLegBPS() + + SWIG/inflation.i | 1 + 1 file changed, 1 insertion(+) -commit f2ce94460fba41c0800139170e74273025bee34d -Merge: 2a70b4c dbe9a48 +commit 0c92ed9941075cce0e3eb357d1cf409fb4b40a5c +Merge: d219d0c7 068d66dc +Author: Luigi Ballabio +Date: Mon, 27 Oct 2025 10:02:45 +0100 + + Adding vanna and volga functions (#790) + +commit 068d66dce19173c447c9acc0ba3f26ca6b16d5ac +Author: kp9991 +Date: Sat, 25 Oct 2025 16:05:31 +1100 + + Adding vanna and volga functions + + SWIG/options.i | 4 ++++ + 1 file changed, 4 insertions(+) + +commit 6f1d8684238c812933e05baf1f53529295cd0ee6 +Author: Luigi Ballabio +Date: Tue, 21 Oct 2025 16:06:50 +0200 + + Upgrade minimum Python version to 3.10 + + This is requested for compatibility with SWIG 4.4, that + we'll use going forward in order to enable building wheels + for free-threading Python. + + Python/setup.py | 46 +++++++++++++++++++++++++--------------------- + Python/setup.py.in | 46 +++++++++++++++++++++++++--------------------- + 2 files changed, 50 insertions(+), 42 deletions(-) + +commit d219d0c72cab206d9ddddf2156a87e40609563b8 +Author: Luigi Ballabio +Date: Tue, 21 Oct 2025 16:10:21 +0200 + + Unpin base for devenv image + + .github/workflows/devenv-images.yml | 2 +- + dockerfiles/ci.csharp.Dockerfile | 5 +++++ + 2 files changed, 6 insertions(+), 1 deletion(-) + +commit 4fef4aba7e7d34266160ddd78d5705e277ee2af8 +Merge: 85ed8efc 6a39261c +Author: Luigi Ballabio +Date: Fri, 24 Oct 2025 09:17:38 +0200 + + Replace passed NULL with NA in R examples for compatibility with R 3.5 (#789) + +commit 6a39261c1b610e7cd453f9b1a19b7a179daeef16 +Author: Luigi Ballabio +Date: Thu, 23 Oct 2025 08:09:18 +0200 + + Replace passed NULL with NA in R examples + + NULL doesn't work with R 3.5 and the current typemaps + + R/demo/european-option.R | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +commit 85ed8efc12645d0b361341941c88487cee871013 +Author: Luigi Ballabio +Date: Wed, 22 Oct 2025 15:14:38 +0200 + + Move CI build to .Net 9 + + CSharp/Makefile.am | 20 ++++++++++---------- + CSharp/csharp/NQuantLib.csproj | 2 +- + .../BermudanSwaption/BermudanSwaption.csproj | 2 +- + CSharp/examples/EquityOption/EquityOption.csproj | 2 +- + .../FiniteDifferenceMethods.csproj | 2 +- + CSharp/examples/Times/Times.csproj | 2 +- + dockerfiles/ci.csharp.Dockerfile | 2 +- + 7 files changed, 16 insertions(+), 16 deletions(-) + +commit 486668c3e029144209728a31ada4c2c0ac7c5623 +Merge: 2e2fce59 4d9ddd92 +Author: Luigi Ballabio +Date: Tue, 21 Oct 2025 13:02:08 +0200 + + Expose forDates on inflation swap helpers (#787) + +commit 2e2fce59eef9476bfd2524ea77a2ef03dd36df0d +Merge: d636f144 77357411 +Author: Luigi Ballabio +Date: Tue, 21 Oct 2025 11:18:34 +0200 + + Rename piecewise spread curves classes to *SpreadDiscount (#786) + +commit 4d9ddd92f1cb49b43c864330f6353f7da394b36f +Author: Eugene Toder +Date: Mon, 20 Oct 2025 13:20:32 -0400 + + Expose forDates on inflation swap helpers + + SWIG/inflation.i | 55 ++++++++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 46 insertions(+), 9 deletions(-) + +commit 77357411385efd3d83c918279bd4fc469f26d234 +Author: Eugene Toder +Date: Sat, 18 Oct 2025 17:28:27 -0400 + + Rename piecewise spread curves classes to *SpreadDiscount + + For consistency with InterpolatedSpreadDiscountCurve classes. + + SWIG/piecewiseyieldcurve.i | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +commit d636f14414e020078de9cbaa0761467106046043 +Merge: b9d03fca 9b904d87 Author: Luigi Ballabio -Date: Fri, 20 Oct 2023 15:24:10 +0200 +Date: Thu, 16 Oct 2025 08:42:39 +0200 - Add tests for ql.BondFunctions.* (#592) + Expose InterpolatedSpreadDiscountCurve (#785) + +commit 9b904d879dd1d1118eb814fc848e8983a349243d +Author: Eugene Toder +Date: Wed, 15 Oct 2025 12:06:43 -0400 -commit d96b955bccb50534e86ae59fb9ebce58ec4f7e65 + Expose InterpolatedSpreadDiscountCurve + + SWIG/ql.i | 1 + + SWIG/spreaddiscountcurve.i | 37 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + +commit b9d03fca2f8e6b251e62bbcf00cca9208aa33af9 +Merge: 7ad4fc40 b319a172 Author: Luigi Ballabio -Date: Fri, 20 Oct 2023 12:16:11 +0200 +Date: Wed, 15 Oct 2025 16:17:22 +0200 + + Expose more spread curve types (#784) + +commit b319a172a896e1b73c15822ac8507e3109f7e187 +Author: Eugene Toder +Date: Tue, 14 Oct 2025 14:09:31 -0400 - Avoid features deprecated in version 1.28 and removed in 1.33 + Expose more spread curve types - Python/test/test_bonds.py | 48 ++++----------- - SWIG/bonds.i | 150 ++-------------------------------------------- - SWIG/volatilities.i | 6 -- - 3 files changed, 14 insertions(+), 190 deletions(-) + SWIG/piecewiseyieldcurve.i | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) -commit 2a70b4c2a11dd3f2be0148643374abd0ab4db131 +commit 7ad4fc406fc93b45ee9a559f795de424bb2d3965 +Merge: 864b090f be0789ef Author: Luigi Ballabio -Date: Fri, 20 Oct 2023 11:34:41 +0200 +Date: Tue, 14 Oct 2025 15:10:04 +0200 - Set version to 1.33-dev + Avoid features removed from underlying C++ library (#783) + +commit 864b090f0a0f78922288f8ff9c96828578a512da +Author: lballabio[bot] <224797326+lballabio-bot@users.noreply.github.com> +Date: Tue, 14 Oct 2025 09:34:11 +0000 + + Set version to 1.41-dev Python/setup.py | 2 +- R/DESCRIPTION | 2 +- configure.ac | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) -commit dbe9a484142577ddad970379867927a44f3fd5ac -Author: Francois Botha -Date: Wed, 18 Oct 2023 14:50:59 +0200 +commit be0789efdc4868d8c0df405ca9a54c1cb818aa22 +Author: Luigi Ballabio +Date: Tue, 14 Oct 2025 11:21:04 +0200 - Add tests for ql.BondFunctions.* + Update tests - Python/test/test_bondfunctions.py | 232 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 232 insertions(+) + Python/test/test_equityindex.py | 2 +- + Python/test/test_swap.py | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) -commit 0448ac9055765af678d22cde159706b45b3d2746 -Author: klaus spanderen -Date: Wed, 11 Oct 2023 22:42:16 +0200 +commit d89406cc88b7e126980cabe9cc72381c2a95c68c +Author: Luigi Ballabio +Date: Tue, 14 Oct 2025 10:10:51 +0200 + + Add workflow to increase version number after release + + .github/workflows/increase-version-number.yml | 37 +++++++++++++++++++++++++++ + 1 file changed, 37 insertions(+) + +commit f54285262cdd40acc00e36e97928e1f979675b7c +Author: Luigi Ballabio +Date: Tue, 14 Oct 2025 10:40:24 +0200 + + Avoid features removed from underlying library in version 1.41 + + SWIG/indexes.i | 5 -- + SWIG/inflation.i | 137 +++++++++++++---------------------------------------- + SWIG/ratehelpers.i | 21 -------- + SWIG/swap.i | 51 -------------------- + 4 files changed, 34 insertions(+), 180 deletions(-) + +commit 1923c81a2809618c9e85f5408201060af4bafd68 +Author: Luigi Ballabio +Date: Tue, 14 Oct 2025 09:52:20 +0200 + + Fix links in draft release notes + + .github/workflows/publish-release.yml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit 2c903776a294111a39aa74a2b35be676b086a1a9 +Author: Luigi Ballabio +Date: Tue, 14 Oct 2025 09:50:56 +0200 + + Don't run CI builds when pushing a tag + + .github/workflows/linux.yml | 2 ++ + 1 file changed, 2 insertions(+) + +commit 2e34601a490a6096d0a79d864f8701cfb2d7c905 +Author: Marcin Rybacki +Date: Thu, 9 Oct 2025 16:07:59 +0200 + + Exposed changes related to UFR rounding. + + Python/test/test_termstructures.py | 2 +- + SWIG/termstructures.i | 17 ++++++++++++++++- + 2 files changed, 17 insertions(+), 2 deletions(-) + +commit ad3db45c4b1f668f11258c7f0a786a97cc394086 +Author: Marcin Rybacki +Date: Thu, 9 Oct 2025 09:55:10 +0200 - Heston update + Updated `UltimateForwardTermStructure` signature - SWIG/daycounters.i | 6 +++ - SWIG/integrals.i | 27 ++++++++++ - SWIG/interpolation.i | 2 + - SWIG/options.i | 144 +++++++++++++++++++++++++++++---------------------- - 4 files changed, 118 insertions(+), 61 deletions(-) + SWIG/termstructures.i | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Python/CMakeLists.txt b/Python/CMakeLists.txt index 74c99e9c..fc12bd66 100644 --- a/Python/CMakeLists.txt +++ b/Python/CMakeLists.txt @@ -19,39 +19,15 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# +# ############################################################################## -if(NOT "${QLR_PYTHON_VERSION}" STREQUAL "") - find_package (Python3 ${QLR_PYTHON_VERSION} EXACT COMPONENTS Interpreter Development.Module REQUIRED) -else() - find_package (Python3 COMPONENTS Interpreter Development.Module REQUIRED) -endif() - -# fetch pybind11 dependency on the fly -include(FetchContent) - -FetchContent_Declare(pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11.git - GIT_TAG v2.11.1) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() -if(WIN32) - set(QL_MODULE_SUFFIX ".${Python3_SOABI}.pyd") -else() - set(QL_MODULE_SUFFIX ".${Python3_SOABI}${CMAKE_SHARED_MODULE_SUFFIX}") -endif() - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_extensions.py.in - ${CMAKE_CURRENT_BINARY_DIR}/build_extensions.py @ONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyproject.toml.in - ${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml @ONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README.md - ${CMAKE_CURRENT_BINARY_DIR}/README.md COPYONLY) +find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module) +find_package(SWIG 4.0 REQUIRED COMPONENTS python) +include(UseSWIG) +# must be the same pybind11 as xad-py's, as converters.cpp shares its Real type +set(PYBIND11_FINDPYTHON ON) +add_subdirectory(${PROJECT_SOURCE_DIR}/lib/pybind11 ${CMAKE_BINARY_DIR}/pybind11) add_subdirectory(QuantLib_Risks) - diff --git a/Python/QuantLib_Risks/CMakeLists.txt b/Python/QuantLib_Risks/CMakeLists.txt index 6eaad302..841352ab 100644 --- a/Python/QuantLib_Risks/CMakeLists.txt +++ b/Python/QuantLib_Risks/CMakeLists.txt @@ -1,5 +1,5 @@ ############################################################################## -# Build SWIG binary module and optionally wheel package. +# Build and install the SWIG binary module and the Python package. # # This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled # for risk computation using automatic differentiation. It uses XAD, @@ -19,64 +19,36 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# +# ############################################################################## -set_property(SOURCE ${PROJECT_SOURCE_DIR}/SWIG/quantlib.i PROPERTY CPLUSPLUS ON) -set_property(SOURCE ${PROJECT_SOURCE_DIR}/SWIG/quantlib.i PROPERTY USE_SWIG_DEPENDENCIES TRUE) -set_property(SOURCE ${PROJECT_SOURCE_DIR}/SWIG/quantlib.i PROPERTY COMPILE_OPTIONS - -DQL_XAD=1 - -DQLR_VERSION=\"${QLR_VERSION}\" +set(QLR_SWIG_SOURCE ${PROJECT_SOURCE_DIR}/SWIG/quantlib.i) +set_property(SOURCE ${QLR_SWIG_SOURCE} PROPERTY CPLUSPLUS ON) +set_property(SOURCE ${QLR_SWIG_SOURCE} PROPERTY USE_SWIG_DEPENDENCIES TRUE) +set_property(SOURCE ${QLR_SWIG_SOURCE} PROPERTY COMPILE_OPTIONS + -DQL_XAD=1 + -DQLR_VERSION=\"${QLR_VERSION}\" -DQLR_HEX_VERSION=${QLR_HEX_VERSION}) + +# builds _QuantLib_Risks and generates QuantLib_Risks.py swig_add_library(QuantLib_Risks LANGUAGE python - OUTPUT_DIR . - SOURCES ${PROJECT_SOURCE_DIR}/SWIG/quantlib.i converters.cpp -) -target_link_libraries(QuantLib_Risks PRIVATE Python3::Module QuantLib::QuantLib pybind11::headers) -target_compile_features(QuantLib_Risks PRIVATE cxx_std_17) -target_compile_definitions(QuantLib_Risks PRIVATE QL_XAD=1 QLR_VERSION=\"${QLR_VERSION}\" QLR_HEX_VERSION=${QLR_HEX_VERSION}) -target_include_directories(QuantLib_Risks PRIVATE .) -set_property(TARGET QuantLib_Risks PROPERTY SUFFIX "${QL_MODULE_SUFFIX}") -if(MSVC) - # we build with the static runtime in Windows - set_target_properties(QuantLib_Risks PROPERTIES - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${QLR_SWIG_SOURCE} converters.cpp) +target_link_libraries(QuantLib_Risks PRIVATE QuantLib::QuantLib pybind11::module) +target_compile_definitions(QuantLib_Risks PRIVATE + QL_XAD=1 + QLR_VERSION=\"${QLR_VERSION}\" + QLR_HEX_VERSION=${QLR_HEX_VERSION}) +target_include_directories(QuantLib_Risks PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +set_target_properties(QuantLib_Risks PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}") +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo") + pybind11_strip(QuantLib_Risks) endif() -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/__init__.py - ${CMAKE_CURRENT_BINARY_DIR}/__init__.py COPYONLY) - -if(WIN32) - set(VENV_PYTHON ${CMAKE_CURRENT_BINARY_DIR}/../.venv/Scripts/python.exe) -else() - set(VENV_PYTHON ${CMAKE_CURRENT_BINARY_DIR}/../.venv/bin/python) -endif() - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../wheel.stamp - DEPENDS QuantLib_Risks - ${CMAKE_CURRENT_BINARY_DIR}/../pyproject.toml - ${CMAKE_CURRENT_BINARY_DIR}/../build_extensions.py - ${CMAKE_CURRENT_BINARY_DIR}/../README.md - __init__.py - # create environment - COMMAND ${Python3_EXECUTABLE} -m venv .venv - # install build dependencies - COMMAND ${VENV_PYTHON} -m pip install -U pip setuptools wheel build - # build the wheel - COMMAND ${VENV_PYTHON} -m pip wheel . - # install the wheel in the venv - COMMAND ${VENV_PYTHON} -m pip install QuantLib_Risks*.whl --force-reinstall - # install other dependencies for running the examples / tests - COMMAND ${VENV_PYTHON} -m pip install -r ${PROJECT_SOURCE_DIR}/binder/requirements.txt - # update the stamp file, to keep track of when we last build it - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/../wheel.stamp - COMMENT "Building Python wheel..." - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/.. -) - -# main target for building the python wheel - custom command above will be hooked to this target -add_custom_target(python_wheel - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/../wheel.stamp) - +get_property(QLR_SWIG_PY TARGET QuantLib_Risks PROPERTY SWIG_SUPPORT_FILES) +install(TARGETS QuantLib_Risks + LIBRARY DESTINATION QuantLib_Risks COMPONENT python + RUNTIME DESTINATION QuantLib_Risks COMPONENT python) +install(FILES __init__.py ${QLR_SWIG_PY} + DESTINATION QuantLib_Risks COMPONENT python) diff --git a/Python/README.md b/Python/README.md index fd58de0c..30935146 100644 --- a/Python/README.md +++ b/Python/README.md @@ -12,6 +12,23 @@ Useful links: pip install QuantLib-Risks ``` +Binary wheels are provided for CPython 3.10 to 3.14 on Linux (x86_64, aarch64), macOS (x86_64, arm64) +and Windows (x86_64). The package requires the matching [xad](https://pypi.org/project/xad/) release, +which pip installs automatically. + +### Building from source + +On other platforms pip builds the package from the source distribution, which compiles QuantLib +and takes a while. This needs a C++17 compiler; CMake, Ninja and SWIG are +fetched as build dependencies, and Boost headers are downloaded unless found on the system +(or via `BOOST_ROOT`). To build from a clone of the +[repository](https://github.com/auto-differentiation/QuantLib-Risks-Py): + +``` +git clone --recurse-submodules https://github.com/auto-differentiation/QuantLib-Risks-Py.git +pip install -v ./QuantLib-Risks-Py +``` + ## Usage Illustration ```python @@ -38,4 +55,4 @@ with Tape() as t: ## Related Projects - XAD Comprehensive automatic differentiation in [Python](https://github.com/auto-differentiation/xad-py) and [C++](https://github.com/auto-differentiation/xad) -- QuantLib-Risks: Fast risk evaluations in [Python](https://github.com/auto-differentiation/QuantLib-Risks-Py) and [C++](https://github.com/auto-differentiation/QuantLib-Risks-Cpp) +- QuantLib-Risks: Fast risk evaluations in [Python](https://github.com/auto-differentiation/QuantLib-Risks-Py) and [C++ (QuantLibAAD)](https://github.com/auto-differentiation/QuantLibAAD) diff --git a/Python/build_extensions.py.in b/Python/build_extensions.py.in deleted file mode 100644 index 229f3ef5..00000000 --- a/Python/build_extensions.py.in +++ /dev/null @@ -1,88 +0,0 @@ -############################################################################## -# -# Build file for extension module - using pre-built binary with SWIG/CMake. -# -# This was inspired by: -# https://github.com/tim-mitchell/prebuilt_binaries/tree/main -# -# -# This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled -# for risk computation using automatic differentiation. It uses XAD, -# a fast and comprehensive C++ library for automatic differentiation. -# -# Copyright (C) 2010-2024 Xcelerit Computing Ltd. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import os -from pathlib import Path -from distutils.file_util import copy_file -try: - from setuptools import Extension - from setuptools.command.build_ext import build_ext -except ImportError: - from distutils.command.build_ext import build_ext - from distutils.extension import Extension - - -class QuantLibExtension(Extension): - """Extension module for QuantLib, using the pre-built file from CMAKE instead of - actually building it.""" - - def __init__(self, name: str, input_file: str): - filepath = Path(input_file) - if not filepath.exists(): - raise ValueError(f"extension file {input_file} does not exist") - self.input_file = input_file - - super().__init__(f"QuantLib_Risks.{name}", ["dont-need-this-source-file.c"]) - -class ql_build_ext(build_ext): - """Overrides build_ext to simply copy the file built with CMake into the - right location, rather than actually building it""" - - def run(self): - for ext in self.extensions: - if not isinstance(ext, QuantLibExtension): - raise ValueError("Only pre-built extensions supported") - - fullname = self.get_ext_fullname(ext.name) - filename = self.get_ext_filename(fullname) - dest_path = Path(self.build_lib) / "QuantLib_Risks" - dest_path.mkdir(parents=True, exist_ok=True) - dest_filename = dest_path / os.path.basename(filename) - - copy_file( - ext.input_file, - dest_filename, - verbose=self.verbose, - dry_run=self.dry_run, - ) - - if self.inplace: - self.copy_extensions_to_source() - -def build(setup_kwargs: dict): - """Main extension build file""" - ext_modules = [QuantLibExtension("_QuantLib_Risks", - 'QuantLib_Risks/_QuantLib_Risks@QL_MODULE_SUFFIX@')] - setup_kwargs.update( - { - "ext_modules": ext_modules, - "cmdclass": {"build_ext": ql_build_ext}, - "zip_safe": False - } - ) diff --git a/Python/examples/bermudan-swaption.py b/Python/examples/bermudan-swaption.py index 23fbcb6e..5f642415 100644 --- a/Python/examples/bermudan-swaption.py +++ b/Python/examples/bermudan-swaption.py @@ -93,7 +93,7 @@ def calibrate(model, helpers, l, name): # This is a flat yield term structure implying a 1x5 swap at 5%. -rate = ql.QuoteHandle(ql.SimpleQuote(0.04875825)) +rate = ql.makeQuoteHandle(0.04875825) termStructure = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, rate, ql.Actual365Fixed())) # Define the ATM/OTM/ITM swaps: @@ -166,7 +166,7 @@ def calibrate(model, helpers, l, name): ql.SwaptionHelper( maturity, length, - ql.QuoteHandle(ql.SimpleQuote(vol)), + ql.makeQuoteHandle(vol), index, index.tenor(), index.dayCounter(), diff --git a/Python/examples/bonds.py b/Python/examples/bonds.py index 4cb27b01..44f65604 100644 --- a/Python/examples/bonds.py +++ b/Python/examples/bonds.py @@ -78,7 +78,7 @@ zcHelpers = [ ql.DepositRateHelper( - ql.QuoteHandle(ql.SimpleQuote(r)), tenor, fixingDays, calendar, ql.ModifiedFollowing, True, zcBondsDayCounter + ql.makeQuoteHandle(r), tenor, fixingDays, calendar, ql.ModifiedFollowing, True, zcBondsDayCounter ) for (r, tenor) in zcQuotes ] @@ -113,7 +113,7 @@ ) bondsHelpers.append( ql.FixedRateBondHelper( - ql.QuoteHandle(ql.SimpleQuote(marketQuote)), + ql.makeQuoteHandle(marketQuote), settlementDays, 100.0, schedule, @@ -154,7 +154,7 @@ depositDayCounter = ql.Actual360() depositHelpers = [ ql.DepositRateHelper( - ql.QuoteHandle(ql.SimpleQuote(rate)), tenor, fixingDays, calendar, ql.ModifiedFollowing, True, depositDayCounter + ql.makeQuoteHandle(rate), tenor, fixingDays, calendar, ql.ModifiedFollowing, True, depositDayCounter ) for rate, tenor in dQuotes ] @@ -166,7 +166,7 @@ forwardStart = ql.Period(1, ql.Days) swapHelpers = [ ql.SwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(rate)), + ql.makeQuoteHandle(rate), tenor, calendar, swFixedLegFrequency, @@ -326,9 +326,12 @@ # Clean price to yield: floatingRateBond.bondYield( - floatingRateBond.cleanPrice(), + ql.BondPrice( + floatingRateBond.cleanPrice(), + ql.BondPrice.Clean, + ), ql.Actual360(), ql.Compounded, ql.Annual, - settlementDate + settlementDate, ) diff --git a/Python/examples/capsfloors.py b/Python/examples/capsfloors.py index cf91b5b1..303d0b1e 100644 --- a/Python/examples/capsfloors.py +++ b/Python/examples/capsfloors.py @@ -79,7 +79,7 @@ strike = 0.02 cap = ql.Cap(ibor_leg, [strike]) -vols = ql.QuoteHandle(ql.SimpleQuote(0.547295)) +vols = ql.makeQuoteHandle(0.547295) engine = ql.BlackCapFloorEngine(ts_handle, vols) cap.setPricingEngine(engine) print("Value of Caps given constant volatility:", cap.NPV()) diff --git a/Python/examples/cds.py b/Python/examples/cds.py index 139abc2f..4891c174 100644 --- a/Python/examples/cds.py +++ b/Python/examples/cds.py @@ -65,7 +65,7 @@ instruments = [ ql.SpreadCdsHelper( - ql.QuoteHandle(ql.SimpleQuote(s)), + ql.makeQuoteHandle(s), tenor, 0, calendar, diff --git a/Python/examples/gaussian1d-models.py b/Python/examples/gaussian1d-models.py index e400c1a8..a3e36566 100644 --- a/Python/examples/gaussian1d-models.py +++ b/Python/examples/gaussian1d-models.py @@ -109,7 +109,7 @@ def calibration_data(basket, volatilities): # %% refDate = ql.Date(30, 4, 2014) -ql.Settings.instance().setEvaluationDate(refDate) +ql.Settings.instance().evaluationDate = refDate # %% [markdown] # We assume a multicurve setup, for simplicity with flat yield term structures. @@ -119,9 +119,9 @@ def calibration_data(basket, volatilities): # For the volatility we assume a flat swaption volatility at 20%. # %% -forward6mQuote = ql.QuoteHandle(ql.SimpleQuote(0.025)) -oisQuote = ql.QuoteHandle(ql.SimpleQuote(0.02)) -volQuote = ql.QuoteHandle(ql.SimpleQuote(0.2)) +forward6mQuote = ql.makeQuoteHandle(0.025) +oisQuote = ql.makeQuoteHandle(0.02) +volQuote = ql.makeQuoteHandle(0.2) # %% dc = ql.Actual365Fixed() @@ -187,8 +187,8 @@ def calibration_data(basket, volatilities): # %% stepDates = exerciseDates[:-1] -sigmas = [ql.QuoteHandle(ql.SimpleQuote(0.01)) for x in range(1, 10)] -reversion = [ql.QuoteHandle(ql.SimpleQuote(0.01))] +sigmas = [ql.makeQuoteHandle(0.01) for x in range(1, 10)] +reversion = [ql.makeQuoteHandle(0.01)] # %% [markdown] # The model's curve is set to the 6m forward curve. Note that the model adapts automatically to other curves where appropriate (e.g. if an index requires a different forwarding curve) or where explicitly specified (e.g. in a swaption pricing engine). @@ -197,7 +197,7 @@ def calibration_data(basket, volatilities): gsr = ql.Gsr(t0_curve, stepDates, sigmas, reversion) swaptionEngine = ql.Gaussian1dSwaptionEngine(gsr, 64, 7.0, True, False, t0_Ois) nonstandardSwaptionEngine = ql.Gaussian1dNonstandardSwaptionEngine( - gsr, 64, 7.0, True, False, ql.QuoteHandle(ql.SimpleQuote(0)), t0_Ois) + gsr, 64, 7.0, True, False, ql.makeQuoteHandle(0.0), t0_Ois) # %% swaption.setPricingEngine(nonstandardSwaptionEngine) @@ -385,7 +385,7 @@ def calibration_data(basket, volatilities): # %% swaption4 = ql.FloatFloatSwaption(underlying4, exercise) floatSwaptionEngine = ql.Gaussian1dFloatFloatSwaptionEngine( - gsr, 64, 7.0, True, False, ql.QuoteHandle(ql.SimpleQuote(0)), t0_Ois, True) + gsr, 64, 7.0, True, False, ql.makeQuoteHandle(0.0), t0_Ois, True) swaption4.setPricingEngine(floatSwaptionEngine) # %% [markdown] @@ -394,7 +394,7 @@ def calibration_data(basket, volatilities): # %% leg0 = underlying4.leg(0) leg1 = underlying4.leg(1) -reversionQuote = ql.QuoteHandle(ql.SimpleQuote(0.01)) +reversionQuote = ql.makeQuoteHandle(0.01) swaptionVolHandle = ql.SwaptionVolatilityStructureHandle(swaptionVol) cmsPricer = ql.LinearTsrPricer(swaptionVolHandle, reversionQuote) iborPricer = ql.BlackIborCouponPricer() @@ -460,7 +460,7 @@ def calibration_data(basket, volatilities): # %% floatEngineMarkov = ql.Gaussian1dFloatFloatSwaptionEngine( - markov, 16, 7.0, True, False, ql.QuoteHandle(ql.SimpleQuote(0)), t0_Ois, True) + markov, 16, 7.0, True, False, ql.makeQuoteHandle(0.0), t0_Ois, True) # %% [markdown] # The option npv is the markov model is: diff --git a/Python/examples/isda-engine.py b/Python/examples/isda-engine.py index 98bbb406..215a81e6 100644 --- a/Python/examples/isda-engine.py +++ b/Python/examples/isda-engine.py @@ -48,7 +48,7 @@ interactive = 'get_ipython' in globals() trade_date = ql.Date(21,5,2009) -ql.Settings.instance().setEvaluationDate(trade_date) +ql.Settings.instance().evaluationDate = trade_date ql.IborCoupon.createAtParCoupons() @@ -156,7 +156,7 @@ probabilityCurve.linkTo( ql.FlatHazardRate(0,ql.WeekendsOnly(), - ql.QuoteHandle(ql.SimpleQuote(h)), + ql.makeQuoteHandle(h), ql.Actual365Fixed())) engine = ql.IsdaCdsEngine(probabilityCurve,recovery,discountCurve) diff --git a/Python/examples/multicurve-bootstrapping.py b/Python/examples/multicurve-bootstrapping.py index 173feb8b..f3b0034f 100644 --- a/Python/examples/multicurve-bootstrapping.py +++ b/Python/examples/multicurve-bootstrapping.py @@ -155,7 +155,7 @@ def priceMulticurveBootstrappingSwap( ), ] eoniaInstruments.extend( - ql.DatedOISRateHelper(startDate, endDate, ql.QuoteHandle(quote), eonia) + ql.OISRateHelper.forDates(startDate, endDate, ql.QuoteHandle(quote), eonia) for startDate, endDate, quote in datedOisQuotes ) diff --git a/Python/examples/slv.py b/Python/examples/slv.py index c3212f9f..7ed47e28 100644 --- a/Python/examples/slv.py +++ b/Python/examples/slv.py @@ -78,7 +78,7 @@ dc = ql.Actual365Fixed() spot = 100 -underlying = ql.QuoteHandle(ql.SimpleQuote(spot)) +underlying = ql.makeQuoteHandle(spot) riskFreeRate = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, 0.05, dc)) dividendYield = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, 0.025, dc)) @@ -132,7 +132,7 @@ ax.set_xlabel("ln(S)") ax.set_ylabel("Time") -ax.text2D(0.225, 0.985, "Leverage Function with $\eta=1.0$", transform=ax.transAxes) +ax.text2D(0.225, 0.985, r"Leverage Function with $\eta=1.0$", transform=ax.transAxes) fig.colorbar(surf, shrink=0.75, aspect=14) diff --git a/Python/examples/swap.py b/Python/examples/swap.py index 4f651102..44fe6108 100644 --- a/Python/examples/swap.py +++ b/Python/examples/swap.py @@ -144,7 +144,7 @@ ql.ModifiedFollowing, True, dayCounter, - ql.QuoteHandle(ql.SimpleQuote(0.0)), + ql.makeQuoteHandle(0.0), ) for d in futures.keys() ] diff --git a/Python/pyproject.toml.in b/Python/pyproject.toml.in deleted file mode 100644 index 0c9bfa69..00000000 --- a/Python/pyproject.toml.in +++ /dev/null @@ -1,96 +0,0 @@ -############################################################################## -# Pypproject main file for QuantLib-Risks -# -# This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled -# for risk computation using automatic differentiation. It uses XAD, -# a fast and comprehensive C++ library for automatic differentiation. -# -# Copyright (C) 2010-2024 Xcelerit Computing Ltd. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -[tool.poetry] -name = "QuantLib-Risks" -version = "@PACKAGE_VERSION@" -description = "Fast risks calculations in QuantLib" -authors = [ - "Auto Differentiation Dev Team ", - "QuantLib Team " -] -readme = "README.md" -homepage = "https://auto-differentiation.github.io" -repository = "https://github.com/auto-differentiation/QuantLib-Risks-Py" -documentation = "https://auto-differentiation.github.io/quantlib-risks" -keywords = [ - "automatic-differentiation", - "derivatives", - "risk-management", - "quant-finance", - "greeks", - "risks" -] -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Financial and Insurance Industry", - "Intended Audience :: Science/Research", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", - "License :: Other/Proprietary License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Natural Language :: English", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Scientific/Engineering", - "Topic :: Software Development" -] -license = "AGPL-3.0-or-later" -packages = [ - { include = "QuantLib_Risks" } -] -exclude = [ - "QuantLib_Risks/CMakeFiles", - "QuantLib_Risks/*.cmake", - "QuantLib_Risks/*.cxx", -] - -[tool.poetry.urls] -download = "https://pypi.org/project/QuantLib-Risks/#files" -tracker = "https://github.com/auto-differentiation/QuantLib-Risks-Py/issues" - -[tool.poetry.build] -script = "build_extensions.py" -generate-setup-file = false - -[tool.poetry.dependencies] -python = ">=3.8.1,<4.0" -xad = ">=1.5.2" - - -[build-system] -requires = [ - "poetry-core>=1.0.0", - "setuptools>=42" -] -build-backend = "poetry.core.masonry.api" - diff --git a/Python/run_tests.sh b/Python/run_tests.sh index d66f60a5..83dcdb6e 100644 --- a/Python/run_tests.sh +++ b/Python/run_tests.sh @@ -28,7 +28,7 @@ set -e SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -python "${SCRIPT_DIR}/test/QuantLibTestSuite.py" +python -m pytest -p no:cacheprovider --import-mode=importlib "${SCRIPT_DIR}/test" cd "${SCRIPT_DIR}/examples" diff --git a/Python/test/QuantLibTestSuite.py b/Python/test/QuantLibTestSuite.py deleted file mode 100644 index b5f0f477..00000000 --- a/Python/test/QuantLibTestSuite.py +++ /dev/null @@ -1,48 +0,0 @@ -""" - Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl - Copyright (C) 2009 Joseph Malicki - Copyright (C) 2024 Xcelerit Computing Limited. - - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - - QuantLib is free software: you can redistribute it and/or modify it - under the terms of the QuantLib license. You should have received a - copy of the license along with this program; if not, please email - . The license is also available online at - . - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the license for more details. -""" - -import os -import sys -import unittest - -import QuantLib_Risks as ql - - -def test(): - print('testing QuantLib_Risks', ql.__version__) - sys.argv[1:1] = ['discover', '-s', os.path.dirname(__file__)] - unittest.main(module=None, verbosity=2) - - -if __name__ == '__main__': - test() diff --git a/Python/test/test_americanquantooption.py b/Python/test/test_americanquantooption.py index 930ae67d..7632f9aa 100644 --- a/Python/test/test_americanquantooption.py +++ b/Python/test/test_americanquantooption.py @@ -48,19 +48,13 @@ def setUp(self): self.divYieldTS = ql.FlatForward(self.today, 0.03, self.dc) - divDate = ql.DateVector() - divDate.push_back(self.today + ql.Period(6, ql.Months)) - - divAmount = ql.DoubleVector() - divAmount.push_back(8.0) + self.dividends = [ql.FixedDividend(8.0, self.today + ql.Period(6, ql.Months))] maturityDate = self.today + ql.Period(9, ql.Months) - self.option = ql.DividendVanillaOption( + self.option = ql.VanillaOption( ql.PlainVanillaPayoff(ql.Option.Call, 105), - ql.AmericanExercise(self.today, maturityDate), - divDate, - divAmount) + ql.AmericanExercise(self.today, maturityDate)) def tearDown(self): @@ -72,13 +66,13 @@ def testAmericanBSQuantoOption(self): volTS = ql.BlackConstantVol(self.today, ql.TARGET(), 0.3, self.dc) bsmProcess = ql.BlackScholesMertonProcess( - ql.QuoteHandle(ql.SimpleQuote(100)), + ql.makeQuoteHandle(100), ql.YieldTermStructureHandle(self.divYieldTS), ql.YieldTermStructureHandle(self.domesticTS), ql.BlackVolTermStructureHandle(volTS)) fdmBlackScholesEngine = ql.FdBlackScholesVanillaEngine( - bsmProcess, self.quantoHelper, 100, 400, 1) + bsmProcess, self.dividends, self.quantoHelper, 100, 400, 1) self.option.setPricingEngine(fdmBlackScholesEngine) @@ -96,11 +90,11 @@ def testAmericanHestonQuantoOption(self): ql.HestonProcess( ql.YieldTermStructureHandle(self.domesticTS), ql.YieldTermStructureHandle(self.divYieldTS), - ql.QuoteHandle(ql.SimpleQuote(100)), + ql.makeQuoteHandle(100), 0.09, 1.0, 0.09, 1e-4, 0.0)) fdmHestonVanillaEngine = ql.FdHestonVanillaEngine( - hestonModel, self.quantoHelper, 100, 400, 3, 1) + hestonModel, self.dividends, self.quantoHelper, 100, 400, 3, 1) self.option.setPricingEngine(fdmHestonVanillaEngine) diff --git a/Python/test/test_assetswap.py b/Python/test/test_assetswap.py deleted file mode 100644 index 8d73a96e..00000000 --- a/Python/test/test_assetswap.py +++ /dev/null @@ -1,5370 +0,0 @@ -""" - Copyright (C) 2011 Lluis Pujol Bajador - Copyright (C) 2024 Xcelerit Computing Limited. - - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - - QuantLib is free software: you can redistribute it and/or modify it - under the terms of the QuantLib license. You should have received a - copy of the license along with this program; if not, please email - . The license is also available online at - . - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the license for more details. -""" - -import QuantLib_Risks as ql -import unittest - - -class AssetSwapTest(unittest.TestCase): - def setUp(self): - # initial setup - self.termStructure = ql.RelinkableYieldTermStructureHandle() - self.swapSettlementDays = 2 - self.faceAmount = 100.0 - self.fixedConvention = ql.Unadjusted - self.compounding = ql.Continuous - self.fixedFrequency = ql.Annual - self.floatingFrequency = ql.Semiannual - self.iborIndex = ql.Euribor(ql.Period(self.floatingFrequency), self.termStructure) - self.calendar = self.iborIndex.fixingCalendar() - self.swapIndex = ql.SwapIndex( - "EuriborSwapIsdaFixA", - ql.Period(10, ql.Years), - self.swapSettlementDays, - self.iborIndex.currency(), - self.calendar, - ql.Period(self.fixedFrequency), - self.fixedConvention, - self.iborIndex.dayCounter(), - self.iborIndex, - ) - self.spread = 0.0 - self.nonnullspread = 0.003 - self.today = ql.Date(24, ql.April, 2007) - ql.Settings.instance().evaluationDate = self.today - self.termStructure.linkTo(ql.FlatForward(self.today, 0.05, ql.Actual365Fixed())) - self.yieldCurve = ql.FlatForward(self.today, 0.05, ql.Actual365Fixed()) - self.pricer = ql.BlackIborCouponPricer() - self.swaptionVolatilityStructure = ql.SwaptionVolatilityStructureHandle( - ql.ConstantSwaptionVolatility(self.today, ql.NullCalendar(), ql.Following, 0.2, ql.Actual365Fixed()) - ) - self.meanReversionQuote = ql.QuoteHandle(ql.SimpleQuote(0.01)) - self.cmspricer = ql.AnalyticHaganPricer( - self.swaptionVolatilityStructure, ql.GFunctionFactory.Standard, self.meanReversionQuote - ) - - def tearDown(self): - ql.Settings.instance().evaluationDate = ql.Date() - - def testConsistency(self): - """Testing consistency between fair price and fair spread...""" - bondCalendar = ql.TARGET() - settlementDays = 3 - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - bondSchedule = ql.Schedule( - ql.Date(4, ql.January, 2005), - ql.Date(4, ql.January, 2037), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - bond = ql.FixedRateBond( - settlementDays, - self.faceAmount, - bondSchedule, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - - payFixedRate = True - bondPrice = 95.0 - isPar = True - parAssetSwap = ql.AssetSwap( - payFixedRate, - bond, - bondPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - - swapEngine = ql.DiscountingSwapEngine( - self.termStructure, True, bond.settlementDate(), ql.Settings.instance().evaluationDate - ) - - parAssetSwap.setPricingEngine(swapEngine) - fairCleanPrice = parAssetSwap.fairCleanPrice() - fairSpread = parAssetSwap.fairSpread() - - tolerance = 1.0e-13 - - assetSwap2 = ql.AssetSwap( - payFixedRate, - bond, - fairCleanPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - - assetSwap2.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap2.NPV()) > tolerance, - "\npar asset swap fair clean price doesn't zero the NPV: " - + "\n clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(fairCleanPrice) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap2.fairCleanPrice() - fairCleanPrice) > tolerance, - "\npar asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(assetSwap2.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap2.fairSpread() - self.spread) > tolerance, - "\npar asset swap fair spread doesn't equal input spread " - + "at zero NPV: " - + "\n input spread: " - + str(self.spread) - + "\n fair spread: " - + str(assetSwap2.fairSpread()) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap3 = ql.AssetSwap( - payFixedRate, bond, bondPrice, self.iborIndex, fairSpread, ql.Schedule(), self.iborIndex.dayCounter(), isPar - ) - assetSwap3.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap3.NPV()) > tolerance, - "\npar asset swap fair spread doesn't zero the NPV: " - + "\n spread: " - + str(self.spread) - + "\n fair spread: " - + str(fairSpread) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap3.fairCleanPrice() - bondPrice) > tolerance, - "\npar asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(assetSwap3.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap3.fairSpread() - fairSpread) > tolerance, - "\npar asset swap fair spread doesn't equal input spread at" - + " zero NPV: " - + "\n input spread: " - + str(fairSpread) - + "\n fair spread: " - + str(assetSwap3.fairSpread()) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - ## let's change the npv date - swapEngine = ql.DiscountingSwapEngine(self.termStructure, True, bond.settlementDate(), bond.settlementDate()) - - parAssetSwap.setPricingEngine(swapEngine) - ## fair clean price and fair spread should not change - self.assertFalse( - abs(parAssetSwap.fairCleanPrice() - fairCleanPrice) > tolerance, - "\npar asset swap fair clean price changed with NpvDate:" - + "\n expected clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(parAssetSwap.fairCleanPrice()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(parAssetSwap.fairSpread() - fairSpread) > tolerance, - "\npar asset swap fair spread changed with NpvDate:" - + "\n expected spread: " - + str(fairSpread) - + "\n fair spread: " - + str(parAssetSwap.fairSpread()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap2 = ql.AssetSwap( - payFixedRate, - bond, - fairCleanPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - assetSwap2.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap2.NPV()) > tolerance, - "\npar asset swap fair clean price doesn't zero the NPV: " - + "\n clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(fairCleanPrice) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap2.fairCleanPrice() - fairCleanPrice) > tolerance, - "\npar asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(assetSwap2.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap2.fairSpread() - self.spread) > tolerance, - "\npar asset swap fair spread doesn't equal input spread at zero NPV: " - + "\n input spread: " - + str(self.spread) - + "\n fair spread: " - + str(assetSwap2.fairSpread()) - + "\n NPV: " - + str(assetSwap2.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap3 = ql.AssetSwap( - payFixedRate, bond, bondPrice, self.iborIndex, fairSpread, ql.Schedule(), self.iborIndex.dayCounter(), isPar - ) - assetSwap3.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap3.NPV()) > tolerance, - "\npar asset swap fair spread doesn't zero the NPV: " - + "\n spread: " - + str(self.spread) - + "\n fair spread: " - + str(fairSpread) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap3.fairCleanPrice() - bondPrice) > tolerance, - "\npar asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(assetSwap3.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap3.fairSpread() - fairSpread) > tolerance, - "\npar asset swap fair spread doesn't equal input spread at zero NPV: " - + "\n input spread: " - + str(fairSpread) - + "\n fair spread: " - + str(assetSwap3.fairSpread()) - + "\n NPV: " - + str(assetSwap3.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - ## now market asset swap - isPar = False - mktAssetSwap = ql.AssetSwap( - payFixedRate, - bond, - bondPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - - swapEngine = ql.DiscountingSwapEngine( - self.termStructure, True, bond.settlementDate(), ql.Settings.instance().evaluationDate - ) - - mktAssetSwap.setPricingEngine(swapEngine) - fairCleanPrice = mktAssetSwap.fairCleanPrice() - fairSpread = mktAssetSwap.fairSpread() - - assetSwap4 = ql.AssetSwap( - payFixedRate, - bond, - fairCleanPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - assetSwap4.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap4.NPV()) > tolerance, - "\nmarket asset swap fair clean price doesn't zero the NPV: " - + "\n clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(fairCleanPrice) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap4.fairCleanPrice() - fairCleanPrice) > tolerance, - "\nmarket asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(assetSwap4.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap4.fairSpread() - self.spread) > tolerance, - "\nmarket asset swap fair spread doesn't equal input spread" - + " at zero NPV: " - + "\n input spread: " - + str(self.spread) - + "\n fair spread: " - + str(assetSwap4.fairSpread()) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap5 = ql.AssetSwap( - payFixedRate, bond, bondPrice, self.iborIndex, fairSpread, ql.Schedule(), self.iborIndex.dayCounter(), isPar - ) - assetSwap5.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap5.NPV()) > tolerance, - "\nmarket asset swap fair spread doesn't zero the NPV: " - + "\n spread: " - + str(self.spread) - + "\n fair spread: " - + str(fairSpread) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap5.fairCleanPrice() - bondPrice) > tolerance, - "\nmarket asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(assetSwap5.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap5.fairSpread() - fairSpread) > tolerance, - "\nmarket asset swap fair spread doesn't equal input spread at zero NPV: " - + "\n input spread: " - + str(fairSpread) - + "\n fair spread: " - + str(assetSwap5.fairSpread()) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - ## let's change the npv date - swapEngine = ql.DiscountingSwapEngine(self.termStructure, True, bond.settlementDate(), bond.settlementDate()) - - mktAssetSwap.setPricingEngine(swapEngine) - ## fair clean price and fair spread should not change - self.assertFalse( - abs(mktAssetSwap.fairCleanPrice() - fairCleanPrice) > tolerance, - "\nmarket asset swap fair clean price changed with NpvDate:" - + "\n expected clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(mktAssetSwap.fairCleanPrice()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(mktAssetSwap.fairSpread() - fairSpread) > tolerance, - "\nmarket asset swap fair spread changed with NpvDate:" - + "\n expected spread: " - + str(fairSpread) - + "\n fair spread: " - + str(mktAssetSwap.fairSpread()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap4 = ql.AssetSwap( - payFixedRate, - bond, - fairCleanPrice, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - isPar, - ) - assetSwap4.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap4.NPV()) > tolerance, - "\nmarket asset swap fair clean price doesn't zero the NPV: " - + "\n clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(fairCleanPrice) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap4.fairCleanPrice() - fairCleanPrice) > tolerance, - "\nmarket asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(fairCleanPrice) - + "\n fair clean price: " - + str(assetSwap4.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap4.fairSpread() - self.spread) > tolerance, - "\nmarket asset swap fair spread doesn't equal input spread at zero NPV: " - + "\n input spread: " - + str(self.spread) - + "\n fair spread: " - + str(assetSwap4.fairSpread()) - + "\n NPV: " - + str(assetSwap4.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - assetSwap5 = ql.AssetSwap( - payFixedRate, bond, bondPrice, self.iborIndex, fairSpread, ql.Schedule(), self.iborIndex.dayCounter(), isPar - ) - assetSwap5.setPricingEngine(swapEngine) - self.assertFalse( - abs(assetSwap5.NPV()) > tolerance, - "\nmarket asset swap fair spread doesn't zero the NPV: " - + "\n spread: " - + str(self.spread) - + "\n fair spread: " - + str(fairSpread) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap5.fairCleanPrice() - bondPrice) > tolerance, - "\nmarket asset swap fair clean price doesn't equal input " - + "clean price at zero NPV: " - + "\n input clean price: " - + str(bondPrice) - + "\n fair clean price: " - + str(assetSwap5.fairCleanPrice()) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - self.assertFalse( - abs(assetSwap5.fairSpread() - fairSpread) > tolerance, - "\nmarket asset swap fair spread doesn't equal input spread at zero NPV: " - + "\n input spread: " - + str(fairSpread) - + "\n fair spread: " - + str(assetSwap5.fairSpread()) - + "\n NPV: " - + str(assetSwap5.NPV()) - + "\n tolerance: " - + str(tolerance), - ) - - def testImpliedValue(self): - """Testing implied bond value against asset-swap fair price with null spread...""" - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - payFixedRate = True - parAssetSwap = True - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - fixedBondSchedule1 = ql.Schedule( - ql.Date(4, ql.January, 2005), - ql.Date(4, ql.January, 2037), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond1 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule1, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - - bondEngine = ql.DiscountingBondEngine(self.termStructure) - swapEngine = ql.DiscountingSwapEngine(self.termStructure, False) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondPrice1 = fixedBond1.cleanPrice() - fixedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap1.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice1 = fixedBondAssetSwap1.fairCleanPrice() - tolerance = 1.0e-13 - - error1 = abs(fixedBondAssetSwapPrice1 - fixedBondPrice1) - - self.assertFalse( - error1 > tolerance, - "wrong zero spread asset swap price for fixed bond:" - + "\n bond's clean price: " - + str(fixedBondPrice1) - + "\n asset swap fair price: " - + str(fixedBondAssetSwapPrice1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - - fixedBondSchedule2 = ql.Schedule( - ql.Date(5, ql.February, 2005), - ql.Date(5, ql.February, 2019), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond2 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule2, - [0.05], - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - 100.0, - ql.Date(5, ql.February, 2005), - ) - - fixedBond2.setPricingEngine(bondEngine) - - fixedBondPrice2 = fixedBond2.cleanPrice() - fixedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap2.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice2 = fixedBondAssetSwap2.fairCleanPrice() - error2 = abs(fixedBondAssetSwapPrice2 - fixedBondPrice2) - - self.assertFalse( - error2 > tolerance, - "wrong zero spread asset swap price for fixed bond:" - + "\n bond's clean price: " - + str(fixedBondPrice2) - + "\n asset swap fair price: " - + str(fixedBondAssetSwapPrice2) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - - floatingBondSchedule1 = ql.Schedule( - ql.Date(29, ql.September, 2003), - ql.Date(29, ql.September, 2013), - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - - floatingBond1 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - fixingDays, - [1], - [0.0056], - [], - [], - inArrears, - 100.0, - ql.Date(29, ql.September, 2003), - ) - - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondPrice1 = floatingBond1.cleanPrice() - floatingBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap1.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice1 = floatingBondAssetSwap1.fairCleanPrice() - error3 = abs(floatingBondAssetSwapPrice1 - floatingBondPrice1) - - self.assertFalse( - error3 > tolerance, - "wrong zero spread asset swap price for floater:" - + "\n bond's clean price: " - + str(floatingBondPrice1) - + "\n asset swap fair price: " - + str(floatingBondAssetSwapPrice1) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - - floatingBondSchedule2 = ql.Schedule( - ql.Date(24, ql.September, 2004), - ql.Date(24, ql.September, 2018), - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBond2 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - fixingDays, - [1], - [0.0025], - [], - [], - inArrears, - 100.0, - ql.Date(24, ql.September, 2004), - ) - - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - currentCoupon = 0.04013 + 0.0025 - floatingCurrentCoupon = floatingBond2.nextCouponRate() - error4 = abs(floatingCurrentCoupon - currentCoupon) - self.assertFalse( - error4 > tolerance, - "wrong current coupon is returned for floater bond:" - + "\n bond's calculated current coupon: " - + str(currentCoupon) - + "\n current coupon asked to the bond: " - + str(floatingCurrentCoupon) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - floatingBondPrice2 = floatingBond2.cleanPrice() - floatingBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap2.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice2 = floatingBondAssetSwap2.fairCleanPrice() - error5 = abs(floatingBondAssetSwapPrice2 - floatingBondPrice2) - - self.assertFalse( - error5 > tolerance, - "wrong zero spread asset swap price for floater:" - + "\n bond's clean price: " - + str(floatingBondPrice2) - + "\n asset swap fair price: " - + str(floatingBondAssetSwapPrice2) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondSchedule1 = ql.Schedule( - ql.Date(22, ql.August, 2005), - ql.Date(22, ql.August, 2020), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond1 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [1.0], - [0.0], - [0.055], - [0.025], - inArrears, - 100.0, - ql.Date(22, ql.August, 2005), - ) - - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondPrice1 = cmsBond1.cleanPrice() - cmsBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap1.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice1 = cmsBondAssetSwap1.fairCleanPrice() - error6 = abs(cmsBondAssetSwapPrice1 - cmsBondPrice1) - - self.assertFalse( - error6 > tolerance, - "wrong zero spread asset swap price for cms bond:" - + "\n bond's clean price: " - + str(cmsBondPrice1) - + "\n asset swap fair price: " - + str(cmsBondAssetSwapPrice1) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - - cmsBondSchedule2 = ql.Schedule( - ql.Date(6, ql.May, 2005), - ql.Date(6, ql.May, 2015), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond2 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [0.84], - [0.0], - [], - [], - inArrears, - 100.0, - ql.Date(6, ql.May, 2005), - ) - - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondPrice2 = cmsBond2.cleanPrice() - cmsBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap2.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice2 = cmsBondAssetSwap2.fairCleanPrice() - error7 = abs(cmsBondAssetSwapPrice2 - cmsBondPrice2) - - self.assertFalse( - error7 > tolerance, - "wrong zero spread asset swap price for cms bond:" - + "\n bond's clean price: " - + str(cmsBondPrice2) - + "\n asset swap fair price: " - + str(cmsBondAssetSwapPrice2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBond1 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(20, ql.December, 2015), - ql.Following, - 100.0, - ql.Date(19, ql.December, 1985), - ) - - zeroCpnBond1.setPricingEngine(bondEngine) - - zeroCpnBondPrice1 = zeroCpnBond1.cleanPrice() - zeroCpnAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice1 = zeroCpnAssetSwap1.fairCleanPrice() - error8 = abs(cmsBondAssetSwapPrice1 - cmsBondPrice1) - - self.assertFalse( - error8 > tolerance, - "wrong zero spread asset swap price for zero cpn bond:" - + "\n bond's clean price: " - + str(zeroCpnBondPrice1) - + "\n asset swap fair price: " - + str(zeroCpnBondAssetSwapPrice1) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - - zeroCpnBond2 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(17, ql.February, 2028), - ql.Following, - 100.0, - ql.Date(17, ql.February, 1998), - ) - - zeroCpnBond2.setPricingEngine(bondEngine) - - zeroCpnBondPrice2 = zeroCpnBond2.cleanPrice() - zeroCpnAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice2 = zeroCpnAssetSwap2.fairCleanPrice() - error9 = abs(cmsBondAssetSwapPrice2 - cmsBondPrice2) - - self.assertFalse( - error9 > tolerance, - "wrong zero spread asset swap price for zero cpn bond:" - + "\n bond's clean price: " - + str(zeroCpnBondPrice2) - + "\n asset swap fair price: " - + str(zeroCpnBondAssetSwapPrice2) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - def testMarketASWSpread(self): - """Testing relationship between market asset swap and par asset swap...""" - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - payFixedRate = True - parAssetSwap = True - mktAssetSwap = False - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - fixedBondSchedule1 = ql.Schedule( - ql.Date(4, ql.January, 2005), - ql.Date(4, ql.January, 2037), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond1 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule1, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - - bondEngine = ql.DiscountingBondEngine(self.termStructure) - swapEngine = ql.DiscountingSwapEngine(self.termStructure, False) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondMktPrice1 = 89.22 ## market price observed on 7th June 2007 - fixedBondMktFullPrice1 = fixedBondMktPrice1 + fixedBond1.accruedAmount() - fixedBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondParAssetSwap1.setPricingEngine(swapEngine) - fixedBondParAssetSwapSpread1 = fixedBondParAssetSwap1.fairSpread() - fixedBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - fixedBondMktAssetSwap1.setPricingEngine(swapEngine) - fixedBondMktAssetSwapSpread1 = fixedBondMktAssetSwap1.fairSpread() - - tolerance = 1.0e-13 - error1 = abs(fixedBondMktAssetSwapSpread1 - 100 * fixedBondParAssetSwapSpread1 / fixedBondMktFullPrice1) - - self.assertFalse( - error1 > tolerance, - "wrong asset swap spreads for fixed bond:" - + "\n market ASW spread: " - + str(fixedBondMktAssetSwapSpread1) - + "\n par ASW spread: " - + str(fixedBondParAssetSwapSpread1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - - fixedBondSchedule2 = ql.Schedule( - ql.Date(5, ql.February, 2005), - ql.Date(5, ql.February, 2019), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond2 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule2, - [0.05], - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - 100.0, - ql.Date(5, ql.February, 2005), - ) - - fixedBond2.setPricingEngine(bondEngine) - - fixedBondMktPrice2 = 99.98 ## market price observed on 7th June 2007 - fixedBondMktFullPrice2 = fixedBondMktPrice2 + fixedBond2.accruedAmount() - fixedBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondParAssetSwap2.setPricingEngine(swapEngine) - fixedBondParAssetSwapSpread2 = fixedBondParAssetSwap2.fairSpread() - fixedBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - fixedBondMktAssetSwap2.setPricingEngine(swapEngine) - fixedBondMktAssetSwapSpread2 = fixedBondMktAssetSwap2.fairSpread() - error2 = abs(fixedBondMktAssetSwapSpread2 - 100 * fixedBondParAssetSwapSpread2 / fixedBondMktFullPrice2) - - self.assertFalse( - error2 > tolerance, - "wrong asset swap spreads for fixed bond:" - + "\n market ASW spread: " - + str(fixedBondMktAssetSwapSpread2) - + "\n par ASW spread: " - + str(fixedBondParAssetSwapSpread2) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - - floatingBondSchedule1 = ql.Schedule( - ql.Date(29, ql.September, 2003), - ql.Date(29, ql.September, 2013), - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - - floatingBond1 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - fixingDays, - [1], - [0.0056], - [], - [], - inArrears, - 100.0, - ql.Date(29, ql.September, 2003), - ) - - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - ## market price observed on 7th June 2007 - floatingBondMktPrice1 = 101.64 - floatingBondMktFullPrice1 = floatingBondMktPrice1 + floatingBond1.accruedAmount() - floatingBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondParAssetSwap1.setPricingEngine(swapEngine) - floatingBondParAssetSwapSpread1 = floatingBondParAssetSwap1.fairSpread() - floatingBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - floatingBondMktAssetSwap1.setPricingEngine(swapEngine) - floatingBondMktAssetSwapSpread1 = floatingBondMktAssetSwap1.fairSpread() - error3 = abs( - floatingBondMktAssetSwapSpread1 - 100 * floatingBondParAssetSwapSpread1 / floatingBondMktFullPrice1 - ) - - self.assertFalse( - error3 > tolerance, - "wrong asset swap spreads for floating bond:" - + "\n market ASW spread: " - + str(floatingBondMktAssetSwapSpread1) - + "\n par ASW spread: " - + str(floatingBondParAssetSwapSpread1) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - - floatingBondSchedule2 = ql.Schedule( - ql.Date(24, ql.September, 2004), - ql.Date(24, ql.September, 2018), - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBond2 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - fixingDays, - [1], - [0.0025], - [], - [], - inArrears, - 100.0, - ql.Date(24, ql.September, 2004), - ) - - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - ## market price observed on 7th June 2007 - floatingBondMktPrice2 = 101.248 - floatingBondMktFullPrice2 = floatingBondMktPrice2 + floatingBond2.accruedAmount() - floatingBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondParAssetSwap2.setPricingEngine(swapEngine) - floatingBondParAssetSwapSpread2 = floatingBondParAssetSwap2.fairSpread() - floatingBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - floatingBondMktAssetSwap2.setPricingEngine(swapEngine) - floatingBondMktAssetSwapSpread2 = floatingBondMktAssetSwap2.fairSpread() - error4 = abs( - floatingBondMktAssetSwapSpread2 - 100 * floatingBondParAssetSwapSpread2 / floatingBondMktFullPrice2 - ) - - self.assertFalse( - error4 > tolerance, - "wrong asset swap spreads for floating bond:" - + "\n market ASW spread: " - + str(floatingBondMktAssetSwapSpread2) - + "\n par ASW spread: " - + str(floatingBondParAssetSwapSpread2) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondSchedule1 = ql.Schedule( - ql.Date(22, ql.August, 2005), - ql.Date(22, ql.August, 2020), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond1 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [1, 1.0], - [0.0], - [0.055], - [0.025], - inArrears, - 100.0, - ql.Date(22, ql.August, 2005), - ) - - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondMktPrice1 = 88.45 ## market price observed on 7th June 2007 - cmsBondMktFullPrice1 = cmsBondMktPrice1 + cmsBond1.accruedAmount() - cmsBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondParAssetSwap1.setPricingEngine(swapEngine) - cmsBondParAssetSwapSpread1 = cmsBondParAssetSwap1.fairSpread() - cmsBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - cmsBondMktAssetSwap1.setPricingEngine(swapEngine) - cmsBondMktAssetSwapSpread1 = cmsBondMktAssetSwap1.fairSpread() - error5 = abs(cmsBondMktAssetSwapSpread1 - 100 * cmsBondParAssetSwapSpread1 / cmsBondMktFullPrice1) - - self.assertFalse( - error5 > tolerance, - "wrong asset swap spreads for cms bond:" - + "\n market ASW spread: " - + str(cmsBondMktAssetSwapSpread1) - + "\n par ASW spread: " - + str(cmsBondParAssetSwapSpread1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - - cmsBondSchedule2 = ql.Schedule( - ql.Date(6, ql.May, 2005), - ql.Date(6, ql.May, 2015), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond2 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [0.84], - [0.0], - [], - [], - inArrears, - 100.0, - ql.Date(6, ql.May, 2005), - ) - - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondMktPrice2 = 94.08 ## market price observed on 7th June 2007 - cmsBondMktFullPrice2 = cmsBondMktPrice2 + cmsBond2.accruedAmount() - cmsBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondParAssetSwap2.setPricingEngine(swapEngine) - cmsBondParAssetSwapSpread2 = cmsBondParAssetSwap2.fairSpread() - cmsBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - cmsBondMktAssetSwap2.setPricingEngine(swapEngine) - cmsBondMktAssetSwapSpread2 = cmsBondMktAssetSwap2.fairSpread() - error6 = abs(cmsBondMktAssetSwapSpread2 - 100 * cmsBondParAssetSwapSpread2 / cmsBondMktFullPrice2) - - self.assertFalse( - error6 > tolerance, - "wrong asset swap spreads for cms bond:" - + "\n market ASW spread: " - + str(cmsBondMktAssetSwapSpread2) - + "\n par ASW spread: " - + str(cmsBondParAssetSwapSpread2) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBond1 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(20, ql.December, 2015), - ql.Following, - 100.0, - ql.Date(19, ql.December, 1985), - ) - - zeroCpnBond1.setPricingEngine(bondEngine) - - ## market price observed on 12th June 2007 - zeroCpnBondMktPrice1 = 70.436 - zeroCpnBondMktFullPrice1 = zeroCpnBondMktPrice1 + zeroCpnBond1.accruedAmount() - zeroCpnBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondParAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondParAssetSwapSpread1 = zeroCpnBondParAssetSwap1.fairSpread() - zeroCpnBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - zeroCpnBondMktAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondMktAssetSwapSpread1 = zeroCpnBondMktAssetSwap1.fairSpread() - error7 = abs(zeroCpnBondMktAssetSwapSpread1 - 100 * zeroCpnBondParAssetSwapSpread1 / zeroCpnBondMktFullPrice1) - - self.assertFalse( - error7 > tolerance, - "wrong asset swap spreads for zero cpn bond:" - + "\n market ASW spread: " - + str(zeroCpnBondMktAssetSwapSpread1) - + "\n par ASW spread: " - + str(zeroCpnBondParAssetSwapSpread1) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - - zeroCpnBond2 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(17, ql.February, 2028), - ql.Following, - 100.0, - ql.Date(17, ql.February, 1998), - ) - - zeroCpnBond2.setPricingEngine(bondEngine) - - ## zeroCpnBondPrice2 = zeroCpnBond2.cleanPrice() - - ## market price observed on 12th June 2007 - zeroCpnBondMktPrice2 = 35.160 - zeroCpnBondMktFullPrice2 = zeroCpnBondMktPrice2 + zeroCpnBond2.accruedAmount() - zeroCpnBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondParAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondParAssetSwapSpread2 = zeroCpnBondParAssetSwap2.fairSpread() - zeroCpnBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - zeroCpnBondMktAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondMktAssetSwapSpread2 = zeroCpnBondMktAssetSwap2.fairSpread() - error8 = abs(zeroCpnBondMktAssetSwapSpread2 - 100 * zeroCpnBondParAssetSwapSpread2 / zeroCpnBondMktFullPrice2) - - self.assertFalse( - error8 > tolerance, - "wrong asset swap spreads for zero cpn bond:" - + "\n market ASW spread: " - + str(zeroCpnBondMktAssetSwapSpread2) - + "\n par ASW spread: " - + str(zeroCpnBondParAssetSwapSpread2) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - def testZSpread(self): - """Testing clean and dirty price with null Z-spread against theoretical prices...""" - - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - inArrears = False - - ## Fixed bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - fixedBondSchedule1 = ql.Schedule( - ql.Date(4, ql.January, 2005), - ql.Date(4, ql.January, 2037), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond1 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule1, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - - bondEngine = ql.DiscountingBondEngine(self.termStructure) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondImpliedValue1 = fixedBond1.cleanPrice() - fixedBondSettlementDate1 = fixedBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YC... - fixedBondCleanPrice1 = ql.cleanPriceFromZSpread( - fixedBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - fixedBondSettlementDate1, - ) - - tolerance = 1.0e-13 - error1 = abs(fixedBondImpliedValue1 - fixedBondCleanPrice1) - self.assertFalse( - error1 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondImpliedValue1) - + "\n par asset swap spread: " - + str(fixedBondCleanPrice1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - - fixedBondSchedule2 = ql.Schedule( - ql.Date(5, ql.February, 2005), - ql.Date(5, ql.February, 2019), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBond2 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule2, - [0.05], - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - 100.0, - ql.Date(5, ql.February, 2005), - ) - - fixedBond2.setPricingEngine(bondEngine) - - fixedBondImpliedValue2 = fixedBond2.cleanPrice() - fixedBondSettlementDate2 = fixedBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - fixedBondCleanPrice2 = ql.cleanPriceFromZSpread( - fixedBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - fixedBondSettlementDate2, - ) - error3 = abs(fixedBondImpliedValue2 - fixedBondCleanPrice2) - self.assertFalse( - error3 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondImpliedValue2) - + "\n par asset swap spread: " - + str(fixedBondCleanPrice2) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - - floatingBondSchedule1 = ql.Schedule( - ql.Date(29, ql.September, 2003), - ql.Date(29, ql.September, 2013), - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - - floatingBond1 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - fixingDays, - [1], - [0.0056], - [], - [], - inArrears, - 100.0, - ql.Date(29, ql.September, 2003), - ) - - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondImpliedValue1 = floatingBond1.cleanPrice() - floatingBondSettlementDate1 = floatingBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - floatingBondCleanPrice1 = ql.cleanPriceFromZSpread( - floatingBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Semiannual, - floatingBondSettlementDate1, - ) - error5 = abs(floatingBondImpliedValue1 - floatingBondCleanPrice1) - self.assertFalse( - error5 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(floatingBondImpliedValue1) - + "\n par asset swap spread: " - + str(floatingBondCleanPrice1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - - floatingBondSchedule2 = ql.Schedule( - ql.Date(24, ql.September, 2004), - ql.Date(24, ql.September, 2018), - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBond2 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - fixingDays, - [1], - [0.0025], - [], - [], - inArrears, - 100.0, - ql.Date(24, ql.September, 2004), - ) - - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - floatingBondImpliedValue2 = floatingBond2.cleanPrice() - floatingBondSettlementDate2 = floatingBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - floatingBondCleanPrice2 = ql.cleanPriceFromZSpread( - floatingBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Semiannual, - floatingBondSettlementDate2, - ) - error7 = abs(floatingBondImpliedValue2 - floatingBondCleanPrice2) - self.assertFalse( - error7 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(floatingBondImpliedValue2) - + "\n par asset swap spread: " - + str(floatingBondCleanPrice2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - #### CMS bond (Isin: XS0228052402 CRDIT 0 8/22/20) - #### maturity doesn't occur on a business day - - cmsBondSchedule1 = ql.Schedule( - ql.Date(22, ql.August, 2005), - ql.Date(22, ql.August, 2020), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond1 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [1.0], - [0.0], - [0.055], - [0.025], - inArrears, - 100.0, - ql.Date(22, ql.August, 2005), - ) - - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondImpliedValue1 = cmsBond1.cleanPrice() - cmsBondSettlementDate1 = cmsBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - cmsBondCleanPrice1 = ql.cleanPriceFromZSpread( - cmsBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - cmsBondSettlementDate1, - ) - error9 = abs(cmsBondImpliedValue1 - cmsBondCleanPrice1) - self.assertFalse( - error9 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(cmsBondImpliedValue1) - + "\n par asset swap spread: " - + str(cmsBondCleanPrice1) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - - cmsBondSchedule2 = ql.Schedule( - ql.Date(6, ql.May, 2005), - ql.Date(6, ql.May, 2015), - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBond2 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [0.84], - [0.0], - [], - [], - inArrears, - 100.0, - ql.Date(6, ql.May, 2005), - ) - - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondImpliedValue2 = cmsBond2.cleanPrice() - cmsBondSettlementDate2 = cmsBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - cmsBondCleanPrice2 = ql.cleanPriceFromZSpread( - cmsBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - cmsBondSettlementDate2, - ) - error11 = abs(cmsBondImpliedValue2 - cmsBondCleanPrice2) - self.assertFalse( - error11 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(cmsBondImpliedValue2) - + "\n par asset swap spread: " - + str(cmsBondCleanPrice2) - + "\n error: " - + str(error11) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero-Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBond1 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(20, ql.December, 2015), - ql.Following, - 100.0, - ql.Date(19, ql.December, 1985), - ) - - zeroCpnBond1.setPricingEngine(bondEngine) - - zeroCpnBondImpliedValue1 = zeroCpnBond1.cleanPrice() - zeroCpnBondSettlementDate1 = zeroCpnBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - zeroCpnBondCleanPrice1 = ql.cleanPriceFromZSpread( - zeroCpnBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - zeroCpnBondSettlementDate1, - ) - error13 = abs(zeroCpnBondImpliedValue1 - zeroCpnBondCleanPrice1) - self.assertFalse( - error13 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n zero cpn implied value: " - + str(zeroCpnBondImpliedValue1) - + "\n zero cpn price: " - + str(zeroCpnBondCleanPrice1) - + "\n error: " - + str(error13) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity doesn't occur on a business day - - zeroCpnBond2 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(17, ql.February, 2028), - ql.Following, - 100.0, - ql.Date(17, ql.February, 1998), - ) - - zeroCpnBond2.setPricingEngine(bondEngine) - - zeroCpnBondImpliedValue2 = zeroCpnBond2.cleanPrice() - zeroCpnBondSettlementDate2 = zeroCpnBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - zeroCpnBondCleanPrice2 = ql.cleanPriceFromZSpread( - zeroCpnBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - zeroCpnBondSettlementDate2, - ) - error15 = abs(zeroCpnBondImpliedValue2 - zeroCpnBondCleanPrice2) - self.assertFalse( - error15 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n zero cpn implied value: " - + str(zeroCpnBondImpliedValue2) - + "\n zero cpn price: " - + str(zeroCpnBondCleanPrice2) - + "\n error: " - + str(error15) - + "\n tolerance: " - + str(tolerance), - ) - - def testGenericBondImplied(self): - """Testing implied generic-bond value against asset-swap fair price with null spread...""" - - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - payFixedRate = True - parAssetSwap = True - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - fixedBondStartDate1 = ql.Date(4, ql.January, 2005) - fixedBondMaturityDate1 = ql.Date(4, ql.January, 2037) - fixedBondSchedule1 = ql.Schedule( - fixedBondStartDate1, - fixedBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg1 = list( - ql.FixedRateLeg(fixedBondSchedule1, ql.ActualActual(ql.ActualActual.ISDA), [self.faceAmount], [0.04]) - ) - fixedbondRedemption1 = bondCalendar.adjust(fixedBondMaturityDate1, ql.Following) - fixedBondLeg1.append(ql.SimpleCashFlow(100.0, fixedbondRedemption1)) - fixedBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - fixedBondMaturityDate1, - fixedBondStartDate1, - tuple(fixedBondLeg1), - ) - bondEngine = ql.DiscountingBondEngine(self.termStructure) - swapEngine = ql.DiscountingSwapEngine(self.termStructure, True) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondPrice1 = fixedBond1.cleanPrice() - fixedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap1.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice1 = fixedBondAssetSwap1.fairCleanPrice() - tolerance = 1.0e-13 - error1 = abs(fixedBondAssetSwapPrice1 - fixedBondPrice1) - - self.assertFalse( - error1 > tolerance, - "wrong zero spread asset swap price for fixed bond:" - + "\n bond's clean price: " - + str(fixedBondPrice1) - + "\n asset swap fair price: " - + str(fixedBondAssetSwapPrice1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - fixedBondStartDate2 = ql.Date(5, ql.February, 2005) - fixedBondMaturityDate2 = ql.Date(5, ql.February, 2019) - fixedBondSchedule2 = ql.Schedule( - fixedBondStartDate2, - fixedBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg2 = list( - ql.FixedRateLeg(fixedBondSchedule2, ql.Thirty360(ql.Thirty360.BondBasis), [self.faceAmount], [0.05]) - ) - fixedbondRedemption2 = bondCalendar.adjust(fixedBondMaturityDate2, ql.Following) - fixedBondLeg2.append(ql.SimpleCashFlow(100.0, fixedbondRedemption2)) - fixedBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - fixedBondMaturityDate2, - fixedBondStartDate2, - tuple(fixedBondLeg2), - ) - fixedBond2.setPricingEngine(bondEngine) - - fixedBondPrice2 = fixedBond2.cleanPrice() - fixedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap2.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice2 = fixedBondAssetSwap2.fairCleanPrice() - error2 = abs(fixedBondAssetSwapPrice2 - fixedBondPrice2) - - self.assertFalse( - error2 > tolerance, - "wrong zero spread asset swap price for fixed bond:" - + "\n bond's clean price: " - + str(fixedBondPrice2) - + "\n asset swap fair price: " - + str(fixedBondAssetSwapPrice2) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - floatingBondStartDate1 = ql.Date(29, ql.September, 2003) - floatingBondMaturityDate1 = ql.Date(29, ql.September, 2013) - floatingBondSchedule1 = ql.Schedule( - floatingBondStartDate1, - floatingBondMaturityDate1, - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg1 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0056], - [], - [], - inArrears, - ) - ) - floatingbondRedemption1 = bondCalendar.adjust(floatingBondMaturityDate1, ql.Following) - floatingBondLeg1.append(ql.SimpleCashFlow(100.0, floatingbondRedemption1)) - floatingBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate1, - floatingBondStartDate1, - tuple(floatingBondLeg1), - ) - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondPrice1 = floatingBond1.cleanPrice() - floatingBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap1.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice1 = floatingBondAssetSwap1.fairCleanPrice() - error3 = abs(floatingBondAssetSwapPrice1 - floatingBondPrice1) - - self.assertFalse( - error3 > tolerance, - "wrong zero spread asset swap price for floater:" - + "\n bond's clean price: " - + str(floatingBondPrice1) - + "\n asset swap fair price: " - + str(floatingBondAssetSwapPrice1) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - floatingBondStartDate2 = ql.Date(24, ql.September, 2004) - floatingBondMaturityDate2 = ql.Date(24, ql.September, 2018) - floatingBondSchedule2 = ql.Schedule( - floatingBondStartDate2, - floatingBondMaturityDate2, - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg2 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0025], - [], - [], - inArrears, - ) - ) - floatingbondRedemption2 = bondCalendar.adjust(floatingBondMaturityDate2, ql.ModifiedFollowing) - floatingBondLeg2.append(ql.SimpleCashFlow(100.0, floatingbondRedemption2)) - floatingBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate2, - floatingBondStartDate2, - tuple(floatingBondLeg2), - ) - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - currentCoupon = 0.04013 + 0.0025 - floatingCurrentCoupon = floatingBond2.nextCouponRate() - error4 = abs(floatingCurrentCoupon - currentCoupon) - self.assertFalse( - error4 > tolerance, - "wrong current coupon is returned for floater bond:" - + "\n bond's calculated current coupon: " - + str(currentCoupon) - + "\n current coupon asked to the bond: " - + str(floatingCurrentCoupon) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - floatingBondPrice2 = floatingBond2.cleanPrice() - floatingBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap2.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice2 = floatingBondAssetSwap2.fairCleanPrice() - error5 = abs(floatingBondAssetSwapPrice2 - floatingBondPrice2) - - self.assertFalse( - error5 > tolerance, - "wrong zero spread asset swap price for floater:" - + "\n bond's clean price: " - + str(floatingBondPrice2) - + "\n asset swap fair price: " - + str(floatingBondAssetSwapPrice2) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondStartDate1 = ql.Date(22, ql.August, 2005) - cmsBondMaturityDate1 = ql.Date(22, ql.August, 2020) - cmsBondSchedule1 = ql.Schedule( - cmsBondStartDate1, - cmsBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg1 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [], - [0.055], - [0.025], - [], - inArrears, - ) - ) - cmsbondRedemption1 = bondCalendar.adjust(cmsBondMaturityDate1, ql.Following) - cmsBondLeg1.append(ql.SimpleCashFlow(100.0, cmsbondRedemption1)) - cmsBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate1, cmsBondStartDate1, tuple(cmsBondLeg1) - ) - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondPrice1 = cmsBond1.cleanPrice() - cmsBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap1.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice1 = cmsBondAssetSwap1.fairCleanPrice() - error6 = abs(cmsBondAssetSwapPrice1 - cmsBondPrice1) - - self.assertFalse( - error6 > tolerance, - "wrong zero spread asset swap price for cms bond:" - + "\n bond's clean price: " - + str(cmsBondPrice1) - + "\n asset swap fair price: " - + str(cmsBondAssetSwapPrice1) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - cmsBondStartDate2 = ql.Date(6, ql.May, 2005) - cmsBondMaturityDate2 = ql.Date(6, ql.May, 2015) - cmsBondSchedule2 = ql.Schedule( - cmsBondStartDate2, - cmsBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg2 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [0.84], - [], - [], - [], - inArrears, - ) - ) - cmsbondRedemption2 = bondCalendar.adjust(cmsBondMaturityDate2, ql.Following) - cmsBondLeg2.append(ql.SimpleCashFlow(100.0, cmsbondRedemption2)) - cmsBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate2, cmsBondStartDate2, tuple(cmsBondLeg2) - ) - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondPrice2 = cmsBond2.cleanPrice() - cmsBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap2.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice2 = cmsBondAssetSwap2.fairCleanPrice() - error7 = abs(cmsBondAssetSwapPrice2 - cmsBondPrice2) - - self.assertFalse( - error7 > tolerance, - "wrong zero spread asset swap price for cms bond:" - + "\n bond's clean price: " - + str(cmsBondPrice2) - + "\n asset swap fair price: " - + str(cmsBondAssetSwapPrice2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBondStartDate1 = ql.Date(19, ql.December, 1985) - zeroCpnBondMaturityDate1 = ql.Date(20, ql.December, 2015) - zeroCpnBondRedemption1 = bondCalendar.adjust(zeroCpnBondMaturityDate1, ql.Following) - zeroCpnBondLeg1 = ql.Leg([ql.SimpleCashFlow(100.0, zeroCpnBondRedemption1)]) - zeroCpnBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate1, - zeroCpnBondStartDate1, - zeroCpnBondLeg1, - ) - zeroCpnBond1.setPricingEngine(bondEngine) - - zeroCpnBondPrice1 = zeroCpnBond1.cleanPrice() - zeroCpnAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice1 = zeroCpnAssetSwap1.fairCleanPrice() - error8 = abs(zeroCpnBondAssetSwapPrice1 - zeroCpnBondPrice1) - - self.assertFalse( - error8 > tolerance, - "wrong zero spread asset swap price for zero cpn bond:" - + "\n bond's clean price: " - + str(zeroCpnBondPrice1) - + "\n asset swap fair price: " - + str(zeroCpnBondAssetSwapPrice1) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - zeroCpnBondStartDate2 = ql.Date(17, ql.February, 1998) - zeroCpnBondMaturityDate2 = ql.Date(17, ql.February, 2028) - zerocpbondRedemption2 = bondCalendar.adjust(zeroCpnBondMaturityDate2, ql.Following) - zeroCpnBondLeg2 = ql.Leg([ql.SimpleCashFlow(100.0, zerocpbondRedemption2)]) - zeroCpnBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate2, - zeroCpnBondStartDate2, - zeroCpnBondLeg2, - ) - zeroCpnBond2.setPricingEngine(bondEngine) - - zeroCpnBondPrice2 = zeroCpnBond2.cleanPrice() - zeroCpnAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice2 = zeroCpnAssetSwap2.fairCleanPrice() - error9 = abs(cmsBondAssetSwapPrice2 - cmsBondPrice2) - - self.assertFalse( - error9 > tolerance, - "wrong zero spread asset swap price for zero cpn bond:" - + "\n bond's clean price: " - + str(zeroCpnBondPrice2) - + "\n asset swap fair price: " - + str(zeroCpnBondAssetSwapPrice2) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - def testMASWWithGenericBond(self): - """Testing market asset swap against par asset swap with generic bond...""" - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - payFixedRate = True - parAssetSwap = True - mktAssetSwap = False - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - fixedBondStartDate1 = ql.Date(4, ql.January, 2005) - fixedBondMaturityDate1 = ql.Date(4, ql.January, 2037) - fixedBondSchedule1 = ql.Schedule( - fixedBondStartDate1, - fixedBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg1 = list( - ql.FixedRateLeg(fixedBondSchedule1, ql.ActualActual(ql.ActualActual.ISDA), [self.faceAmount], [0.04]) - ) - fixedbondRedemption1 = bondCalendar.adjust(fixedBondMaturityDate1, ql.Following) - fixedBondLeg1.append(ql.SimpleCashFlow(100.0, fixedbondRedemption1)) - fixedBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate1, fixedBondStartDate1, fixedBondLeg1 - ) - bondEngine = ql.DiscountingBondEngine(self.termStructure) - swapEngine = ql.DiscountingSwapEngine(self.termStructure, False) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondMktPrice1 = 89.22 ## market price observed on 7th June 2007 - fixedBondMktFullPrice1 = fixedBondMktPrice1 + fixedBond1.accruedAmount() - fixedBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondParAssetSwap1.setPricingEngine(swapEngine) - fixedBondParAssetSwapSpread1 = fixedBondParAssetSwap1.fairSpread() - fixedBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - fixedBondMktAssetSwap1.setPricingEngine(swapEngine) - fixedBondMktAssetSwapSpread1 = fixedBondMktAssetSwap1.fairSpread() - - tolerance = 1.0e-13 - error1 = abs(fixedBondMktAssetSwapSpread1 - 100 * fixedBondParAssetSwapSpread1 / fixedBondMktFullPrice1) - - self.assertFalse( - error1 > tolerance, - "wrong asset swap spreads for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondMktAssetSwapSpread1) - + "\n par asset swap spread: " - + str(fixedBondParAssetSwapSpread1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - - fixedBondStartDate2 = ql.Date(5, ql.February, 2005) - fixedBondMaturityDate2 = ql.Date(5, ql.February, 2019) - fixedBondSchedule2 = ql.Schedule( - fixedBondStartDate2, - fixedBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg2 = list( - ql.FixedRateLeg(fixedBondSchedule2, ql.Thirty360(ql.Thirty360.BondBasis), [self.faceAmount], [0.05]) - ) - fixedbondRedemption2 = bondCalendar.adjust(fixedBondMaturityDate2, ql.Following) - fixedBondLeg2.append(ql.SimpleCashFlow(100.0, fixedbondRedemption2)) - fixedBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate2, fixedBondStartDate2, fixedBondLeg2 - ) - fixedBond2.setPricingEngine(bondEngine) - - fixedBondMktPrice2 = 99.98 ## market price observed on 7th June 2007 - fixedBondMktFullPrice2 = fixedBondMktPrice2 + fixedBond2.accruedAmount() - fixedBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondParAssetSwap2.setPricingEngine(swapEngine) - fixedBondParAssetSwapSpread2 = fixedBondParAssetSwap2.fairSpread() - fixedBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - fixedBondMktAssetSwap2.setPricingEngine(swapEngine) - fixedBondMktAssetSwapSpread2 = fixedBondMktAssetSwap2.fairSpread() - error2 = abs(fixedBondMktAssetSwapSpread2 - 100 * fixedBondParAssetSwapSpread2 / fixedBondMktFullPrice2) - - self.assertFalse( - error2 > tolerance, - "wrong asset swap spreads for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondMktAssetSwapSpread2) - + "\n par asset swap spread: " - + str(fixedBondParAssetSwapSpread2) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - - floatingBondStartDate1 = ql.Date(29, ql.September, 2003) - floatingBondMaturityDate1 = ql.Date(29, ql.September, 2013) - floatingBondSchedule1 = ql.Schedule( - floatingBondStartDate1, - floatingBondMaturityDate1, - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg1 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - [fixingDays], - [], - [0.0056], - [], - [], - inArrears, - ) - ) - floatingbondRedemption1 = bondCalendar.adjust(floatingBondMaturityDate1, ql.Following) - floatingBondLeg1.append(ql.SimpleCashFlow(100.0, floatingbondRedemption1)) - floatingBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate1, - floatingBondStartDate1, - floatingBondLeg1, - ) - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - ## market price observed on 7th June 2007 - floatingBondMktPrice1 = 101.64 - floatingBondMktFullPrice1 = floatingBondMktPrice1 + floatingBond1.accruedAmount() - floatingBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondParAssetSwap1.setPricingEngine(swapEngine) - floatingBondParAssetSwapSpread1 = floatingBondParAssetSwap1.fairSpread() - floatingBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - floatingBondMktAssetSwap1.setPricingEngine(swapEngine) - floatingBondMktAssetSwapSpread1 = floatingBondMktAssetSwap1.fairSpread() - error3 = abs( - floatingBondMktAssetSwapSpread1 - 100 * floatingBondParAssetSwapSpread1 / floatingBondMktFullPrice1 - ) - - self.assertFalse( - error3 > tolerance, - "wrong asset swap spreads for floating bond:" - + "\n market asset swap spread: " - + str(floatingBondMktAssetSwapSpread1) - + "\n par asset swap spread: " - + str(floatingBondParAssetSwapSpread1) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - - floatingBondStartDate2 = ql.Date(24, ql.September, 2004) - floatingBondMaturityDate2 = ql.Date(24, ql.September, 2018) - floatingBondSchedule2 = ql.Schedule( - floatingBondStartDate2, - floatingBondMaturityDate2, - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg2 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0025], - [], - [], - inArrears, - ) - ) - floatingbondRedemption2 = bondCalendar.adjust(floatingBondMaturityDate2, ql.ModifiedFollowing) - floatingBondLeg2.append(ql.SimpleCashFlow(100.0, floatingbondRedemption2)) - floatingBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate2, - floatingBondStartDate2, - floatingBondLeg2, - ) - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - ## market price observed on 7th June 2007 - floatingBondMktPrice2 = 101.248 - floatingBondMktFullPrice2 = floatingBondMktPrice2 + floatingBond2.accruedAmount() - floatingBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondParAssetSwap2.setPricingEngine(swapEngine) - floatingBondParAssetSwapSpread2 = floatingBondParAssetSwap2.fairSpread() - floatingBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - floatingBondMktAssetSwap2.setPricingEngine(swapEngine) - floatingBondMktAssetSwapSpread2 = floatingBondMktAssetSwap2.fairSpread() - error4 = abs( - floatingBondMktAssetSwapSpread2 - 100 * floatingBondParAssetSwapSpread2 / floatingBondMktFullPrice2 - ) - - self.assertFalse( - error4 > tolerance, - "wrong asset swap spreads for floating bond:" - + "\n market asset swap spread: " - + str(floatingBondMktAssetSwapSpread2) - + "\n par asset swap spread: " - + str(floatingBondParAssetSwapSpread2) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondStartDate1 = ql.Date(22, ql.August, 2005) - cmsBondMaturityDate1 = ql.Date(22, ql.August, 2020) - cmsBondSchedule1 = ql.Schedule( - cmsBondStartDate1, - cmsBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg1 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [], - [], - [0.055], - [0.025], - inArrears, - ) - ) - cmsbondRedemption1 = bondCalendar.adjust(cmsBondMaturityDate1, ql.Following) - cmsBondLeg1.append(ql.SimpleCashFlow(100.0, cmsbondRedemption1)) - cmsBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate1, cmsBondStartDate1, cmsBondLeg1 - ) - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondMktPrice1 = 88.45 ## market price observed on 7th June 2007 - cmsBondMktFullPrice1 = cmsBondMktPrice1 + cmsBond1.accruedAmount() - cmsBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondParAssetSwap1.setPricingEngine(swapEngine) - cmsBondParAssetSwapSpread1 = cmsBondParAssetSwap1.fairSpread() - cmsBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - cmsBondMktAssetSwap1.setPricingEngine(swapEngine) - cmsBondMktAssetSwapSpread1 = cmsBondMktAssetSwap1.fairSpread() - error5 = abs(cmsBondMktAssetSwapSpread1 - 100 * cmsBondParAssetSwapSpread1 / cmsBondMktFullPrice1) - - self.assertFalse( - error5 > tolerance, - "wrong asset swap spreads for cms bond:" - + "\n market asset swap spread: " - + str(cmsBondMktAssetSwapSpread1) - + "\n par asset swap spread: " - + str(100 * cmsBondParAssetSwapSpread1 / cmsBondMktFullPrice1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - - cmsBondStartDate2 = ql.Date(6, ql.May, 2005) - cmsBondMaturityDate2 = ql.Date(6, ql.May, 2015) - cmsBondSchedule2 = ql.Schedule( - cmsBondStartDate2, - cmsBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg2 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [0.84], - [], - [], - [], - inArrears, - ) - ) - cmsbondRedemption2 = bondCalendar.adjust(cmsBondMaturityDate2, ql.Following) - cmsBondLeg2.append(ql.SimpleCashFlow(100.0, cmsbondRedemption2)) - cmsBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate2, cmsBondStartDate2, cmsBondLeg2 - ) - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondMktPrice2 = 94.08 ## market price observed on 7th June 2007 - cmsBondMktFullPrice2 = cmsBondMktPrice2 + cmsBond2.accruedAmount() - cmsBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondParAssetSwap2.setPricingEngine(swapEngine) - cmsBondParAssetSwapSpread2 = cmsBondParAssetSwap2.fairSpread() - cmsBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - cmsBondMktAssetSwap2.setPricingEngine(swapEngine) - cmsBondMktAssetSwapSpread2 = cmsBondMktAssetSwap2.fairSpread() - error6 = abs(cmsBondMktAssetSwapSpread2 - 100 * cmsBondParAssetSwapSpread2 / cmsBondMktFullPrice2) - - self.assertFalse( - error6 > tolerance, - "wrong asset swap spreads for cms bond:" - + "\n market asset swap spread: " - + str(cmsBondMktAssetSwapSpread2) - + "\n par asset swap spread: " - + str(cmsBondParAssetSwapSpread2) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBondStartDate1 = ql.Date(19, ql.December, 1985) - zeroCpnBondMaturityDate1 = ql.Date(20, ql.December, 2015) - zeroCpnBondRedemption1 = bondCalendar.adjust(zeroCpnBondMaturityDate1, ql.Following) - zeroCpnBondLeg1 = ql.Leg([ql.SimpleCashFlow(100.0, zeroCpnBondRedemption1)]) - zeroCpnBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate1, - zeroCpnBondStartDate1, - zeroCpnBondLeg1, - ) - zeroCpnBond1.setPricingEngine(bondEngine) - - ## market price observed on 12th June 2007 - zeroCpnBondMktPrice1 = 70.436 - zeroCpnBondMktFullPrice1 = zeroCpnBondMktPrice1 + zeroCpnBond1.accruedAmount() - zeroCpnBondParAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondParAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondParAssetSwapSpread1 = zeroCpnBondParAssetSwap1.fairSpread() - zeroCpnBondMktAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - zeroCpnBondMktAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondMktAssetSwapSpread1 = zeroCpnBondMktAssetSwap1.fairSpread() - error7 = abs(zeroCpnBondMktAssetSwapSpread1 - 100 * zeroCpnBondParAssetSwapSpread1 / zeroCpnBondMktFullPrice1) - - self.assertFalse( - error7 > tolerance, - "wrong asset swap spreads for zero cpn bond:" - + "\n market asset swap spread: " - + str(zeroCpnBondMktAssetSwapSpread1) - + "\n par asset swap spread: " - + str(zeroCpnBondParAssetSwapSpread1) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - - zeroCpnBondStartDate2 = ql.Date(17, ql.February, 1998) - zeroCpnBondMaturityDate2 = ql.Date(17, ql.February, 2028) - zerocpbondRedemption2 = bondCalendar.adjust(zeroCpnBondMaturityDate2, ql.Following) - zeroCpnBondLeg2 = ql.Leg([ql.SimpleCashFlow(100.0, zerocpbondRedemption2)]) - zeroCpnBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate2, - zeroCpnBondStartDate2, - zeroCpnBondLeg2, - ) - zeroCpnBond2.setPricingEngine(bondEngine) - - ## zeroCpnBondPrice2 = zeroCpnBond2.cleanPrice() - ## market price observed on 12th June 2007 - zeroCpnBondMktPrice2 = 35.160 - zeroCpnBondMktFullPrice2 = zeroCpnBondMktPrice2 + zeroCpnBond2.accruedAmount() - zeroCpnBondParAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondParAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondParAssetSwapSpread2 = zeroCpnBondParAssetSwap2.fairSpread() - zeroCpnBondMktAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - mktAssetSwap, - ) - zeroCpnBondMktAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondMktAssetSwapSpread2 = zeroCpnBondMktAssetSwap2.fairSpread() - error8 = abs(zeroCpnBondMktAssetSwapSpread2 - 100 * zeroCpnBondParAssetSwapSpread2 / zeroCpnBondMktFullPrice2) - - self.assertFalse( - error8 > tolerance, - "wrong asset swap spreads for zero cpn bond:" - + "\n market asset swap spread: " - + str(zeroCpnBondMktAssetSwapSpread2) - + "\n par asset swap spread: " - + str(zeroCpnBondParAssetSwapSpread2) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - def testZSpreadWithGenericBond(self): - """Testing clean and dirty price with null Z-spread against theoretical prices...""" - - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - - fixedBondStartDate1 = ql.Date(4, ql.January, 2005) - fixedBondMaturityDate1 = ql.Date(4, ql.January, 2037) - fixedBondSchedule1 = ql.Schedule( - fixedBondStartDate1, - fixedBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg1 = list( - ql.FixedRateLeg(fixedBondSchedule1, ql.ActualActual(ql.ActualActual.ISDA), [self.faceAmount], [0.04]) - ) - fixedbondRedemption1 = bondCalendar.adjust(fixedBondMaturityDate1, ql.Following) - fixedBondLeg1.append(ql.SimpleCashFlow(100.0, fixedbondRedemption1)) - fixedBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate1, fixedBondStartDate1, fixedBondLeg1 - ) - bondEngine = ql.DiscountingBondEngine(self.termStructure) - fixedBond1.setPricingEngine(bondEngine) - - fixedBondImpliedValue1 = fixedBond1.cleanPrice() - fixedBondSettlementDate1 = fixedBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - fixedBondCleanPrice1 = ql.cleanPriceFromZSpread( - fixedBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - fixedBondSettlementDate1, - ) - tolerance = 1.0e-13 - error1 = abs(fixedBondImpliedValue1 - fixedBondCleanPrice1) - self.assertFalse( - error1 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondImpliedValue1) - + "\n par asset swap spread: " - + str(fixedBondCleanPrice1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - - fixedBondStartDate2 = ql.Date(5, ql.February, 2005) - fixedBondMaturityDate2 = ql.Date(5, ql.February, 2019) - fixedBondSchedule2 = ql.Schedule( - fixedBondStartDate2, - fixedBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg2 = list( - ql.FixedRateLeg(fixedBondSchedule2, ql.Thirty360(ql.Thirty360.BondBasis), [self.faceAmount], [0.05]) - ) - fixedbondRedemption2 = bondCalendar.adjust(fixedBondMaturityDate2, ql.Following) - fixedBondLeg2.append(ql.SimpleCashFlow(100.0, fixedbondRedemption2)) - fixedBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate2, fixedBondStartDate2, fixedBondLeg2 - ) - fixedBond2.setPricingEngine(bondEngine) - - fixedBondImpliedValue2 = fixedBond2.cleanPrice() - fixedBondSettlementDate2 = fixedBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - - fixedBondCleanPrice2 = ql.cleanPriceFromZSpread( - fixedBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - fixedBondSettlementDate2, - ) - error3 = abs(fixedBondImpliedValue2 - fixedBondCleanPrice2) - self.assertFalse( - error3 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(fixedBondImpliedValue2) - + "\n par asset swap spread: " - + str(fixedBondCleanPrice2) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - - floatingBondStartDate1 = ql.Date(29, ql.September, 2003) - floatingBondMaturityDate1 = ql.Date(29, ql.September, 2013) - floatingBondSchedule1 = ql.Schedule( - floatingBondStartDate1, - floatingBondMaturityDate1, - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg1 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - [fixingDays], - [], - [0.0056], - [], - [], - inArrears, - ) - ) - floatingbondRedemption1 = bondCalendar.adjust(floatingBondMaturityDate1, ql.Following) - floatingBondLeg1.append(ql.SimpleCashFlow(100.0, floatingbondRedemption1)) - floatingBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate1, - floatingBondStartDate1, - floatingBondLeg1, - ) - floatingBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondImpliedValue1 = floatingBond1.cleanPrice() - floatingBondSettlementDate1 = floatingBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - floatingBondCleanPrice1 = ql.cleanPriceFromZSpread( - floatingBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Semiannual, - floatingBondSettlementDate1, - ) - error5 = abs(floatingBondImpliedValue1 - floatingBondCleanPrice1) - self.assertFalse( - error5 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(floatingBondImpliedValue1) - + "\n par asset swap spread: " - + str(floatingBondCleanPrice1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - - floatingBondStartDate2 = ql.Date(24, ql.September, 2004) - floatingBondMaturityDate2 = ql.Date(24, ql.September, 2018) - floatingBondSchedule2 = ql.Schedule( - floatingBondStartDate2, - floatingBondMaturityDate2, - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg2 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0025], - [], - [], - inArrears, - ) - ) - floatingbondRedemption2 = bondCalendar.adjust(floatingBondMaturityDate2, ql.ModifiedFollowing) - floatingBondLeg2.append(ql.SimpleCashFlow(100.0, floatingbondRedemption2)) - floatingBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate2, - floatingBondStartDate2, - floatingBondLeg2, - ) - floatingBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - floatingBondImpliedValue2 = floatingBond2.cleanPrice() - floatingBondSettlementDate2 = floatingBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - floatingBondCleanPrice2 = ql.cleanPriceFromZSpread( - floatingBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Semiannual, - floatingBondSettlementDate2, - ) - error7 = abs(floatingBondImpliedValue2 - floatingBondCleanPrice2) - self.assertFalse( - error7 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(floatingBondImpliedValue2) - + "\n par asset swap spread: " - + str(floatingBondCleanPrice2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondStartDate1 = ql.Date(22, ql.August, 2005) - cmsBondMaturityDate1 = ql.Date(22, ql.August, 2020) - cmsBondSchedule1 = ql.Schedule( - cmsBondStartDate1, - cmsBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg1 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [], - [], - [0.055], - [0.025], - inArrears, - ) - ) - cmsbondRedemption1 = bondCalendar.adjust(cmsBondMaturityDate1, ql.Following) - cmsBondLeg1.append(ql.SimpleCashFlow(100.0, cmsbondRedemption1)) - cmsBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate1, cmsBondStartDate1, cmsBondLeg1 - ) - cmsBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondImpliedValue1 = cmsBond1.cleanPrice() - cmsBondSettlementDate1 = cmsBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - cmsBondCleanPrice1 = ql.cleanPriceFromZSpread( - cmsBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - cmsBondSettlementDate1, - ) - error9 = abs(cmsBondImpliedValue1 - cmsBondCleanPrice1) - self.assertFalse( - error9 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(cmsBondImpliedValue1) - + "\n par asset swap spread: " - + str(cmsBondCleanPrice1) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - - cmsBondStartDate2 = ql.Date(6, ql.May, 2005) - cmsBondMaturityDate2 = ql.Date(6, ql.May, 2015) - cmsBondSchedule2 = ql.Schedule( - cmsBondStartDate2, - cmsBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg2 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [0.84], - [], - [], - [], - inArrears, - ) - ) - cmsbondRedemption2 = bondCalendar.adjust(cmsBondMaturityDate2, ql.Following) - cmsBondLeg2.append(ql.SimpleCashFlow(100.0, cmsbondRedemption2)) - cmsBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate2, cmsBondStartDate2, cmsBondLeg2 - ) - cmsBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondImpliedValue2 = cmsBond2.cleanPrice() - cmsBondSettlementDate2 = cmsBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - cmsBondCleanPrice2 = ql.cleanPriceFromZSpread( - cmsBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - cmsBondSettlementDate2, - ) - error11 = abs(cmsBondImpliedValue2 - cmsBondCleanPrice2) - self.assertFalse( - error11 > tolerance, - "wrong clean price for fixed bond:" - + "\n market asset swap spread: " - + str(cmsBondImpliedValue2) - + "\n par asset swap spread: " - + str(cmsBondCleanPrice2) - + "\n error: " - + str(error11) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - - zeroCpnBondStartDate1 = ql.Date(19, ql.December, 1985) - zeroCpnBondMaturityDate1 = ql.Date(20, ql.December, 2015) - zeroCpnBondRedemption1 = bondCalendar.adjust(zeroCpnBondMaturityDate1, ql.Following) - zeroCpnBondLeg1 = ql.Leg([ql.SimpleCashFlow(100.0, zeroCpnBondRedemption1)]) - zeroCpnBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate1, - zeroCpnBondStartDate1, - zeroCpnBondLeg1, - ) - zeroCpnBond1.setPricingEngine(bondEngine) - - zeroCpnBondImpliedValue1 = zeroCpnBond1.cleanPrice() - zeroCpnBondSettlementDate1 = zeroCpnBond1.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - zeroCpnBondCleanPrice1 = ql.cleanPriceFromZSpread( - zeroCpnBond1, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - zeroCpnBondSettlementDate1, - ) - error13 = abs(zeroCpnBondImpliedValue1 - zeroCpnBondCleanPrice1) - self.assertFalse( - error13 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n zero cpn implied value: " - + str(zeroCpnBondImpliedValue1) - + "\n zero cpn price: " - + str(zeroCpnBondCleanPrice1) - + "\n error: " - + str(error13) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - - zeroCpnBondStartDate2 = ql.Date(17, ql.February, 1998) - zeroCpnBondMaturityDate2 = ql.Date(17, ql.February, 2028) - zerocpbondRedemption2 = bondCalendar.adjust(zeroCpnBondMaturityDate2, ql.Following) - zeroCpnBondLeg2 = ql.Leg([ql.SimpleCashFlow(100.0, zerocpbondRedemption2)]) - zeroCpnBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate2, - zeroCpnBondStartDate2, - zeroCpnBondLeg2, - ) - zeroCpnBond2.setPricingEngine(bondEngine) - - zeroCpnBondImpliedValue2 = zeroCpnBond2.cleanPrice() - zeroCpnBondSettlementDate2 = zeroCpnBond2.settlementDate() - ## standard market conventions: - ## bond's frequency + coumpounding and daycounter of the YieldCurve - zeroCpnBondCleanPrice2 = ql.cleanPriceFromZSpread( - zeroCpnBond2, - self.yieldCurve, - self.spread, - ql.Actual365Fixed(), - self.compounding, - ql.Annual, - zeroCpnBondSettlementDate2, - ) - error15 = abs(zeroCpnBondImpliedValue2 - zeroCpnBondCleanPrice2) - - self.assertFalse( - error15 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n zero cpn implied value: " - + str(zeroCpnBondImpliedValue2) - + "\n zero cpn price: " - + str(zeroCpnBondCleanPrice2) - + "\n error: " - + str(error15) - + "\n tolerance: " - + str(tolerance), - ) - - def testSpecializedBondVsGenericBond(self): - """Testing clean and dirty prices for specialized bond against equivalent generic bond...""" - - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - inArrears = False - - ## Fixed Underlying bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - fixedBondStartDate1 = ql.Date(4, ql.January, 2005) - fixedBondMaturityDate1 = ql.Date(4, ql.January, 2037) - fixedBondSchedule1 = ql.Schedule( - fixedBondStartDate1, - fixedBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg1 = list( - ql.FixedRateLeg(fixedBondSchedule1, ql.ActualActual(ql.ActualActual.ISDA), [self.faceAmount], [0.04]) - ) - fixedbondRedemption1 = bondCalendar.adjust(fixedBondMaturityDate1, ql.Following) - fixedBondLeg1.append(ql.SimpleCashFlow(100.0, fixedbondRedemption1)) - ## generic bond - fixedBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate1, fixedBondStartDate1, fixedBondLeg1 - ) - bondEngine = ql.DiscountingBondEngine(self.termStructure) - fixedBond1.setPricingEngine(bondEngine) - - ## equivalent specialized fixed rate bond - fixedSpecializedBond1 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule1, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - fixedSpecializedBond1.setPricingEngine(bondEngine) - - fixedBondTheoValue1 = fixedBond1.cleanPrice() - fixedSpecializedBondTheoValue1 = fixedSpecializedBond1.cleanPrice() - tolerance = 1.0e-13 - error1 = abs(fixedBondTheoValue1 - fixedSpecializedBondTheoValue1) - - self.assertFalse( - error1 > tolerance, - "wrong clean price for fixed bond:" - + "\n specialized fixed rate bond's theo clean price: " - + str(fixedBondTheoValue1) - + "\n generic equivalent bond's theo clean price: " - + str(fixedSpecializedBondTheoValue1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - fixedBondTheoDirty1 = fixedBondTheoValue1 + fixedBond1.accruedAmount() - fixedSpecializedTheoDirty1 = fixedSpecializedBondTheoValue1 + fixedSpecializedBond1.accruedAmount() - error2 = abs(fixedBondTheoDirty1 - fixedSpecializedTheoDirty1) - - self.assertFalse( - error2 > tolerance, - "wrong dirty price for fixed bond:" - + "\n specialized fixed rate bond's theo dirty price: " - + str(fixedBondTheoDirty1) - + "\n generic equivalent bond's theo dirty price: " - + str(fixedSpecializedTheoDirty1) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ## Fixed Underlying bond (Isin: IT0006527060 IBRD 5 02/05/19) - ## maturity occurs on a business day - fixedBondStartDate2 = ql.Date(5, ql.February, 2005) - fixedBondMaturityDate2 = ql.Date(5, ql.February, 2019) - fixedBondSchedule2 = ql.Schedule( - fixedBondStartDate2, - fixedBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg2 = list( - ql.FixedRateLeg(fixedBondSchedule2, ql.Thirty360(ql.Thirty360.BondBasis), [self.faceAmount], [0.05]) - ) - fixedbondRedemption2 = bondCalendar.adjust(fixedBondMaturityDate2, ql.Following) - fixedBondLeg2.append(ql.SimpleCashFlow(100.0, fixedbondRedemption2)) - - ## generic bond - fixedBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate2, fixedBondStartDate2, fixedBondLeg2 - ) - fixedBond2.setPricingEngine(bondEngine) - - ## equivalent specialized fixed rate bond - fixedSpecializedBond2 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule2, - [0.05], - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - 100.0, - ql.Date(5, ql.February, 2005), - ) - fixedSpecializedBond2.setPricingEngine(bondEngine) - - fixedBondTheoValue2 = fixedBond2.cleanPrice() - fixedSpecializedBondTheoValue2 = fixedSpecializedBond2.cleanPrice() - - error3 = abs(fixedBondTheoValue2 - fixedSpecializedBondTheoValue2) - self.assertFalse( - error3 > tolerance, - "wrong clean price for fixed bond:" - + "\n specialized fixed rate bond's theo clean price: " - + str(fixedBondTheoValue2) - + "\n generic equivalent bond's theo clean price: " - + str(fixedSpecializedBondTheoValue2) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - fixedBondTheoDirty2 = fixedBondTheoValue2 + fixedBond2.accruedAmount() - fixedSpecializedBondTheoDirty2 = fixedSpecializedBondTheoValue2 + fixedSpecializedBond2.accruedAmount() - - error4 = abs(fixedBondTheoDirty2 - fixedSpecializedBondTheoDirty2) - self.assertFalse( - error4 > tolerance, - "wrong dirty price for fixed bond:" - + "\n specialized fixed rate bond's dirty clean price: " - + str(fixedBondTheoDirty2) - + "\n generic equivalent bond's theo dirty price: " - + str(fixedSpecializedBondTheoDirty2) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ## maturity doesn't occur on a business day - floatingBondStartDate1 = ql.Date(29, ql.September, 2003) - floatingBondMaturityDate1 = ql.Date(29, ql.September, 2013) - floatingBondSchedule1 = ql.Schedule( - floatingBondStartDate1, - floatingBondMaturityDate1, - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg1 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - [fixingDays], - [], - [0.0056], - [], - [], - inArrears, - ) - ) - floatingbondRedemption1 = bondCalendar.adjust(floatingBondMaturityDate1, ql.Following) - floatingBondLeg1.append(ql.SimpleCashFlow(100.0, floatingbondRedemption1)) - ## generic bond - floatingBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate1, - floatingBondStartDate1, - floatingBondLeg1, - ) - floatingBond1.setPricingEngine(bondEngine) - - ## equivalent specialized floater - floatingSpecializedBond1 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - fixingDays, - [1], - [0.0056], - [], - [], - inArrears, - 100.0, - ql.Date(29, ql.September, 2003), - ) - floatingSpecializedBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - ql.setCouponPricer(floatingSpecializedBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondTheoValue1 = floatingBond1.cleanPrice() - floatingSpecializedBondTheoValue1 = floatingSpecializedBond1.cleanPrice() - - error5 = abs(floatingBondTheoValue1 - floatingSpecializedBondTheoValue1) - self.assertFalse( - error5 > tolerance, - "wrong clean price for fixed bond:" - + "\n generic fixed rate bond's theo clean price: " - + str(floatingBondTheoValue1) - + "\n equivalent specialized bond's theo clean price: " - + str(floatingSpecializedBondTheoValue1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - floatingBondTheoDirty1 = floatingBondTheoValue1 + floatingBond1.accruedAmount() - floatingSpecializedBondTheoDirty1 = floatingSpecializedBondTheoValue1 + floatingSpecializedBond1.accruedAmount() - error6 = abs(floatingBondTheoDirty1 - floatingSpecializedBondTheoDirty1) - self.assertFalse( - error6 > tolerance, - "wrong dirty price for frn bond:" - + "\n generic frn bond's dirty clean price: " - + str(floatingBondTheoDirty1) - + "\n equivalent specialized bond's theo dirty price: " - + str(floatingSpecializedBondTheoDirty1) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ## FRN Underlying bond (Isin: XS0090566539 COE 0 09/24/18) - ## maturity occurs on a business day - floatingBondStartDate2 = ql.Date(24, ql.September, 2004) - floatingBondMaturityDate2 = ql.Date(24, ql.September, 2018) - floatingBondSchedule2 = ql.Schedule( - floatingBondStartDate2, - floatingBondMaturityDate2, - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg2 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0025], - [], - [], - inArrears, - ) - ) - floatingbondRedemption2 = bondCalendar.adjust(floatingBondMaturityDate2, ql.ModifiedFollowing) - floatingBondLeg2.append(ql.SimpleCashFlow(100.0, floatingbondRedemption2)) - ## generic bond - floatingBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate2, - floatingBondStartDate2, - floatingBondLeg2, - ) - floatingBond2.setPricingEngine(bondEngine) - - ## equivalent specialized floater - floatingSpecializedBond2 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - fixingDays, - [1], - [0.0025], - [], - [], - inArrears, - 100.0, - ql.Date(24, ql.September, 2004), - ) - floatingSpecializedBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - ql.setCouponPricer(floatingSpecializedBond2.cashflows(), self.pricer) - - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - - floatingBondTheoValue2 = floatingBond2.cleanPrice() - floatingSpecializedBondTheoValue2 = floatingSpecializedBond2.cleanPrice() - - error7 = abs(floatingBondTheoValue2 - floatingSpecializedBondTheoValue2) - self.assertFalse( - error7 > tolerance, - "wrong clean price for floater bond:" - + "\n generic floater bond's theo clean price: " - + str(floatingBondTheoValue2) - + "\n equivalent specialized bond's theo clean price: " - + str(floatingSpecializedBondTheoValue2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - floatingBondTheoDirty2 = floatingBondTheoValue2 + floatingBond2.accruedAmount() - floatingSpecializedTheoDirty2 = floatingSpecializedBondTheoValue2 + floatingSpecializedBond2.accruedAmount() - - error8 = abs(floatingBondTheoDirty2 - floatingSpecializedTheoDirty2) - self.assertFalse( - error8 > tolerance, - "wrong dirty price for floater bond:" - + "\n generic floater bond's theo dirty price: " - + str(floatingBondTheoDirty2) - + "\n equivalent specialized bond's theo dirty price: " - + str(floatingSpecializedTheoDirty2) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - cmsBondStartDate1 = ql.Date(22, ql.August, 2005) - cmsBondMaturityDate1 = ql.Date(22, ql.August, 2020) - cmsBondSchedule1 = ql.Schedule( - cmsBondStartDate1, - cmsBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg1 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [], - [], - [0.055], - [0.025], - inArrears, - ) - ) - cmsbondRedemption1 = bondCalendar.adjust(cmsBondMaturityDate1, ql.Following) - cmsBondLeg1.append(ql.SimpleCashFlow(100.0, cmsbondRedemption1)) - ## generic cms bond - cmsBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate1, cmsBondStartDate1, cmsBondLeg1 - ) - cmsBond1.setPricingEngine(bondEngine) - - ## equivalent specialized cms bond - cmsSpecializedBond1 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [1.0], - [0.0], - [0.055], - [0.025], - inArrears, - 100.0, - ql.Date(22, ql.August, 2005), - ) - cmsSpecializedBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - ql.setCouponPricer(cmsSpecializedBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondTheoValue1 = cmsBond1.cleanPrice() - cmsSpecializedBondTheoValue1 = cmsSpecializedBond1.cleanPrice() - error9 = abs(cmsBondTheoValue1 - cmsSpecializedBondTheoValue1) - self.assertFalse( - error9 > tolerance, - "wrong clean price for cms bond:" - + "\n generic cms bond's theo clean price: " - + str(cmsBondTheoValue1) - + "\n equivalent specialized bond's theo clean price: " - + str(cmsSpecializedBondTheoValue1) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - cmsBondTheoDirty1 = cmsBondTheoValue1 + cmsBond1.accruedAmount() - cmsSpecializedBondTheoDirty1 = cmsSpecializedBondTheoValue1 + cmsSpecializedBond1.accruedAmount() - error10 = abs(cmsBondTheoDirty1 - cmsSpecializedBondTheoDirty1) - self.assertFalse( - error10 > tolerance, - "wrong dirty price for cms bond:" - + "\n generic cms bond's theo dirty price: " - + str(cmsBondTheoDirty1) - + "\n specialized cms bond's theo dirty price: " - + str(cmsSpecializedBondTheoDirty1) - + "\n error: " - + str(error10) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS Underlying bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ## maturity occurs on a business day - cmsBondStartDate2 = ql.Date(6, ql.May, 2005) - cmsBondMaturityDate2 = ql.Date(6, ql.May, 2015) - cmsBondSchedule2 = ql.Schedule( - cmsBondStartDate2, - cmsBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg2 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [0.84], - [], - [], - [], - inArrears, - ) - ) - cmsbondRedemption2 = bondCalendar.adjust(cmsBondMaturityDate2, ql.Following) - cmsBondLeg2.append(ql.SimpleCashFlow(100.0, cmsbondRedemption2)) - ## generic bond - cmsBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate2, cmsBondStartDate2, cmsBondLeg2 - ) - cmsBond2.setPricingEngine(bondEngine) - - ## equivalent specialized cms bond - cmsSpecializedBond2 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [0.84], - [0.0], - [], - [], - inArrears, - 100.0, - ql.Date(6, ql.May, 2005), - ) - cmsSpecializedBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - ql.setCouponPricer(cmsSpecializedBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondTheoValue2 = cmsBond2.cleanPrice() - cmsSpecializedBondTheoValue2 = cmsSpecializedBond2.cleanPrice() - - error11 = abs(cmsBondTheoValue2 - cmsSpecializedBondTheoValue2) - self.assertFalse( - error11 > tolerance, - "wrong clean price for cms bond:" - + "\n generic cms bond's theo clean price: " - + str(cmsBondTheoValue2) - + "\n cms bond's theo clean price: " - + str(cmsSpecializedBondTheoValue2) - + "\n error: " - + str(error11) - + "\n tolerance: " - + str(tolerance), - ) - - cmsBondTheoDirty2 = cmsBondTheoValue2 + cmsBond2.accruedAmount() - cmsSpecializedBondTheoDirty2 = cmsSpecializedBondTheoValue2 + cmsSpecializedBond2.accruedAmount() - error12 = abs(cmsBondTheoDirty2 - cmsSpecializedBondTheoDirty2) - self.assertFalse( - error12 > tolerance, - "wrong dirty price for cms bond:" - + "\n generic cms bond's dirty price: " - + str(cmsBondTheoDirty2) - + "\n specialized cms bond's theo dirty price: " - + str(cmsSpecializedBondTheoDirty2) - + "\n error: " - + str(error12) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - zeroCpnBondStartDate1 = ql.Date(19, ql.December, 1985) - zeroCpnBondMaturityDate1 = ql.Date(20, ql.December, 2015) - zeroCpnBondRedemption1 = bondCalendar.adjust(zeroCpnBondMaturityDate1, ql.Following) - zeroCpnBondLeg1 = ql.Leg([ql.SimpleCashFlow(100.0, zeroCpnBondRedemption1)]) - - ## generic bond - zeroCpnBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate1, - zeroCpnBondStartDate1, - zeroCpnBondLeg1, - ) - zeroCpnBond1.setPricingEngine(bondEngine) - - ## specialized zerocpn bond - zeroCpnSpecializedBond1 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(20, ql.December, 2015), - ql.Following, - 100.0, - ql.Date(19, ql.December, 1985), - ) - zeroCpnSpecializedBond1.setPricingEngine(bondEngine) - - zeroCpnBondTheoValue1 = zeroCpnBond1.cleanPrice() - zeroCpnSpecializedBondTheoValue1 = zeroCpnSpecializedBond1.cleanPrice() - - error13 = abs(zeroCpnBondTheoValue1 - zeroCpnSpecializedBondTheoValue1) - self.assertFalse( - error13 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n generic zero bond's clean price: " - + str(zeroCpnBondTheoValue1) - + "\n specialized zero bond's clean price: " - + str(zeroCpnSpecializedBondTheoValue1) - + "\n error: " - + str(error13) - + "\n tolerance: " - + str(tolerance), - ) - - zeroCpnBondTheoDirty1 = zeroCpnBondTheoValue1 + zeroCpnBond1.accruedAmount() - zeroCpnSpecializedBondTheoDirty1 = zeroCpnSpecializedBondTheoValue1 + zeroCpnSpecializedBond1.accruedAmount() - error14 = abs(zeroCpnBondTheoDirty1 - zeroCpnSpecializedBondTheoDirty1) - self.assertFalse( - error14 > tolerance, - "wrong dirty price for zero bond:" - + "\n generic zerocpn bond's dirty price: " - + str(zeroCpnBondTheoDirty1) - + "\n specialized zerocpn bond's clean price: " - + str(zeroCpnSpecializedBondTheoDirty1) - + "\n error: " - + str(error14) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity occurs on a business day - zeroCpnBondStartDate2 = ql.Date(17, ql.February, 1998) - zeroCpnBondMaturityDate2 = ql.Date(17, ql.February, 2028) - zerocpbondRedemption2 = bondCalendar.adjust(zeroCpnBondMaturityDate2, ql.Following) - zeroCpnBondLeg2 = ql.Leg([ql.SimpleCashFlow(100.0, zerocpbondRedemption2)]) - ## generic bond - zeroCpnBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate2, - zeroCpnBondStartDate2, - zeroCpnBondLeg2, - ) - zeroCpnBond2.setPricingEngine(bondEngine) - - ## specialized zerocpn bond - zeroCpnSpecializedBond2 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(17, ql.February, 2028), - ql.Following, - 100.0, - ql.Date(17, ql.February, 1998), - ) - zeroCpnSpecializedBond2.setPricingEngine(bondEngine) - - zeroCpnBondTheoValue2 = zeroCpnBond2.cleanPrice() - zeroCpnSpecializedBondTheoValue2 = zeroCpnSpecializedBond2.cleanPrice() - - error15 = abs(zeroCpnBondTheoValue2 - zeroCpnSpecializedBondTheoValue2) - self.assertFalse( - error15 > tolerance, - "wrong clean price for zero coupon bond:" - + "\n generic zerocpn bond's clean price: " - + str(zeroCpnBondTheoValue2) - + "\n specialized zerocpn bond's clean price: " - + str(zeroCpnSpecializedBondTheoValue2) - + "\n error: " - + str(error15) - + "\n tolerance: " - + str(tolerance), - ) - - zeroCpnBondTheoDirty2 = zeroCpnBondTheoValue2 + zeroCpnBond2.accruedAmount() - - zeroCpnSpecializedBondTheoDirty2 = zeroCpnSpecializedBondTheoValue2 + zeroCpnSpecializedBond2.accruedAmount() - - error16 = abs(zeroCpnBondTheoDirty2 - zeroCpnSpecializedBondTheoDirty2) - self.assertFalse( - error16 > tolerance, - "wrong dirty price for zero coupon bond:" - + "\n generic zerocpn bond's dirty price: " - + str(zeroCpnBondTheoDirty2) - + "\n specialized zerocpn bond's dirty price: " - + str(zeroCpnSpecializedBondTheoDirty2) - + "\n error: " - + str(error16) - + "\n tolerance: " - + str(tolerance), - ) - - def testSpecializedBondVsGenericBondUsingAsw(self): - """Testing asset-swap prices and spreads for specialized bond against equivalent generic bond...""" - - bondCalendar = ql.TARGET() - settlementDays = 3 - fixingDays = 2 - payFixedRate = True - parAssetSwap = True - inArrears = False - - ## Fixed bond (Isin: DE0001135275 DBR 4 01/04/37) - ## maturity doesn't occur on a business day - fixedBondStartDate1 = ql.Date(4, ql.January, 2005) - fixedBondMaturityDate1 = ql.Date(4, ql.January, 2037) - fixedBondSchedule1 = ql.Schedule( - fixedBondStartDate1, - fixedBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg1 = list( - ql.FixedRateLeg(fixedBondSchedule1, ql.ActualActual(ql.ActualActual.ISDA), [self.faceAmount], [0.04]) - ) - fixedbondRedemption1 = bondCalendar.adjust(fixedBondMaturityDate1, ql.Following) - fixedBondLeg1.append(ql.SimpleCashFlow(100.0, fixedbondRedemption1)) - ## generic bond - fixedBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate1, fixedBondStartDate1, fixedBondLeg1 - ) - - bondEngine = ql.DiscountingBondEngine(self.termStructure) - swapEngine = ql.DiscountingSwapEngine(self.termStructure, False) - fixedBond1.setPricingEngine(bondEngine) - - ## equivalent specialized fixed rate bond - fixedSpecializedBond1 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule1, - [0.04], - ql.ActualActual(ql.ActualActual.ISDA), - ql.Following, - 100.0, - ql.Date(4, ql.January, 2005), - ) - fixedSpecializedBond1.setPricingEngine(bondEngine) - - fixedBondPrice1 = fixedBond1.cleanPrice() - fixedSpecializedBondPrice1 = fixedSpecializedBond1.cleanPrice() - fixedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap1.setPricingEngine(swapEngine) - fixedSpecializedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - fixedSpecializedBond1, - fixedSpecializedBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedSpecializedBondAssetSwap1.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice1 = fixedBondAssetSwap1.fairCleanPrice() - fixedSpecializedBondAssetSwapPrice1 = fixedSpecializedBondAssetSwap1.fairCleanPrice() - tolerance = 1.0e-13 - error1 = abs(fixedBondAssetSwapPrice1 - fixedSpecializedBondAssetSwapPrice1) - self.assertFalse( - error1 > tolerance, - "wrong clean price for fixed bond:" - + "\n generic fixed rate bond's clean price: " - + str(fixedBondAssetSwapPrice1) - + "\n equivalent specialized bond's clean price: " - + str(fixedSpecializedBondAssetSwapPrice1) - + "\n error: " - + str(error1) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - fixedBondMktPrice1 = 91.832 - fixedBondASW1 = ql.AssetSwap( - payFixedRate, - fixedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondASW1.setPricingEngine(swapEngine) - fixedSpecializedBondASW1 = ql.AssetSwap( - payFixedRate, - fixedSpecializedBond1, - fixedBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedSpecializedBondASW1.setPricingEngine(swapEngine) - fixedBondASWSpread1 = fixedBondASW1.fairSpread() - fixedSpecializedBondASWSpread1 = fixedSpecializedBondASW1.fairSpread() - error2 = abs(fixedBondASWSpread1 - fixedSpecializedBondASWSpread1) - self.assertFalse( - error2 > tolerance, - "wrong asw spread for fixed bond:" - + "\n generic fixed rate bond's asw spread: " - + str(fixedBondASWSpread1) - + "\n equivalent specialized bond's asw spread: " - + str(fixedSpecializedBondASWSpread1) - + "\n error: " - + str(error2) - + "\n tolerance: " - + str(tolerance), - ) - - ##Fixed bond (Isin: IT0006527060 IBRD 5 02/05/19) - ##maturity occurs on a business day - - fixedBondStartDate2 = ql.Date(5, ql.February, 2005) - fixedBondMaturityDate2 = ql.Date(5, ql.February, 2019) - fixedBondSchedule2 = ql.Schedule( - fixedBondStartDate2, - fixedBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - fixedBondLeg2 = list( - ql.FixedRateLeg(fixedBondSchedule2, ql.Thirty360(ql.Thirty360.BondBasis), [self.faceAmount], [0.05]) - ) - fixedbondRedemption2 = bondCalendar.adjust(fixedBondMaturityDate2, ql.Following) - fixedBondLeg2.append(ql.SimpleCashFlow(100.0, fixedbondRedemption2)) - - ## generic bond - fixedBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, fixedBondMaturityDate2, fixedBondStartDate2, fixedBondLeg2 - ) - fixedBond2.setPricingEngine(bondEngine) - - ## equivalent specialized fixed rate bond - fixedSpecializedBond2 = ql.FixedRateBond( - settlementDays, - self.faceAmount, - fixedBondSchedule2, - [0.05], - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - 100.0, - ql.Date(5, ql.February, 2005), - ) - fixedSpecializedBond2.setPricingEngine(bondEngine) - - fixedBondPrice2 = fixedBond2.cleanPrice() - fixedSpecializedBondPrice2 = fixedSpecializedBond2.cleanPrice() - fixedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondAssetSwap2.setPricingEngine(swapEngine) - fixedSpecializedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - fixedSpecializedBond2, - fixedSpecializedBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedSpecializedBondAssetSwap2.setPricingEngine(swapEngine) - fixedBondAssetSwapPrice2 = fixedBondAssetSwap2.fairCleanPrice() - fixedSpecializedBondAssetSwapPrice2 = fixedSpecializedBondAssetSwap2.fairCleanPrice() - - error3 = abs(fixedBondAssetSwapPrice2 - fixedSpecializedBondAssetSwapPrice2) - self.assertFalse( - error3 > tolerance, - "wrong clean price for fixed bond:" - + "\n generic fixed rate bond's clean price: " - + str(fixedBondAssetSwapPrice2) - + "\n equivalent specialized bond's clean price: " - + str(fixedSpecializedBondAssetSwapPrice2) - + "\n error: " - + str(error3) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - fixedBondMktPrice2 = 102.178 - fixedBondASW2 = ql.AssetSwap( - payFixedRate, - fixedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedBondASW2.setPricingEngine(swapEngine) - fixedSpecializedBondASW2 = ql.AssetSwap( - payFixedRate, - fixedSpecializedBond2, - fixedBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - fixedSpecializedBondASW2.setPricingEngine(swapEngine) - fixedBondASWSpread2 = fixedBondASW2.fairSpread() - fixedSpecializedBondASWSpread2 = fixedSpecializedBondASW2.fairSpread() - error4 = abs(fixedBondASWSpread2 - fixedSpecializedBondASWSpread2) - self.assertFalse( - error4 > tolerance, - "wrong asw spread for fixed bond:" - + "\n generic fixed rate bond's asw spread: " - + str(fixedBondASWSpread2) - + "\n equivalent specialized bond's asw spread: " - + str(fixedSpecializedBondASWSpread2) - + "\n error: " - + str(error4) - + "\n tolerance: " - + str(tolerance), - ) - - ##FRN bond (Isin: IT0003543847 ISPIM 0 09/29/13) - ##maturity doesn't occur on a business day - floatingBondStartDate1 = ql.Date(29, ql.September, 2003) - floatingBondMaturityDate1 = ql.Date(29, ql.September, 2013) - floatingBondSchedule1 = ql.Schedule( - floatingBondStartDate1, - floatingBondMaturityDate1, - ql.Period(ql.Semiannual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg1 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - [fixingDays], - [], - [0.0056], - [], - [], - inArrears, - ) - ) - floatingbondRedemption1 = bondCalendar.adjust(floatingBondMaturityDate1, ql.Following) - floatingBondLeg1.append(ql.SimpleCashFlow(100.0, floatingbondRedemption1)) - ## generic bond - floatingBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate1, - floatingBondStartDate1, - floatingBondLeg1, - ) - floatingBond1.setPricingEngine(bondEngine) - - ## equivalent specialized floater - floatingSpecializedBond1 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule1, - self.iborIndex, - ql.Actual360(), - ql.Following, - fixingDays, - [1], - [0.0056], - [], - [], - inArrears, - 100.0, - ql.Date(29, ql.September, 2003), - ) - floatingSpecializedBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond1.cashflows(), self.pricer) - ql.setCouponPricer(floatingSpecializedBond1.cashflows(), self.pricer) - self.iborIndex.addFixing(ql.Date(27, ql.March, 2007), 0.0402) - floatingBondPrice1 = floatingBond1.cleanPrice() - floatingSpecializedBondPrice1 = floatingSpecializedBond1.cleanPrice() - floatingBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap1.setPricingEngine(swapEngine) - floatingSpecializedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - floatingSpecializedBond1, - floatingSpecializedBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingSpecializedBondAssetSwap1.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice1 = floatingBondAssetSwap1.fairCleanPrice() - floatingSpecializedBondAssetSwapPrice1 = floatingSpecializedBondAssetSwap1.fairCleanPrice() - - error5 = abs(floatingBondAssetSwapPrice1 - floatingSpecializedBondAssetSwapPrice1) - self.assertFalse( - error5 > tolerance, - "wrong clean price for frnbond:" - + "\n generic frn rate bond's clean price: " - + str(floatingBondAssetSwapPrice1) - + "\n equivalent specialized bond's price: " - + str(floatingSpecializedBondAssetSwapPrice1) - + "\n error: " - + str(error5) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - floatingBondMktPrice1 = 101.33 - floatingBondASW1 = ql.AssetSwap( - payFixedRate, - floatingBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondASW1.setPricingEngine(swapEngine) - floatingSpecializedBondASW1 = ql.AssetSwap( - payFixedRate, - floatingSpecializedBond1, - floatingBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingSpecializedBondASW1.setPricingEngine(swapEngine) - floatingBondASWSpread1 = floatingBondASW1.fairSpread() - floatingSpecializedBondASWSpread1 = floatingSpecializedBondASW1.fairSpread() - error6 = abs(floatingBondASWSpread1 - floatingSpecializedBondASWSpread1) - self.assertFalse( - error6 > tolerance, - "wrong asw spread for fixed bond:" - + "\n generic frn rate bond's asw spread: " - + str(floatingBondASWSpread1) - + "\n equivalent specialized bond's asw spread: " - + str(floatingSpecializedBondASWSpread1) - + "\n error: " - + str(error6) - + "\n tolerance: " - + str(tolerance), - ) - - ##FRN bond (Isin: XS0090566539 COE 0 09/24/18) - ##maturity occurs on a business day - floatingBondStartDate2 = ql.Date(24, ql.September, 2004) - floatingBondMaturityDate2 = ql.Date(24, ql.September, 2018) - floatingBondSchedule2 = ql.Schedule( - floatingBondStartDate2, - floatingBondMaturityDate2, - ql.Period(ql.Semiannual), - bondCalendar, - ql.ModifiedFollowing, - ql.ModifiedFollowing, - ql.DateGeneration.Backward, - False, - ) - floatingBondLeg2 = list( - ql.IborLeg( - [self.faceAmount], - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - [fixingDays], - [], - [0.0025], - [], - [], - inArrears, - ) - ) - floatingbondRedemption2 = bondCalendar.adjust(floatingBondMaturityDate2, ql.ModifiedFollowing) - floatingBondLeg2.append(ql.SimpleCashFlow(100.0, floatingbondRedemption2)) - ## generic bond - floatingBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - floatingBondMaturityDate2, - floatingBondStartDate2, - floatingBondLeg2, - ) - floatingBond2.setPricingEngine(bondEngine) - - ## equivalent specialized floater - floatingSpecializedBond2 = ql.FloatingRateBond( - settlementDays, - self.faceAmount, - floatingBondSchedule2, - self.iborIndex, - ql.Actual360(), - ql.ModifiedFollowing, - fixingDays, - [1], - [0.0025], - [], - [], - inArrears, - 100.0, - ql.Date(24, ql.September, 2004), - ) - floatingSpecializedBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(floatingBond2.cashflows(), self.pricer) - ql.setCouponPricer(floatingSpecializedBond2.cashflows(), self.pricer) - - self.iborIndex.addFixing(ql.Date(22, ql.March, 2007), 0.04013) - - floatingBondPrice2 = floatingBond2.cleanPrice() - floatingSpecializedBondPrice2 = floatingSpecializedBond2.cleanPrice() - floatingBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondAssetSwap2.setPricingEngine(swapEngine) - floatingSpecializedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - floatingSpecializedBond2, - floatingSpecializedBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingSpecializedBondAssetSwap2.setPricingEngine(swapEngine) - floatingBondAssetSwapPrice2 = floatingBondAssetSwap2.fairCleanPrice() - floatingSpecializedBondAssetSwapPrice2 = floatingSpecializedBondAssetSwap2.fairCleanPrice() - error7 = abs(floatingBondAssetSwapPrice2 - floatingSpecializedBondAssetSwapPrice2) - self.assertFalse( - error7 > tolerance, - "wrong clean price for frnbond:" - + "\n generic frn rate bond's clean price: " - + str(floatingBondAssetSwapPrice2) - + "\n equivalent specialized frn bond's price: " - + str(floatingSpecializedBondAssetSwapPrice2) - + "\n error: " - + str(error7) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - floatingBondMktPrice2 = 101.26 - floatingBondASW2 = ql.AssetSwap( - payFixedRate, - floatingBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingBondASW2.setPricingEngine(swapEngine) - floatingSpecializedBondASW2 = ql.AssetSwap( - payFixedRate, - floatingSpecializedBond2, - floatingBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - floatingSpecializedBondASW2.setPricingEngine(swapEngine) - floatingBondASWSpread2 = floatingBondASW2.fairSpread() - floatingSpecializedBondASWSpread2 = floatingSpecializedBondASW2.fairSpread() - error8 = abs(floatingBondASWSpread2 - floatingSpecializedBondASWSpread2) - self.assertFalse( - error8 > tolerance, - "wrong asw spread for frn bond:" - + "\n generic frn rate bond's asw spread: " - + str(floatingBondASWSpread2) - + "\n equivalent specialized bond's asw spread: " - + str(floatingSpecializedBondASWSpread2) - + "\n error: " - + str(error8) - + "\n tolerance: " - + str(tolerance), - ) - - ## CMS bond (Isin: XS0228052402 CRDIT 0 8/22/20) - ## maturity doesn't occur on a business day - - cmsBondStartDate1 = ql.Date(22, ql.August, 2005) - cmsBondMaturityDate1 = ql.Date(22, ql.August, 2020) - cmsBondSchedule1 = ql.Schedule( - cmsBondStartDate1, - cmsBondMaturityDate1, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg1 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [], - [], - [0.055], - [0.025], - inArrears, - ) - ) - cmsbondRedemption1 = bondCalendar.adjust(cmsBondMaturityDate1, ql.Following) - cmsBondLeg1.append(ql.SimpleCashFlow(100.0, cmsbondRedemption1)) - ## generic cms bond - cmsBond1 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate1, cmsBondStartDate1, cmsBondLeg1 - ) - cmsBond1.setPricingEngine(bondEngine) - - ## equivalent specialized cms bond - cmsSpecializedBond1 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule1, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [1.0], - [0.0], - [0.055], - [0.025], - inArrears, - 100.0, - ql.Date(22, ql.August, 2005), - ) - cmsSpecializedBond1.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond1.cashflows(), self.cmspricer) - ql.setCouponPricer(cmsSpecializedBond1.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(18, ql.August, 2006), 0.04158) - cmsBondPrice1 = cmsBond1.cleanPrice() - cmsSpecializedBondPrice1 = cmsSpecializedBond1.cleanPrice() - cmsBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap1.setPricingEngine(swapEngine) - cmsSpecializedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - cmsSpecializedBond1, - cmsSpecializedBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsSpecializedBondAssetSwap1.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice1 = cmsBondAssetSwap1.fairCleanPrice() - cmsSpecializedBondAssetSwapPrice1 = cmsSpecializedBondAssetSwap1.fairCleanPrice() - error9 = abs(cmsBondAssetSwapPrice1 - cmsSpecializedBondAssetSwapPrice1) - self.assertFalse( - error9 > tolerance, - "wrong clean price for cmsbond:" - + "\n generic bond's clean price: " - + str(cmsBondAssetSwapPrice1) - + "\n equivalent specialized cms rate bond's price: " - + str(cmsSpecializedBondAssetSwapPrice1) - + "\n error: " - + str(error9) - + "\n tolerance: " - + str(tolerance), - ) - - cmsBondMktPrice1 = 87.02 ## market executable price as of 4th sept 2007 - cmsBondASW1 = ql.AssetSwap( - payFixedRate, - cmsBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondASW1.setPricingEngine(swapEngine) - cmsSpecializedBondASW1 = ql.AssetSwap( - payFixedRate, - cmsSpecializedBond1, - cmsBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsSpecializedBondASW1.setPricingEngine(swapEngine) - cmsBondASWSpread1 = cmsBondASW1.fairSpread() - cmsSpecializedBondASWSpread1 = cmsSpecializedBondASW1.fairSpread() - error10 = abs(cmsBondASWSpread1 - cmsSpecializedBondASWSpread1) - self.assertFalse( - error10 > tolerance, - "wrong asw spread for cm bond:" - + "\n generic cms rate bond's asw spread: " - + str(cmsBondASWSpread1) - + "\n equivalent specialized bond's asw spread: " - + str(cmsSpecializedBondASWSpread1) - + "\n error: " - + str(error10) - + "\n tolerance: " - + str(tolerance), - ) - - ##CMS bond (Isin: XS0218766664 ISPIM 0 5/6/15) - ##maturity occurs on a business day - cmsBondStartDate2 = ql.Date(6, ql.May, 2005) - cmsBondMaturityDate2 = ql.Date(6, ql.May, 2015) - cmsBondSchedule2 = ql.Schedule( - cmsBondStartDate2, - cmsBondMaturityDate2, - ql.Period(ql.Annual), - bondCalendar, - ql.Unadjusted, - ql.Unadjusted, - ql.DateGeneration.Backward, - False, - ) - cmsBondLeg2 = list( - ql.CmsLeg( - [self.faceAmount], - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - [fixingDays], - [0.84], - [], - [], - [], - inArrears, - ) - ) - cmsbondRedemption2 = bondCalendar.adjust(cmsBondMaturityDate2, ql.Following) - cmsBondLeg2.append(ql.SimpleCashFlow(100.0, cmsbondRedemption2)) - ## generic bond - cmsBond2 = ql.Bond( - settlementDays, bondCalendar, self.faceAmount, cmsBondMaturityDate2, cmsBondStartDate2, cmsBondLeg2 - ) - cmsBond2.setPricingEngine(bondEngine) - - ## equivalent specialized cms bond - cmsSpecializedBond2 = ql.CmsRateBond( - settlementDays, - self.faceAmount, - cmsBondSchedule2, - self.swapIndex, - ql.Thirty360(ql.Thirty360.BondBasis), - ql.Following, - fixingDays, - [0.84], - [0.0], - [], - [], - inArrears, - 100.0, - ql.Date(6, ql.May, 2005), - ) - cmsSpecializedBond2.setPricingEngine(bondEngine) - - ql.setCouponPricer(cmsBond2.cashflows(), self.cmspricer) - ql.setCouponPricer(cmsSpecializedBond2.cashflows(), self.cmspricer) - self.swapIndex.addFixing(ql.Date(4, ql.May, 2006), 0.04217) - cmsBondPrice2 = cmsBond2.cleanPrice() - cmsSpecializedBondPrice2 = cmsSpecializedBond2.cleanPrice() - cmsBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondAssetSwap2.setPricingEngine(swapEngine) - cmsSpecializedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - cmsSpecializedBond2, - cmsSpecializedBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsSpecializedBondAssetSwap2.setPricingEngine(swapEngine) - cmsBondAssetSwapPrice2 = cmsBondAssetSwap2.fairCleanPrice() - cmsSpecializedBondAssetSwapPrice2 = cmsSpecializedBondAssetSwap2.fairCleanPrice() - error11 = abs(cmsBondAssetSwapPrice2 - cmsSpecializedBondAssetSwapPrice2) - self.assertFalse( - error11 > tolerance, - "wrong clean price for cmsbond:" - + "\n generic bond's clean price: " - + str(cmsBondAssetSwapPrice2) - + "\n equivalent specialized cms rate bond's price: " - + str(cmsSpecializedBondAssetSwapPrice2) - + "\n error: " - + str(error11) - + "\n tolerance: " - + str(tolerance), - ) - - cmsBondMktPrice2 = 94.35 ## market executable price as of 4th sept 2007 - cmsBondASW2 = ql.AssetSwap( - payFixedRate, - cmsBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsBondASW2.setPricingEngine(swapEngine) - cmsSpecializedBondASW2 = ql.AssetSwap( - payFixedRate, - cmsSpecializedBond2, - cmsBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - cmsSpecializedBondASW2.setPricingEngine(swapEngine) - cmsBondASWSpread2 = cmsBondASW2.fairSpread() - cmsSpecializedBondASWSpread2 = cmsSpecializedBondASW2.fairSpread() - error12 = abs(cmsBondASWSpread2 - cmsSpecializedBondASWSpread2) - self.assertFalse( - error12 > tolerance, - "wrong asw spread for cm bond:" - + "\n generic cms rate bond's asw spread: " - + str(cmsBondASWSpread2) - + "\n equivalent specialized bond's asw spread: " - + str(cmsSpecializedBondASWSpread2) - + "\n error: " - + str(error12) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero-Coupon bond (Isin: DE0004771662 IBRD 0 12/20/15) - ## maturity doesn't occur on a business day - zeroCpnBondStartDate1 = ql.Date(19, ql.December, 1985) - zeroCpnBondMaturityDate1 = ql.Date(20, ql.December, 2015) - zeroCpnBondRedemption1 = bondCalendar.adjust(zeroCpnBondMaturityDate1, ql.Following) - zeroCpnBondLeg1 = ql.Leg([ql.SimpleCashFlow(100.0, zeroCpnBondRedemption1)]) - ## generic bond - zeroCpnBond1 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate1, - zeroCpnBondStartDate1, - zeroCpnBondLeg1, - ) - zeroCpnBond1.setPricingEngine(bondEngine) - - ## specialized zerocpn bond - zeroCpnSpecializedBond1 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(20, ql.December, 2015), - ql.Following, - 100.0, - ql.Date(19, ql.December, 1985), - ) - zeroCpnSpecializedBond1.setPricingEngine(bondEngine) - - zeroCpnBondPrice1 = zeroCpnBond1.cleanPrice() - zeroCpnSpecializedBondPrice1 = zeroCpnSpecializedBond1.cleanPrice() - zeroCpnBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondAssetSwap1.setPricingEngine(swapEngine) - zeroCpnSpecializedBondAssetSwap1 = ql.AssetSwap( - payFixedRate, - zeroCpnSpecializedBond1, - zeroCpnSpecializedBondPrice1, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnSpecializedBondAssetSwap1.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice1 = zeroCpnBondAssetSwap1.fairCleanPrice() - zeroCpnSpecializedBondAssetSwapPrice1 = zeroCpnSpecializedBondAssetSwap1.fairCleanPrice() - error13 = abs(zeroCpnBondAssetSwapPrice1 - zeroCpnSpecializedBondAssetSwapPrice1) - self.assertFalse( - error13 > tolerance, - "wrong clean price for zerocpn bond:" - + "\n generic zero cpn bond's clean price: " - + str(zeroCpnBondAssetSwapPrice1) - + "\n specialized equivalent bond's price: " - + str(zeroCpnSpecializedBondAssetSwapPrice1) - + "\n error: " - + str(error13) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - zeroCpnBondMktPrice1 = 72.277 - zeroCpnBondASW1 = ql.AssetSwap( - payFixedRate, - zeroCpnBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondASW1.setPricingEngine(swapEngine) - zeroCpnSpecializedBondASW1 = ql.AssetSwap( - payFixedRate, - zeroCpnSpecializedBond1, - zeroCpnBondMktPrice1, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnSpecializedBondASW1.setPricingEngine(swapEngine) - zeroCpnBondASWSpread1 = zeroCpnBondASW1.fairSpread() - zeroCpnSpecializedBondASWSpread1 = zeroCpnSpecializedBondASW1.fairSpread() - error14 = abs(zeroCpnBondASWSpread1 - zeroCpnSpecializedBondASWSpread1) - self.assertFalse( - error14 > tolerance, - "wrong asw spread for zeroCpn bond:" - + "\n generic zeroCpn bond's asw spread: " - + str(zeroCpnBondASWSpread1) - + "\n equivalent specialized bond's asw spread: " - + str(zeroCpnSpecializedBondASWSpread1) - + "\n error: " - + str(error14) - + "\n tolerance: " - + str(tolerance), - ) - - ## Zero Coupon bond (Isin: IT0001200390 ISPIM 0 02/17/28) - ## maturity doesn't occur on a business day - zeroCpnBondStartDate2 = ql.Date(17, ql.February, 1998) - zeroCpnBondMaturityDate2 = ql.Date(17, ql.February, 2028) - zerocpbondRedemption2 = bondCalendar.adjust(zeroCpnBondMaturityDate2, ql.Following) - zeroCpnBondLeg2 = ql.Leg([ql.SimpleCashFlow(100.0, zerocpbondRedemption2)]) - ## generic bond - zeroCpnBond2 = ql.Bond( - settlementDays, - bondCalendar, - self.faceAmount, - zeroCpnBondMaturityDate2, - zeroCpnBondStartDate2, - zeroCpnBondLeg2, - ) - zeroCpnBond2.setPricingEngine(bondEngine) - - ## specialized zerocpn bond - zeroCpnSpecializedBond2 = ql.ZeroCouponBond( - settlementDays, - bondCalendar, - self.faceAmount, - ql.Date(17, ql.February, 2028), - ql.Following, - 100.0, - ql.Date(17, ql.February, 1998), - ) - zeroCpnSpecializedBond2.setPricingEngine(bondEngine) - - zeroCpnBondPrice2 = zeroCpnBond2.cleanPrice() - zeroCpnSpecializedBondPrice2 = zeroCpnSpecializedBond2.cleanPrice() - - zeroCpnBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondAssetSwap2.setPricingEngine(swapEngine) - zeroCpnSpecializedBondAssetSwap2 = ql.AssetSwap( - payFixedRate, - zeroCpnSpecializedBond2, - zeroCpnSpecializedBondPrice2, - self.iborIndex, - self.nonnullspread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnSpecializedBondAssetSwap2.setPricingEngine(swapEngine) - zeroCpnBondAssetSwapPrice2 = zeroCpnBondAssetSwap2.fairCleanPrice() - zeroCpnSpecializedBondAssetSwapPrice2 = zeroCpnSpecializedBondAssetSwap2.fairCleanPrice() - error15 = abs(zeroCpnBondAssetSwapPrice2 - zeroCpnSpecializedBondAssetSwapPrice2) - self.assertFalse( - error8 > tolerance, - "wrong clean price for zerocpn bond:" - + "\n generic zero cpn bond's clean price: " - + str(zeroCpnBondAssetSwapPrice2) - + "\n equivalent specialized bond's price: " - + str(zeroCpnSpecializedBondAssetSwapPrice2) - + "\n error: " - + str(error15) - + "\n tolerance: " - + str(tolerance), - ) - - ## market executable price as of 4th sept 2007 - zeroCpnBondMktPrice2 = 72.277 - zeroCpnBondASW2 = ql.AssetSwap( - payFixedRate, - zeroCpnBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnBondASW2.setPricingEngine(swapEngine) - zeroCpnSpecializedBondASW2 = ql.AssetSwap( - payFixedRate, - zeroCpnSpecializedBond2, - zeroCpnBondMktPrice2, - self.iborIndex, - self.spread, - ql.Schedule(), - self.iborIndex.dayCounter(), - parAssetSwap, - ) - zeroCpnSpecializedBondASW2.setPricingEngine(swapEngine) - zeroCpnBondASWSpread2 = zeroCpnBondASW2.fairSpread() - zeroCpnSpecializedBondASWSpread2 = zeroCpnSpecializedBondASW2.fairSpread() - error16 = abs(zeroCpnBondASWSpread2 - zeroCpnSpecializedBondASWSpread2) - self.assertFalse( - error16 > tolerance, - "wrong asw spread for zeroCpn bond:" - + "\n generic zeroCpn bond's asw spread: " - + str(zeroCpnBondASWSpread2) - + "\n equivalent specialized bond's asw spread: " - + str(zeroCpnSpecializedBondASWSpread2) - + "\n error: " - + str(error16) - + "\n tolerance: " - + str(tolerance), - ) - - -if __name__ == "__main__": - print("testing QuantLib", ql.__version__) - unittest.main(verbosity=2) diff --git a/Python/test/test_basket_option.py b/Python/test/test_basket_option.py new file mode 100644 index 00000000..9b7648ac --- /dev/null +++ b/Python/test/test_basket_option.py @@ -0,0 +1,174 @@ +""" + Copyright (C) 2024 Klaus Spanderen + + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +""" + +import unittest + +import QuantLib_Risks as ql + + +class BasketOptionTest(unittest.TestCase): + def setUp(self): + self.todaysDate = ql.Date(26, ql.October, 2024) + ql.Settings.instance().evaluationDate = self.todaysDate + + def tearDown(self): + ql.Settings.instance().evaluationDate = ql.Date() + + def testThreeAssetSpreadOption(self): + """Testing three asset spread option""" + + def build_process(s: float, q: float, v: float) -> ql.BlackScholesMertonProcess: + return ql.BlackScholesMertonProcess( + ql.QuoteHandle(ql.SimpleQuote(s)), + ql.YieldTermStructureHandle( + ql.FlatForward(self.todaysDate, q, ql.Actual365Fixed()) + ), + ql.YieldTermStructureHandle( + ql.FlatForward(self.todaysDate, 0.05, ql.Actual365Fixed()) + ), + ql.BlackVolTermStructureHandle( + ql.BlackConstantVol( + self.todaysDate, ql.TARGET(), v, ql.Actual365Fixed() + ) + ), + ) + + processes = [ + build_process(100, 0.05, 0.3), + build_process(50, 0.07, 0.45), + build_process(50, 0.025, 0.2), + ] + + processes_vector = ql.GeneralizedBlackScholesProcessVector(processes) + + rho = ql.Matrix([[1.0, 0.2, -0.1], [0.2, 1.0, -0.3], [-0.1, -0.3, 1.0]]) + + exercise = ql.EuropeanExercise(self.todaysDate + ql.Period(1, ql.Years)) + payoff = ql.PlainVanillaPayoff(ql.Option.Call, 2.0) + + basket_option = ql.BasketOption( + ql.AverageBasketPayoff(payoff, ql.Array([1, -1, -1])), exercise + ) + + expected = 11.932739641 + + basket_option.setPricingEngine(ql.ChoiBasketEngine(processes_vector, rho, 10)) + self.assertAlmostEqual(basket_option.NPV(), expected) + + basket_option.setPricingEngine(ql.DengLiZhouBasketEngine(processes_vector, rho)) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine( + ql.MCEuropeanBasketEngine( + ql.StochasticProcessArray(processes, rho), + "lowdiscrepancy", + timeSteps=1, + requiredTolerance=0.1, + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine( + ql.FdndimBlackScholesVanillaEngine( + processes_vector, rho, ql.UnsignedIntVector([25, 15, 15]), 15 + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + def testTwoAssetSpreadOption(self): + """Testing two asset spread option""" + + def build_process(s: float, v: float) -> ql.BlackProcess: + return ql.BlackProcess( + ql.QuoteHandle(ql.SimpleQuote(s)), + ql.YieldTermStructureHandle( + ql.FlatForward(self.todaysDate, 0.05, ql.Actual365Fixed()) + ), + ql.BlackVolTermStructureHandle( + ql.BlackConstantVol( + self.todaysDate, ql.TARGET(), v, ql.Actual365Fixed() + ) + ), + ) + + p1 = build_process(100, 0.3) + p2 = build_process(90, 0.45) + rho = -0.75 + rho_m = ql.Matrix([[1, rho], [rho, 1]]) + + processes_vector = ql.GeneralizedBlackScholesProcessVector([p1, p2]) + + exercise = ql.EuropeanExercise(self.todaysDate + ql.Period(6, ql.Months)) + payoff = ql.PlainVanillaPayoff(ql.Option.Put, 10.0) + basket_option = ql.BasketOption(ql.SpreadBasketPayoff(payoff), exercise) + + expected = 17.96241322097977 + + basket_option.setPricingEngine(ql.ChoiBasketEngine(processes_vector, rho_m, 15)) + self.assertAlmostEqual(basket_option.NPV(), expected, 10) + + basket_option.setPricingEngine( + ql.DengLiZhouBasketEngine(processes_vector, rho_m) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 4) + + basket_option.setPricingEngine(ql.KirkEngine(p1, p2, rho)) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine(ql.BjerksundStenslandSpreadEngine(p1, p2, rho)) + self.assertAlmostEqual(basket_option.NPV(), expected, 2) + + basket_option.setPricingEngine( + ql.OperatorSplittingSpreadEngine( + p1, p2, rho, ql.OperatorSplittingSpreadEngine.First + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine( + ql.OperatorSplittingSpreadEngine( + p1, p2, rho, ql.OperatorSplittingSpreadEngine.Second + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 2) + + basket_option.setPricingEngine( + ql.FdndimBlackScholesVanillaEngine( + processes_vector, rho_m, ql.UnsignedIntVector([25, 25]), 15 + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine( + ql.Fd2dBlackScholesVanillaEngine(p1, p2, rho, xGrid=25, yGrid=25, tGrid=15) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + basket_option.setPricingEngine( + ql.MCEuropeanBasketEngine( + ql.StochasticProcessArray([p1, p2], rho_m), + "lowdiscrepancy", + timeSteps=1, + requiredTolerance=0.1, + ) + ) + self.assertAlmostEqual(basket_option.NPV(), expected, 1) + + +if __name__ == "__main__": + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) diff --git a/Python/test/test_blackformula.py b/Python/test/test_blackformula.py index c3299367..27e8404b 100644 --- a/Python/test/test_blackformula.py +++ b/Python/test/test_blackformula.py @@ -31,12 +31,8 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ import unittest +import math import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math - class BlackFormulaTest(unittest.TestCase): diff --git a/Python/test/test_bondfunctions.py b/Python/test/test_bondfunctions.py index b02aa218..6f5f28c6 100644 --- a/Python/test/test_bondfunctions.py +++ b/Python/test/test_bondfunctions.py @@ -185,12 +185,15 @@ def testCleanPrice(self): def testAtmRate(self): """ Testing BondFunctions atmRate. """ self.assertEqual(round(ql.BondFunctions.atmRate(self.bond, self.flat_forward, - self.settlement_date, 99.94475138121548), 4), + self.settlement_date, + ql.BondPrice(99.94475138121548, ql.BondPrice.Clean)), 4), 0.05) def testBondYield(self): """ Testing BondFunctions bondYield. """ - self.assertEqual(round(ql.BondFunctions.bondYield(self.bond, 110, self.day_counter, ql.Compounded, ql.Annual), 8), + self.assertEqual(round(ql.BondFunctions.bondYield(self.bond, + ql.BondPrice(110, ql.BondPrice.Clean), + self.day_counter, ql.Compounded, ql.Annual), 8), 0.03582431) def testDuration(self): @@ -237,7 +240,9 @@ def testYieldValueBasisPoint(self): def testZSpread(self): """ Testing BondFunctions zSpread. """ - self.assertEqual(round(ql.BondFunctions.zSpread(self.bond, 87.5, self.flat_forward, + self.assertEqual(round(ql.BondFunctions.zSpread(self.bond, + ql.BondPrice(87.5, ql.BondPrice.Clean), + self.flat_forward, self.day_counter, ql.Compounded, ql.Annual), 8), 0.02125053) diff --git a/Python/test/test_calendars.py b/Python/test/test_calendars.py index 4427afed..60c4f5d4 100644 --- a/Python/test/test_calendars.py +++ b/Python/test/test_calendars.py @@ -44,18 +44,29 @@ def test_joint_calendar_holidays(self): end_date = ql.Date(31, ql.December, 2023) joint_holidays = set(joint_nordics.holidayList(start_date, end_date)) - base_holidays = [calendar.holidayList(start_date, end_date) for calendar in base_calendars] - base_holidays = set(itertools.chain.from_iterable(base_holidays)) - for holiday in base_holidays: - self.assertIn(holiday, joint_holidays) - - -class ResetBespokeCalendarTest(unittest.TestCase): + base_holidays = set(itertools.chain.from_iterable( + calendar.holidayList(start_date, end_date) for calendar in base_calendars + )) + self.assertEqual(joint_holidays, base_holidays) + + +class BespokeCalendarTest(unittest.TestCase): + + def test_hash(self): + empty1, empty2 = ql.CalendarVector(2) + for cal1 in (ql.BespokeCalendar("one"), ql.BespokeCalendar("two"), empty1): + for cal2 in (ql.BespokeCalendar("one"), ql.BespokeCalendar("two"), empty2): + if cal1.empty() or cal2.empty(): + expected = cal1.empty() == cal2.empty() + else: + expected = cal1.name() == cal2.name() + self.assertEqual(cal1 == cal2, expected) + self.assertEqual(cal1 != cal2, not expected) + self.assertEqual(hash(cal1) == hash(cal2), expected) def test_reset_added_holidays(self): calendar = ql.BespokeCalendar("bespoke thing") - - test_date: ql.Date = ql.Date(1, ql.January, 2024) + test_date = ql.Date(1, ql.January, 2024) self.assertFalse(calendar.isHoliday(test_date)) calendar.addHoliday(test_date) self.assertTrue(calendar.isHoliday(test_date)) diff --git a/Python/test/test_capfloor.py b/Python/test/test_capfloor.py index 35ad6a29..28400f6c 100644 --- a/Python/test/test_capfloor.py +++ b/Python/test/test_capfloor.py @@ -81,7 +81,7 @@ def setUp(self): self.cap = ql.Cap(self.ibor_leg, [self.strike]) self.cap_npv = 8.54 - self.black_vol = ql.QuoteHandle(ql.SimpleQuote(0.6)) + self.black_vol = ql.makeQuoteHandle(0.6) def tearDown(self): ql.Settings.instance().evaluationDate = ql.Date() @@ -109,8 +109,7 @@ def testBachelierCapFloorEngine(self): bpvol = self.black_vol.value() * self.flat_forward_rate bachelier_engine = ql.BachelierCapFloorEngine(self.term_structure_handle, - ql.QuoteHandle( - ql.SimpleQuote(bpvol))) + ql.makeQuoteHandle(bpvol)) self.cap.setPricingEngine(bachelier_engine) diff --git a/Python/test/test_cms.py b/Python/test/test_cms.py index 17214413..d29efc94 100644 --- a/Python/test/test_cms.py +++ b/Python/test/test_cms.py @@ -42,7 +42,7 @@ def setUp(self): ql.Settings.instance().evaluationDate = self.referenceDate self.termStructure = ql.RelinkableYieldTermStructureHandle() self.termStructure.linkTo( - ql.FlatForward(self.referenceDate, ql.QuoteHandle(ql.SimpleQuote(0.05)), ql.Actual365Fixed()) + ql.FlatForward(self.referenceDate, ql.makeQuoteHandle(0.05), ql.Actual365Fixed()) ) self.yieldCurveModels = [] self.numericalPricers = [] @@ -110,7 +110,7 @@ def setUp(self): for i in range(self.nRows): self.volSpreadsRow = [] for j in range(self.nCols): - self.volSpreadsRow.append(ql.QuoteHandle(ql.SimpleQuote(self.volSpreadsMatrix[i][j]))) + self.volSpreadsRow.append(ql.makeQuoteHandle(self.volSpreadsMatrix[i][j])) self.volSpreads.append(self.volSpreadsRow) self.iborIndex = ql.Euribor6M(self.termStructure) @@ -148,7 +148,7 @@ def setUp(self): for i in range(self.nRows): self.guessRow = [] for j in range(4): - self.guessRow.append(ql.QuoteHandle(ql.SimpleQuote(self.guessMatrix[i][j]))) + self.guessRow.append(ql.makeQuoteHandle(self.guessMatrix[i][j])) self.guess.append(self.guessRow) self.isParameterFixed = [False, True, False, False] @@ -179,7 +179,7 @@ def setUp(self): ql.GFunctionFactory.NonParallelShifts, ] - self.zeroMeanRev = ql.QuoteHandle(ql.SimpleQuote(0.0)) + self.zeroMeanRev = ql.makeQuoteHandle(0.0) self.numericalPricers = {} self.analyticPricers = {} diff --git a/Python/test/test_coupons.py b/Python/test/test_coupons.py index 52a3534e..2bbd3a69 100644 --- a/Python/test/test_coupons.py +++ b/Python/test/test_coupons.py @@ -1,29 +1,14 @@ """ Copyright (C) 2021 Marcin Rybacki - Copyright (C) 2024 Xcelerit Computing Limited. - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -46,7 +31,7 @@ def flat_rate(rate): return ql.FlatForward( - 2, CAL, ql.QuoteHandle(ql.SimpleQuote(rate)), ql.Actual365Fixed()) + 2, CAL, ql.makeQuoteHandle(rate), ql.Actual365Fixed()) def create_ibor_leg(ibor_idx, start, end, payment_lag=0): diff --git a/Python/test/test_currencies.py b/Python/test/test_currencies.py index 3826d9c6..8d724673 100644 --- a/Python/test/test_currencies.py +++ b/Python/test/test_currencies.py @@ -52,9 +52,20 @@ def test_bespoke_currency_constructor(self): """Testing bespoke currency constructor""" fail_msg = "Failed to create bespoke currency." custom_ccy = ql.Currency( - "CCY", "CCY", 100, "#", "", 100, ql.Rounding(), "") + "CCY", "CCY", 100, "#", "", 100, ql.Rounding()) self.assertFalse(custom_ccy.empty(), fail_msg) + def test_hash(self): + for ccy1 in (ql.EURCurrency(), ql.USDCurrency(), ql.Currency()): + for ccy2 in (ql.EURCurrency(), ql.USDCurrency(), ql.Currency()): + if ccy1.empty() or ccy2.empty(): + expected = ccy1.empty() == ccy2.empty() + else: + expected = ccy1.name() == ccy2.name() + self.assertEqual(ccy1 == ccy2, expected) + self.assertEqual(ccy1 != ccy2, not expected) + self.assertEqual(hash(ccy1) == hash(ccy2), expected) + if __name__ == '__main__': print("testing QuantLib", ql.__version__) diff --git a/Python/test/test_date.py b/Python/test/test_date.py index 087ca5c1..a6060a6b 100644 --- a/Python/test/test_date.py +++ b/Python/test/test_date.py @@ -30,14 +30,13 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ +import datetime +import operator import QuantLib_Risks as ql import unittest class DateTest(unittest.TestCase): - def setUp(self): - pass - def testArithmetics(self): "Testing date arithmetics" today = ql.Date.todaysDate() @@ -74,15 +73,58 @@ def testArithmetics(self): mold = m yold = y + def test_hash(self): + for date1 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()): + for date2 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()): + expected = str(date1) == str(date2) + self.assertEqual(date1 == date2, expected) + self.assertEqual(date1 != date2, not expected) + self.assertEqual(hash(date1) == hash(date2), expected) + + def test_order(self): + ops = [operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge] + for date1 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()): + for date2 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()): + for op in ops: + self.assertEqual(op(date1, date2), + op(date1.serialNumber(), date2.serialNumber())) + def testHolidayList(self): """ Testing Calendar testHolidayList() method. """ holidayLstFunction = ql.Calendar.holidayList(ql.Poland(), ql.Date(31, 12, 2014), ql.Date(3, 4, 2015), False) holidayLstManual = (ql.Date(1, 1, 2015), ql.Date(6, 1, 2015)) - # check if dates both from function and from manual imput are the same + # check if dates both from function and from manual input are the same self.assertTrue(all([(a == b) for a, b in zip(holidayLstFunction, holidayLstManual)])) - def tearDown(self): - pass + def testConversion(self): + for m in range(1, 13): + ql_date = ql.Date(m * 2, m, 2020) + py_date = datetime.date(2020, m, m * 2) + self.assertEqual(ql_date.to_date(), py_date) + self.assertEqual(ql.Date.from_date(py_date), ql_date) + # datetime works as well + py_dt = datetime.datetime(2020, m, m * 2) + self.assertEqual(ql.Date.from_date(py_dt), ql_date) + + with self.assertRaisesRegex(RuntimeError, "from_date requires a date"): + ql.Date.from_date("2020-01-02") + + +class PeriodTest(unittest.TestCase): + def test_hash(self): + for per1 in (ql.Period("1D"), ql.Period("1W"), ql.Period("12M"), ql.Period()): + for per2 in (ql.Period("1D"), ql.Period("1Y"), ql.Period()): + expected = str(per1.normalized()) == str(per2.normalized()) + self.assertEqual(per1 == per2, expected) + self.assertEqual(per1 != per2, not expected) + self.assertEqual(hash(per1) == hash(per2), expected) + + def test_order(self): + ops = [operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge] + for per1 in (ql.Period("1D"), ql.Period("1W"), ql.Period("12M")): + for per2 in (ql.Period("1D"), ql.Period("1Y")): + for op in ops: + self.assertEqual(op(per1, per2), op(per2.frequency(), per1.frequency())) if __name__ == "__main__": diff --git a/Python/test/test_daycounters.py b/Python/test/test_daycounters.py index 312dcfbd..b92261ee 100644 --- a/Python/test/test_daycounters.py +++ b/Python/test/test_daycounters.py @@ -1,54 +1,33 @@ -""" - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - - QuantLib is free software: you can redistribute it and/or modify it - under the terms of the QuantLib license. You should have received a - copy of the license along with this program; if not, please email - . The license is also available online at - . - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the license for more details. -""" - -import QuantLib_Risks as ql -import unittest - - -class DayCountersTest(unittest.TestCase): - def test_bus252(self): - """Test Business252 daycounter""" - - calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond) - - # - # Check that SWIG signature for Business252 calendar allows to - # pass custom calendar into the class constructor. Old - # QuantLib-SWIG versions allow only to create Business252 - # calendar with default constructor parameter (Brazil - # calendar), and generate an exception when trying to pass a - # custom calendar as a parameter. So we just check here that - # no exception occurs. - # - ql.Business252(calendar) - - -if __name__ == "__main__": - print("testing QuantLib", ql.__version__) - unittest.main(verbosity=2) +import QuantLib_Risks as ql +import unittest + + +class DayCountersTest(unittest.TestCase): + def test_bus252(self): + """Test Business252 daycounter""" + + calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond) + + # + # Check that SWIG signature for Business252 calendar allows to + # pass custom calendar into the class constructor. Old + # QuantLib-SWIG versions allow only to create Business252 + # calendar with default constructor parameter (Brazil + # calendar), and generate an exception when trying to pass a + # custom calendar as a parameter. So we just check here that + # no exception occurs. + # + ql.Business252(calendar) + + def test_hash(self): + for dc1 in (ql.Actual360(), ql.Thirty365()): + for dc2 in (ql.Actual360(), ql.Thirty365()): + expected = dc1.name() == dc2.name() + self.assertEqual(dc1 == dc2, expected) + self.assertEqual(dc1 != dc2, not expected) + self.assertEqual(hash(dc1) == hash(dc2), expected) + + +if __name__ == "__main__": + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) diff --git a/Python/test/test_equityindex.py b/Python/test/test_equityindex.py index 05f7c1e9..1143454e 100644 --- a/Python/test/test_equityindex.py +++ b/Python/test/test_equityindex.py @@ -1,29 +1,14 @@ """ Copyright (C) 2023 Marcin Rybacki - Copyright (C) 2024 Xcelerit Computing Limited. - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -43,7 +28,7 @@ def flat_rate(rate): return ql.FlatForward( - 2, CAL, ql.QuoteHandle(ql.SimpleQuote(rate)), DCT) + 2, CAL, ql.makeQuoteHandle(rate), DCT) class EquityIndexTest(unittest.TestCase): @@ -52,11 +37,11 @@ def setUp(self): self.interest_handle = ql.YieldTermStructureHandle(flat_rate(0.03)) self.dividend_handle = ql.YieldTermStructureHandle(flat_rate(0.01)) - spot_handle = ql.QuoteHandle(ql.SimpleQuote(8690.0)) + spot_handle = ql.makeQuoteHandle(8690.0) ql.IndexManager.instance().clearHistory("eq_idx") self.equity_idx = ql.EquityIndex( - "eq_idx", CAL, self.interest_handle, self.dividend_handle, spot_handle) + "eq_idx", CAL, ql.Currency(), self.interest_handle, self.dividend_handle, spot_handle) def test_equity_index_inspectors(self): """Testing equity index inspectors""" diff --git a/Python/test/test_extrapolation.py b/Python/test/test_extrapolation.py index 341f972c..1e5e4abf 100644 --- a/Python/test/test_extrapolation.py +++ b/Python/test/test_extrapolation.py @@ -30,13 +30,10 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ +import math import unittest import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math class ExtrapolationTest(unittest.TestCase): diff --git a/Python/test/test_fdm.py b/Python/test/test_fdm.py index aa3c2ed4..5923b166 100644 --- a/Python/test/test_fdm.py +++ b/Python/test/test_fdm.py @@ -30,13 +30,11 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ +import math import unittest import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math + class FdmTest(unittest.TestCase): def setUp(self): @@ -63,9 +61,9 @@ def test1dMesher(self): self.assertAlmostEqual(m.location(0), 0.0, 14) self.assertAlmostEqual(m.location(9), 1.0, 14) - p = list(x for x in m.locations() if ql.close_enough(x, 0.5)) + p = [x for x in m.locations() if ql.close_enough(x, 0.5)] self.assertEqual(len(p), 1) - p = list(x for x in m.locations() if ql.close_enough(x, 0.75)) + p = [x for x in m.locations() if ql.close_enough(x, 0.75)] self.assertEqual(len(p), 0) m = ql.Predefined1dMesher([0,2,4]) @@ -128,10 +126,10 @@ def testFdmMesherComposite(self): locations = m.locations(0) self.assertEqual(len(locations), 6) - self.assertEqual(list(map(lambda x: int(x+0.5), locations)), [0, 1, 0, 1, 0, 1]) + self.assertEqual(list(map(round, locations)), [0, 1, 0, 1, 0, 1]) locations = m.locations(1) - self.assertEqual(list(map(lambda x: int(x+0.5), locations)), [0, 0, 1, 1, 2, 2]) + self.assertEqual(list(map(round, locations)), [0, 0, 1, 1, 2, 2]) def testFdmLinearOpComposite(self): """Testing linear operator composites""" @@ -180,16 +178,16 @@ def preconditioner(self, r, s): self.assertAlmostEqual(foo.t2, 2.0, 14) r = ql.Array([1,2,3,4]) - self.assertEqual(list(c.apply(r)), list(2*r)) - self.assertEqual(list(c.apply_mixed(r)), list(3*r)) - self.assertEqual(list(c.apply_direction(7, r)), list(7*r)) + self.assertEqual(c.apply(r), 2*r) + self.assertEqual(c.apply_mixed(r), 3*r) + self.assertEqual(c.apply_direction(7, r), 7*r) - s = list(c.solve_splitting(7, r, 0.5)) + s = c.solve_splitting(7, r, 0.5) self.assertEqual(len(s), len(r)) for i, x in enumerate(s): self.assertAlmostEqual(x, 3.5*r[i], 14) - self.assertEqual(list(c.preconditioner(r, 4)), list(4*r)) + self.assertEqual(c.preconditioner(r, 4), 4*r) class Bar: @classmethod @@ -241,7 +239,7 @@ def testFdmBlackScholesOp(self): op.setTime(0, 0.1) - c = list(map(lambda x: payoff(math.exp(x)), mesher.locations(0))) + c = [payoff(math.exp(x)) for x in mesher.locations(0)] p = op.apply(c) e = [ 0.0, 0.0, 0.0, 0.0, 0.0, @@ -390,7 +388,7 @@ def testAmericanOptionPricing(self): exercise = ql.AmericanExercise(todaysDate, maturityDate) - spot = ql.QuoteHandle(ql.SimpleQuote(100.0)) + spot = ql.makeQuoteHandle(100.0) volatility = ql.BlackConstantVol(todaysDate, ql.TARGET(), 0.20, dc) process = ql.BlackScholesMertonProcess( @@ -539,7 +537,7 @@ def testBSMRNDCalculator(self): s0 = 100 process = ql.BlackScholesMertonProcess( - ql.QuoteHandle(ql.SimpleQuote(s0)), + ql.makeQuoteHandle(s0), ql.YieldTermStructureHandle( ql.FlatForward(todaysDate, q, dc)), ql.YieldTermStructureHandle( diff --git a/Python/test/test_futures.py b/Python/test/test_futures.py new file mode 100644 index 00000000..41871ec6 --- /dev/null +++ b/Python/test/test_futures.py @@ -0,0 +1,135 @@ +""" + Copyright (C) 2025 Hiroto Ogawa + + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +""" +import unittest +import math +import QuantLib_Risks as ql + +LAG = 0 +DC = ql.ActualActual(ql.ActualActual.ISDA) +CAL = ql.NullCalendar() + +def flat_rate(rate): + return ql.FlatForward( + LAG, CAL, ql.makeQuoteHandle(rate), DC) + +class PerpetualFuturesTest(unittest.TestCase): + def test_perpetual_futures(self): + """ Testing PerpetualFutures pricing by discounted cashflow method against analytic formulae. """ + val_date = ql.Date(20, 6, 2025) + ql.Settings.instance().evaluationDate = val_date + + test_dicts = [ + # Discrete time + {"payoff": ql.PerpetualFutures.Linear, "funding": ql.PerpetualFutures.FundingWithPreviousSpot, + "freq": ql.Period(3, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.01, "rateDiff": 0.005, "tol": 1.e-6}, + {"payoff": ql.PerpetualFutures.Linear, "funding": ql.PerpetualFutures.FundingWithCurrentSpot, + "freq": ql.Period(3, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.01, "rateDiff": 0.005, "tol": 1.e-6}, + {"payoff": ql.PerpetualFutures.Inverse, "funding": ql.PerpetualFutures.FundingWithPreviousSpot, + "freq": ql.Period(3, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.01, "rateDiff": 0.005, "tol": 1.e-6}, + {"payoff": ql.PerpetualFutures.Inverse, "funding": ql.PerpetualFutures.FundingWithCurrentSpot, + "freq": ql.Period(3, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.01, "rateDiff": 0.005, "tol": 1.e-6}, + # Continuous time + {"payoff": ql.PerpetualFutures.Linear, "funding": ql.PerpetualFutures.FundingWithPreviousSpot, + "freq": ql.Period(0, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.2, "rateDiff": 0.005, "tol": 1.e-6}, + {"payoff": ql.PerpetualFutures.Inverse, "funding": ql.PerpetualFutures.FundingWithPreviousSpot, + "freq": ql.Period(0, ql.Months), "spot": 10000., "domRate": 0.04, "btcYield": 0.02, + "fundingRate": 0.2, "rateDiff": 0.005, "tol": 1.e-6}, + ] + + for d in test_dicts: + pf = ql.PerpetualFutures(d["payoff"], d["funding"], d["freq"], CAL, DC) + domYC = ql.YieldTermStructureHandle(flat_rate(d["domRate"])) + btcYC = ql.YieldTermStructureHandle(flat_rate(d["btcYield"])) + spot = ql.QuoteHandle(ql.SimpleQuote(d["spot"])) + + fundingTimes = [0.0] + fundingRates = [d["fundingRate"]] + ir_diffs = [d["rateDiff"]] + engine = ql.DiscountingPerpetualFuturesEngine(domYC, btcYC, spot, fundingTimes, fundingRates, ir_diffs, + ql.DiscountingPerpetualFuturesEngine.PiecewiseConstant) + # pricing + pf.setPricingEngine(engine) + npv = pf.NPV() + + # analytic price + # for details, refer to + # Perpetual Futures Pricing, Damien Ackerer, Julien Hugonnier, Urban Jermann, 2024 + # https://finance.wharton.upenn.edu/~jermann/AHJ-main-10.pdf + period = d["freq"] + length = float(period.length()) + unit = period.units() + if unit == ql.Years: + dt = length + elif unit == ql.Months: + dt = length / 12. + elif unit == ql.Weeks: + dt = length * 7. / 365. + elif unit == ql.Days: + dt = length / 365. + elif unit == ql.Hours: + dt = length / 365. / 24. + elif unit == ql.Minutes: + dt = length / 365. / 24. / 60. + elif unit == ql.Seconds: + dt = length / 365. / 24. / 60. / 60. + else: + raise RuntimeError("unknown funding frequency unit: " + str(unit)) + + # Discrete time + if length > 0: + if d["payoff"] == ql.PerpetualFutures.Linear: + if d["funding"] == ql.PerpetualFutures.FundingWithPreviousSpot: + # Equation (12) in the above paper + expected = ( + d["spot"] * (d["fundingRate"] - d["rateDiff"]) * math.exp(d["btcYield"] * dt) / + (math.exp(d["btcYield"] * dt) - math.exp(d["domRate"] * dt) + d["fundingRate"] * math.exp(d["btcYield"] * dt))) + elif d["funding"] == ql.PerpetualFutures.FundingWithCurrentSpot: + # at the end of "3 Perpetual futures pricing" in the above paper + expected = ( + d["spot"] * (d["fundingRate"] - d["rateDiff"]) * math.exp(d["domRate"] * dt) / + (math.exp(d["btcYield"] * dt) - math.exp(d["domRate"] * dt) + d["fundingRate"] * math.exp(d["domRate"] * dt))) + elif d["payoff"] == ql.PerpetualFutures.Inverse: + if d["funding"] == ql.PerpetualFutures.FundingWithPreviousSpot: + # "Proposition 2" in the above paper + expected = ( + d["spot"] * + (math.exp(d["domRate"] * dt) - math.exp(d["btcYield"] * dt) + d["fundingRate"] * math.exp(d["domRate"] * dt)) / + (d["fundingRate"] - d["rateDiff"]) / math.exp(d["domRate"] * dt)) + elif d["funding"] == ql.PerpetualFutures.FundingWithCurrentSpot: + expected = ( + d["spot"] * + (math.exp(d["domRate"] * dt) - math.exp(d["btcYield"] * dt) + d["fundingRate"] * math.exp(d["btcYield"] * dt)) / + (d["fundingRate"] - d["rateDiff"]) / math.exp(d["btcYield"] * dt)) + else: + # Continuous time + if d["payoff"] == ql.PerpetualFutures.Linear: + # "Proposition 3" in the above paper + expected = d["spot"] * (d["fundingRate"] - d["rateDiff"]) / (d["btcYield"] - d["domRate"] + d["fundingRate"]) + elif d["payoff"] == ql.PerpetualFutures.Inverse: + # "Proposition 4" in the above paper + expected = d["spot"] * (d["domRate"] - d["btcYield"] + d["fundingRate"]) / (d["fundingRate"] - d["rateDiff"]) + failed_msg = f"Perpetual future price {npv} differs from analytic price {expected} with relative tolerance {d['tol']}\n" + self.assertAlmostEqual(npv, expected, delta=d["tol"] * expected, msg=failed_msg) + +if __name__ == '__main__': + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) diff --git a/Python/test/test_inflation.py b/Python/test/test_inflation.py index e3f17120..396dd94a 100644 --- a/Python/test/test_inflation.py +++ b/Python/test/test_inflation.py @@ -74,13 +74,12 @@ def create_inflation_swap_helper( inflation_data, inflation_index, interpolation, - discount_curve_handle, observation_lag=OBSERVATION_LAG, calendar=CAL, business_day_convention=BDC, day_counter=DAY_COUNTER): maturity = CAL.advance(reference_date, inflation_data[0]) - quote = ql.QuoteHandle(ql.SimpleQuote(inflation_data[1])) + quote = ql.makeQuoteHandle(inflation_data[1]) return ql.ZeroCouponInflationSwapHelper( quote, observation_lag, @@ -90,7 +89,7 @@ def create_inflation_swap_helper( day_counter, inflation_index, interpolation, - discount_curve_handle) + ) def build_nominal_term_structure( @@ -104,9 +103,8 @@ def build_nominal_term_structure( def build_hicp_index( fixing_data, - inflation_crv_handle, - interpolated=False): - index = ql.EUHICP(interpolated, inflation_crv_handle) + inflation_crv_handle): + index = ql.EUHICP(inflation_crv_handle) for x in fixing_data: # force override in case of multiple use index.addFixing(x[0], x[1], True) @@ -133,23 +131,18 @@ def build_inflation_term_structure( zero_coupon_swaps_data, inflation_index, interpolation, - nominal_term_structure_handle, observation_lag=OBSERVATION_LAG, include_seasonality=False): helpers = [create_inflation_swap_helper(reference_date, x, inflation_index, - interpolation, - nominal_term_structure_handle) + interpolation) for x in zero_coupon_swaps_data] - base_zero_rate = zero_coupon_swaps_data[0][1] cpi_term_structure = ql.PiecewiseZeroInflation( reference_date, - CAL, - DAY_COUNTER, - observation_lag, + inflation_index.lastFixingDate(), inflation_index.frequency(), - base_zero_rate, + DAY_COUNTER, helpers) if include_seasonality: seasonality = construct_seasonality(reference_date) @@ -210,8 +203,7 @@ def test_par_swap_pricing_fom_indexation_without_seasonality(self): VALUATION_DATE, EUR_BEI_SWAP_RATES, inflation_idx, - ql.CPI.Flat, - self.nominal_ts_handle) + ql.CPI.Flat) self.inflation_ts_handle.linkTo(inflation_ts) zciis = create_inflation_swap( @@ -249,8 +241,7 @@ def test_inflation_leg_payment_fom_indexation_without_seasonality(self): VALUATION_DATE, EUR_BEI_SWAP_RATES, inflation_idx, - ql.CPI.Flat, - self.nominal_ts_handle) + ql.CPI.Flat) self.inflation_ts_handle.linkTo(inflation_ts) zciis = create_inflation_swap( @@ -304,91 +295,6 @@ def test_inflation_leg_payment_fom_indexation_without_seasonality(self): delta=EPSILON, msg=fail_msg) - def test_swap_base_fixing_linear_indexation_without_seasonality(self): - """Testing swap base fixing for linear indexation""" - - inflation_idx = build_hicp_index( - EU_FIXING_DATA, self.inflation_ts_handle, interpolated=True) - inflation_ts = build_inflation_term_structure( - VALUATION_DATE, - EUR_BEI_SWAP_RATES, - inflation_idx, - ql.CPI.Linear, - self.nominal_ts_handle) - self.inflation_ts_handle.linkTo(inflation_ts) - - zciis = create_inflation_swap( - inflation_idx, - ql.Date(24, ql.August, 2018), - ql.Date(24, ql.August, 2023), - 0.032, - ql.CPI.Linear) - zciis.setPricingEngine(self.discount_engine) - - inflation_cf = ql.as_indexed_cashflow( - zciis.inflationLeg()[0]) - - swap_base_dt = inflation_cf.baseDate() - swap_base_fixing = inflation_idx.fixing(swap_base_dt) - expected_swap_base_index = interpolate_historic_index( - inflation_idx, swap_base_dt) - - fail_msg = """ Failed to replicate inflation swap base index fixing - for linear indexation: - index: {inflation_idx} - end date: {end_date} - observation lag: {observation_lag} - base index fixing: {base_index} - replicated base index fixing: {expected_base_index} - tolerance: {tolerance} - """.format(inflation_idx=inflation_idx.familyName(), - end_date=zciis.maturityDate(), - observation_lag=OBSERVATION_LAG, - base_index=swap_base_fixing, - expected_base_index=expected_swap_base_index, - tolerance=EPSILON) - self.assertAlmostEqual( - first=swap_base_fixing, - second=expected_swap_base_index, - delta=EPSILON, - msg=fail_msg) - - def test_inflation_curve_base_fixing(self): - """Testing inflation curve base fixing for linear indexation""" - - inflation_idx = build_hicp_index( - EU_FIXING_DATA, self.inflation_ts_handle, interpolated=True) - inflation_ts = build_inflation_term_structure( - VALUATION_DATE, - EUR_BEI_SWAP_RATES, - inflation_idx, - ql.CPI.Linear, - self.nominal_ts_handle) - self.inflation_ts_handle.linkTo(inflation_ts) - - curve_base_dt = inflation_ts.baseDate() - curve_base_fixing = inflation_idx.fixing(curve_base_dt) - expected_curve_base_fixing = interpolate_historic_index( - inflation_idx, curve_base_dt) - - fail_msg = """ Failed to replicate inflation curve base index fixing - for linear indexation: - index: {inflation_idx} - inflation curve base date : {base_date} - inflation curve base fixing: {base_fixing} - expected base fixing: {expected_base_fixing} - tolerance: {tolerance} - """.format(inflation_idx=inflation_idx.familyName(), - base_date=curve_base_dt, - base_fixing=curve_base_fixing, - expected_base_fixing=expected_curve_base_fixing, - tolerance=EPSILON) - self.assertAlmostEqual( - first=curve_base_fixing, - second=expected_curve_base_fixing, - msg=fail_msg, - delta=EPSILON) - def test_lagged_fixing_method(self): """Testing lagged fixing method""" @@ -398,8 +304,7 @@ def test_lagged_fixing_method(self): VALUATION_DATE, EUR_BEI_SWAP_RATES, inflation_idx, - ql.CPI.Flat, - self.nominal_ts_handle) + ql.CPI.Flat) self.inflation_ts_handle.linkTo(inflation_ts) maturity_date = ql.Date(25, ql.October, 2027) diff --git a/Python/test/test_integrals.py b/Python/test/test_integrals.py index 911716bf..1b60345a 100644 --- a/Python/test/test_integrals.py +++ b/Python/test/test_integrals.py @@ -32,10 +32,7 @@ import QuantLib_Risks as ql import unittest -if ql.XAD_ENABLED: - from xad import math -else: - import math +import math class IntegralTest(unittest.TestCase): diff --git a/Python/test/test_linear_algebra.py b/Python/test/test_linear_algebra.py new file mode 100644 index 00000000..6316798d --- /dev/null +++ b/Python/test/test_linear_algebra.py @@ -0,0 +1,35 @@ +import QuantLib_Risks as ql +import unittest + + +class ArrayTest(unittest.TestCase): + def test_math(self): + a = ql.Array([1, 2, 3]) + b = ql.Array([4, 5, 6]) + self.assertEqual(-a, ql.Array([-1, -2, -3])) + self.assertEqual(a + 1, ql.Array([2, 3, 4])) + self.assertEqual(a + b, ql.Array([5, 7, 9])) + self.assertEqual(b - 1, ql.Array([3, 4, 5])) + self.assertEqual(b - a, ql.Array([3, 3, 3])) + self.assertEqual(a * 2, ql.Array([2, 4, 6])) + self.assertEqual(3 * a, ql.Array([3, 6, 9])) + self.assertEqual(a * b, ql.Array([4, 10, 18])) + self.assertEqual( + a * ql.Matrix([[1, 2], [3, 4], [5, 6]]), + ql.Array([22, 28]) + ) + self.assertEqual(b / 2, ql.Array([2, 2.5, 3])) + self.assertEqual(b / a, ql.Array([4, 2.5, 2])) + self.assertEqual(a @ b, 32) + + def test_compare(self): + for v1 in ([1, 2], [1, 2, 3], [2, 3, 4]): + for v2 in ([1, 2], [1, 2, 3], [2, 3, 4]): + self.assertEqual(ql.Array(v1) == ql.Array(v2), v1 == v2) + self.assertEqual(ql.Array(v1) != ql.Array(v2), v1 != v2) + + +if __name__ == "__main__": + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) + diff --git a/Python/test/test_marketelements.py b/Python/test/test_marketelements.py index e32119d0..99095c90 100644 --- a/Python/test/test_marketelements.py +++ b/Python/test/test_marketelements.py @@ -52,6 +52,11 @@ def testObservable(self): me.setValue(3.14) if not flag: self.fail("Observer was not notified of market element change") + flag = None + obs.unregisterWith(me) + me.setValue(2.71) + if flag: + self.fail("Observer was notified after unregistering") def testObservableHandle(self): "Testing observability of market element handles" @@ -69,6 +74,30 @@ def testObservableHandle(self): h.linkTo(me2) if not flag: self.fail("Observer was not notified of market element change") + flag = None + obs.unregisterWith(h) + me2.setValue(2.71) + if flag: + self.fail("Observer was notified after unregistering") + + def testObservableErrors(self): + class Handle: + def __init__(self, x): + self.x = x + + def asObservable(self): + return 10 / self.x + + obs = ql.Observer(raiseFlag) + self.assertRaises(TypeError, obs.registerWith, 123) + self.assertRaises(TypeError, obs.registerWith, obs) + self.assertRaises(TypeError, obs.registerWith, Handle) + self.assertRaises(ZeroDivisionError, obs.registerWith, Handle(0)) + self.assertRaises(TypeError, obs.registerWith, Handle(1)) + + def test_SimpleQuote(self): + for value in (100, 2.71, 10**100): + self.assertAlmostEqual(ql.SimpleQuote(value).value(), value) if __name__ == "__main__": diff --git a/Python/test/test_money.py b/Python/test/test_money.py new file mode 100644 index 00000000..c2c7c2b8 --- /dev/null +++ b/Python/test/test_money.py @@ -0,0 +1,18 @@ +import QuantLib_Risks as ql +import operator +import unittest + + +class MoneyTest(unittest.TestCase): + def test_order(self): + ops = [operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge] + usd = lambda v: ql.Money(v, ql.USDCurrency()) + for m1 in (usd(1), usd(2)): + for m2 in (usd(1), usd(2)): + for op in ops: + self.assertEqual(op(m1, m2), op(m1.value(), m2.value())) + + +if __name__ == "__main__": + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) diff --git a/Python/test/test_ode.py b/Python/test/test_ode.py index 3428ca6b..050ff43c 100644 --- a/Python/test/test_ode.py +++ b/Python/test/test_ode.py @@ -30,13 +30,10 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ +import math import unittest import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math class OdeTest(unittest.TestCase): diff --git a/Python/test/test_options.py b/Python/test/test_options.py index 847fecc7..23357533 100644 --- a/Python/test/test_options.py +++ b/Python/test/test_options.py @@ -40,9 +40,8 @@ def testFdHestonHullWhite(self): """ Testing FDM Heston Hull-White pricing """ dc = ql.Actual365Fixed() - todays_date = ql.Date(19, ql.May, 2021) - ql.Settings.instance().evaluationDate = todays_date - + todays_date = ql.Settings.instance().evaluationDate + r = ql.YieldTermStructureHandle(ql.FlatForward(todays_date, 0.075, dc)) d = ql.YieldTermStructureHandle(ql.FlatForward(todays_date, 0.01, dc)) @@ -57,11 +56,11 @@ def testFdHestonHullWhite(self): a = 0.00883 sig = 0.00631 - underlying = ql.QuoteHandle(ql.SimpleQuote(s0)) + underlying = ql.makeQuoteHandle(s0) option = ql.VanillaOption( ql.PlainVanillaPayoff(ql.Option.Call, s0), - ql.EuropeanExercise(todays_date + ql.Period(1, ql.Years)) + ql.EuropeanExercise(todays_date + ql.Period(365, ql.Days)) ) hull_white_process = ql.HullWhiteProcess(r, a, sig) @@ -75,14 +74,11 @@ def testFdHestonHullWhite(self): ) self.assertAlmostEqual(0.87628, option.NPV(), 4) - - def tearDown(self) -> None: - ql.Settings.instance().evaluationDate = ql.Date() def testAnalyticHestonHullWhite(self): """ Testing Analytic Heston Hull-White pricing """ - today = ql.Date.todaysDate() - ql.Settings.instance().evaluationDate = today + + today = ql.Settings.instance().evaluationDate dc = ql.Actual365Fixed() maturityDate = today + ql.Period(10 * 365, ql.Days) @@ -106,7 +102,7 @@ def testAnalyticHestonHullWhite(self): expected = 40.028973 s0 = 100 - underlying = ql.QuoteHandle(ql.SimpleQuote(s0)) + underlying = ql.makeQuoteHandle(s0) hull_white_model = ql.HullWhite(r, a, sig) heston_model = ql.HestonModel( @@ -123,6 +119,47 @@ def testAnalyticHestonHullWhite(self): ) self.assertAlmostEqual(expected, option.NPV(), 5) + def testCashDividendEuropeanEngine(self): + """Testing cash dividend European engine""" + + today = today = ql.Settings.instance().evaluationDate + dc = ql.Actual365Fixed() + + maturityDate = today + ql.Period(366, ql.Days) + + option = ql.VanillaOption( + ql.PlainVanillaPayoff(ql.Option.Call, 100.0), + ql.EuropeanExercise(maturityDate) + ) + + div_schedule = ql.DividendSchedule() + div_schedule.append(ql.FixedDividend(5.0, today + ql.Period(92, ql.Days))) + + process = ql.BlackScholesMertonProcess( + ql.makeQuoteHandle(100.0), + ql.YieldTermStructureHandle(ql.FlatForward(today, 0.075, dc)), + ql.YieldTermStructureHandle(ql.FlatForward(today, 0.05, dc)), + ql.BlackVolTermStructureHandle(ql.BlackConstantVol(today, ql.TARGET(), 0.3, dc)) + ) + + option.setPricingEngine( + ql.CashDividendEuropeanEngine( + process, div_schedule, ql.CashDividendEuropeanEngine.Escrowed + ) + ) + calculated = option.NPV() + + option.setPricingEngine(ql.AnalyticDividendEuropeanEngine(process, div_schedule)) + expected = option.NPV() + self.assertAlmostEqual(expected, calculated, 8) + + option.setPricingEngine( + ql.CashDividendEuropeanEngine( + process, div_schedule, ql.CashDividendEuropeanEngine.Spot + ) + ) + self.assertAlmostEqual(7.9193, option.NPV(), 3) + if __name__ == '__main__': print("testing QuantLib", ql.__version__) diff --git a/Python/test/test_ratehelpers.py b/Python/test/test_ratehelpers.py index 5f97e11f..b7feca10 100644 --- a/Python/test/test_ratehelpers.py +++ b/Python/test/test_ratehelpers.py @@ -43,7 +43,7 @@ def setUp(self): self.settlement_days = 3 self.face_amount = 100.0 self.redemption = 100.0 - self.quote_handle = ql.QuoteHandle(ql.SimpleQuote(100.0)) + self.quote_handle = ql.makeQuoteHandle(100.0) self.issue_date = ql.Date(2, 1, 2008) self.maturity_date = ql.Date(2, 1, 2018) @@ -136,7 +136,7 @@ def build_eur_curve(self, quotes_date): # build rate helpers dayCounter = ql.Actual360() - # looping left if somone wants two add more deposits to tests, e.g. T/N + # looping left if someone wants two add more deposits to tests, e.g. T/N self.depositHelpers = [ ql.DepositRateHelper( @@ -205,7 +205,7 @@ def test_ois_default_calendar(self): already set and additional calendar will have no impact. The test checks if the constructor exposed to Python maintains this desired property and verifies that the start date of a EUR plain vanilla OIS traded on March - 29th, 2018 is equal to April 4th, 2018 (du to holiday on March 30th, + 29th, 2018 is equal to April 4th, 2018 (due to holiday on March 30th, 2018 in TARGET calendar. """ test_date = ql.Date(29, 3, 2018) @@ -353,7 +353,7 @@ def build_pln_fx_swap_curve(self, base_ccy_yts, fx_swaps, fx_spot): fxSwapHelpers = [ ql.FxSwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(fx_swaps[(n, unit)])), + ql.makeQuoteHandle(fx_swaps[(n, unit)]), ql.QuoteHandle(spotFx), ql.Period(n, unit), spot_date_lag, @@ -496,8 +496,8 @@ def testFxMarketConventionsForCrossRateONPeriod(self): # Settlement should be on a day where all three centers are operating # and follow EndOfMonth rule on_rate_helper = ql.FxSwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(fwd_points)), - ql.QuoteHandle(ql.SimpleQuote(self.fx_spot_quote_EURPLN)), + ql.makeQuoteHandle(fwd_points), + ql.makeQuoteHandle(self.fx_spot_quote_EURPLN), on_period, fixing_days, joint_calendar, @@ -560,8 +560,8 @@ def testFxMarketConventionsForDatesInEURUSD_ON_Period(self): trading_calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond) on_rate_helper = ql.FxSwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(fwd_points)), - ql.QuoteHandle(ql.SimpleQuote(self.fx_spot_quote_EURUSD)), + ql.makeQuoteHandle(fwd_points), + ql.makeQuoteHandle(self.fx_spot_quote_EURUSD), on_period, fixing_days, calendar, @@ -600,8 +600,8 @@ def testFxMarketConventionsForDatesInEURUSD_ShortEnd(self): trading_calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond) rate_helper = ql.FxSwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(fwd_points)), - ql.QuoteHandle(ql.SimpleQuote(self.fx_spot_quote_EURUSD)), + ql.makeQuoteHandle(fwd_points), + ql.makeQuoteHandle(self.fx_spot_quote_EURUSD), period, fixing_days, calendar, @@ -620,7 +620,7 @@ def tearDown(self): def flat_rate(rate): return ql.FlatForward( - 0, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(rate)), ql.Actual365Fixed()) + 0, ql.NullCalendar(), ql.makeQuoteHandle(rate), ql.Actual365Fixed()) class CrossCurrencyBasisSwapRateHelperTest(unittest.TestCase): @@ -663,7 +663,7 @@ def buildRateHelper( is_fx_base_ccy_collateral_ccy, is_basis_on_fx_base_ccy_leg): tenor, rate = quote_tuple - quote_handle = ql.QuoteHandle(ql.SimpleQuote(rate * self.basis_point)) + quote_handle = ql.makeQuoteHandle(rate * self.basis_point) return ql.ConstNotionalCrossCurrencyBasisSwapRateHelper( quote_handle, tenor, diff --git a/Python/test/test_sabr.py b/Python/test/test_sabr.py index e6d068a4..abd70757 100644 --- a/Python/test/test_sabr.py +++ b/Python/test/test_sabr.py @@ -30,13 +30,10 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ +import math import unittest import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math class SabrTest(unittest.TestCase): diff --git a/Python/test/test_settings.py b/Python/test/test_settings.py new file mode 100644 index 00000000..54cf9e38 --- /dev/null +++ b/Python/test/test_settings.py @@ -0,0 +1,34 @@ +import QuantLib_Risks as ql +import unittest + + +class SettingsTest(unittest.TestCase): + def test_properties(self): + settings = ql.Settings.instance() + with ql.SavedSettings(): + for v in (ql.Date(1, 1, 2023), ql.Date(2, 2, 2023)): + settings.evaluationDate = v + self.assertEqual(settings.evaluationDate, v) + for v in (True, False): + settings.enforcesTodaysHistoricFixings = v + self.assertEqual(settings.enforcesTodaysHistoricFixings, v) + for v in (True, False): + settings.includeReferenceDateEvents = v + self.assertEqual(settings.includeReferenceDateEvents, v) + for v in (True, False, None): + settings.includeTodaysCashFlows = v + self.assertEqual(settings.includeTodaysCashFlows, v) + + def test_saved_settings(self): + settings = ql.Settings.instance() + settings.evaluationDate = ql.Date(1, 1, 2023) + with ql.SavedSettings(): + settings.evaluationDate = ql.Date(2, 2, 2023) + self.assertEqual(settings.evaluationDate, ql.Date(2, 2, 2023)) + # Test that SavedSettings restores settings on exit. + self.assertEqual(settings.evaluationDate, ql.Date(1, 1, 2023)) + + +if __name__ == "__main__": + print("testing QuantLib", ql.__version__) + unittest.main(verbosity=2) diff --git a/Python/test/test_slv.py b/Python/test/test_slv.py index a4d25ecf..099d4d67 100644 --- a/Python/test/test_slv.py +++ b/Python/test/test_slv.py @@ -43,7 +43,7 @@ def setUp(self): self.dc = ql.Actual365Fixed() self.riskFreeRate = ql.YieldTermStructureHandle(ql.FlatForward(self.settlementDate, 0.05, self.dc)) self.dividendYield = ql.YieldTermStructureHandle(ql.FlatForward(self.settlementDate, 0.025, self.dc)) - self.underlying = ql.QuoteHandle(ql.SimpleQuote(100.0)) + self.underlying = ql.makeQuoteHandle(100.0) def tearDown(self): ql.Settings.instance().evaluationDate = ql.Date() diff --git a/Python/test/test_swap.py b/Python/test/test_swap.py index b1b8b780..9714f979 100644 --- a/Python/test/test_swap.py +++ b/Python/test/test_swap.py @@ -1,30 +1,15 @@ """ Copyright (C) 2021 Marcin Rybacki Copyright (C) 2023 Marcin Rybacki - Copyright (C) 2024 Xcelerit Computing Limited. - This file is part of QuantLib-Risks, a Python wrapper for QuantLib enabled - for risk computation using automatic differentiation. It uses XAD, - a fast and comprehensive C++ library for automatic differentiation. - - QuantLib-Risks and XAD are free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QuantLib-Risks is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -60,7 +45,7 @@ def flat_rate(rate): return ql.FlatForward( - 2, CAL, ql.QuoteHandle(ql.SimpleQuote(rate)), ql.Actual365Fixed()) + 2, CAL, ql.makeQuoteHandle(rate), ql.Actual365Fixed()) class ZeroCouponSwapTest(unittest.TestCase): @@ -174,11 +159,12 @@ def setUp(self): self.interest_handle = ql.YieldTermStructureHandle(flat_rate(0.03)) self.dividend_handle = ql.YieldTermStructureHandle(flat_rate(0.0)) - equity_spot = ql.QuoteHandle(ql.SimpleQuote(8690.0)) + equity_spot = ql.makeQuoteHandle(8690.0) self.equity_idx = ql.EquityIndex( "eq_idx", CAL, + ql.Currency(), self.interest_handle, self.dividend_handle, equity_spot) diff --git a/Python/test/test_swaption.py b/Python/test/test_swaption.py index 0a1e086b..6b3ef004 100644 --- a/Python/test/test_swaption.py +++ b/Python/test/test_swaption.py @@ -80,19 +80,20 @@ def swap_pv01(underlying): def make_const_black_vol_engine(discount_handle, volatility): - h = ql.QuoteHandle(ql.SimpleQuote(volatility)) + h = ql.makeQuoteHandle(volatility) return ql.BlackSwaptionEngine(discount_handle, h) def make_const_bachelier_vol_engine(discount_handle, volatility): - h = ql.QuoteHandle(ql.SimpleQuote(volatility)) + h = ql.makeQuoteHandle(volatility) return ql.BachelierSwaptionEngine(discount_handle, h) class SwaptionTest(unittest.TestCase): def setUp(self): self.calendar = ql.TARGET() - self.today = self.calendar.adjust(ql.Date.todaysDate()) + # fixed date: the par yield annuity check fails on some dates, e.g. 26 Oct 2026 + self.today = self.calendar.adjust(ql.Date(15, ql.June, 2026)) ql.Settings.instance().evaluationDate = self.today projection_curve_handle = ql.RelinkableYieldTermStructureHandle() @@ -103,7 +104,7 @@ def setUp(self): projection_curve_handle.linkTo(projection_curve) self.discount_handle = ql.YieldTermStructureHandle(ql.FlatForward( - self.today, ql.QuoteHandle(ql.SimpleQuote(0.0085)), ql.Actual365Fixed())) + self.today, ql.makeQuoteHandle(0.0085), ql.Actual365Fixed())) self.swap_engine = ql.DiscountingSwapEngine(self.discount_handle) self.idx = ql.Euribor6M(projection_curve_handle) diff --git a/Python/test/test_termstructures.py b/Python/test/test_termstructures.py index 0b8cd6c9..ef28e176 100644 --- a/Python/test/test_termstructures.py +++ b/Python/test/test_termstructures.py @@ -34,10 +34,7 @@ import QuantLib_Risks as ql import unittest -if ql.XAD_ENABLED: - from xad import math -else: - import math +import math flag = None @@ -73,10 +70,10 @@ def setUp(self): ql.Settings.instance().evaluationDate = today self.settlementDays = 2 self.dayCounter = ql.Actual360() - settlement = self.calendar.advance(today, self.settlementDays, ql.Days) + self.settlement = self.calendar.advance(today, self.settlementDays, ql.Days) deposits = [ ql.DepositRateHelper( - ql.QuoteHandle(ql.SimpleQuote(rate / 100)), + ql.makeQuoteHandle(rate / 100), ql.Period(n, units), self.settlementDays, self.calendar, @@ -94,7 +91,7 @@ def setUp(self): ] swaps = [ ql.SwapRateHelper( - ql.QuoteHandle(ql.SimpleQuote(rate / 100)), + ql.makeQuoteHandle(rate / 100), ql.Period(years, ql.Years), self.calendar, ql.Annual, @@ -104,9 +101,10 @@ def setUp(self): ) for (years, rate) in [(1, 4.54), (5, 4.99), (10, 5.47), (20, 5.89), (30, 5.96)] ] + self.instruments = deposits + swaps self.termStructure = ql.PiecewiseFlatForward( - settlement, deposits + swaps, self.dayCounter) + self.settlement, self.instruments, self.dayCounter) def tearDown(self): ql.Settings.instance().evaluationDate = ql.Date() @@ -168,7 +166,7 @@ def testCompositeZeroYieldStructure(self): freq = ql.Semiannual flatTs = ql.FlatForward( settlement, - ql.QuoteHandle(ql.SimpleQuote(0.0085)), + ql.makeQuoteHandle(0.0085), self.dayCounter) firstHandle = ql.YieldTermStructureHandle(flatTs) secondHandle = ql.YieldTermStructureHandle(self.termStructure) @@ -196,13 +194,13 @@ def testCompositeZeroYieldStructure(self): def testUltimateForwardTermStructure(self): """Testing ultimate forward term structure""" settlement = self.termStructure.referenceDate() - ufr = ql.QuoteHandle(ql.SimpleQuote(0.06)) - llfr = ql.QuoteHandle(ql.SimpleQuote(0.05)) + ufr = ql.makeQuoteHandle(0.06) + llfr = ql.makeQuoteHandle(0.05) fsp = ql.Period(20, ql.Years) alpha = 0.05 baseCrvHandle = ql.YieldTermStructureHandle(self.termStructure) ufrCrv = ql.UltimateForwardTermStructure( - baseCrvHandle, llfr, ufr, fsp, alpha) + baseCrvHandle, llfr, ufr, fsp, alpha, None) cutOff = ufrCrv.timeFromReference(settlement + fsp) forwardCalculator = extrapolatedForwardRate( cutOff, llfr.value(), ufr.value(), alpha) @@ -225,6 +223,83 @@ def testUltimateForwardTermStructure(self): delta=1.0e-12, msg=failMsg) + def testTermStructureInterpolationSchemes(self): + """Testing different interpolation schemes and their consistency""" + args = [self.settlement, self.instruments, self.dayCounter] + mapping = [ + [ql.PiecewiseParabolicCubicZero, + ql.ParabolicCubicZeroCurve, 'Parabolic Zero'], + [ql.PiecewiseMonotonicParabolicCubicZero, + ql.MonotonicParabolicCubicZeroCurve, 'Monotone Parabolic Zero'], + [ql.PiecewiseLogParabolicCubicDiscount, + ql.LogParabolicCubicDiscountCurve, 'Log Parabolic Discount'], + [ql.PiecewiseMonotonicLogParabolicCubicDiscount, + ql.MonotonicLogParabolicCubicDiscountCurve, 'Monotone Log Parabolic Discount'], + ] + + for bootstrap, interp, name in mapping: + bootstrap_crv = bootstrap(*args) + dates, nodes = zip(*bootstrap_crv.nodes()) + equivalent_crv = interp(dates, nodes, self.dayCounter) + + for d in dates: + expected = equivalent_crv.zeroRate( + d, self.dayCounter, ql.Continuous, ql.NoFrequency).rate() + actual = bootstrap_crv.zeroRate( + d, self.dayCounter, ql.Continuous, ql.NoFrequency).rate() + + failMsg = """ Interpolation check failed for: + interpolation: {interpolation} + expected zero rate: {expected} + actual zero rate: {actual} + """.format(interpolation=name, + expected=expected, + actual=actual) + self.assertAlmostEqual( + first=expected, + second=actual, + delta=1.0e-12, + msg=failMsg) + + def testInterpolatedPiecewiseZeroSpreadedTermStructure(self): + """Testing different interpolation schemes for zero spreaded term structure""" + h = ql.RelinkableYieldTermStructureHandle() + h.linkTo(self.termStructure) + spreads = [(1, 0.005), (2, 0.008), (3, 0.0103), (4, 0.0145), (5, 0.025)] + dates, quotes = zip(*[(h.referenceDate() + ql.Period(t, ql.Years), + ql.QuoteHandle(ql.SimpleQuote(s))) + for t, s in spreads]) + args = [h, quotes, dates] + constructors = [ql.SpreadedLinearZeroInterpolatedTermStructure, + ql.SpreadedBackwardFlatZeroInterpolatedTermStructure, + ql.SpreadedCubicZeroInterpolatedTermStructure, + ql.SpreadedKrugerZeroInterpolatedTermStructure, + ql.SpreadedSplineCubicZeroInterpolatedTermStructure] + for constructor in constructors: + spreadedTs = constructor(*args) + for d, r in zip(dates, quotes): + expected = r.value() + + zeroFromSpreadTS = spreadedTs.zeroRate( + d, self.dayCounter, ql.Continuous, ql.NoFrequency).rate() + zeroFromBaseTs = h.zeroRate( + d, self.dayCounter, ql.Continuous, ql.NoFrequency).rate() + actual = zeroFromSpreadTS - zeroFromBaseTs + + failMsg = """ Interpolated piecewise zero spreaded term structure + zero rate replication failed for: + maturity: {maturity} + expected zero rate: {expected} + actual zero rate: {actual} + """.format(maturity=d, + expected=expected, + actual=actual) + self.assertAlmostEqual( + first=expected, + second=actual, + delta=1.0e-12, + msg=failMsg) + def testQuantoTermStructure(self): """Testing quanto term structure""" today = ql.Date.todaysDate() @@ -232,21 +307,21 @@ def testQuantoTermStructure(self): dividend_ts = ql.YieldTermStructureHandle( ql.FlatForward( today, - ql.QuoteHandle(ql.SimpleQuote(0.055)), + ql.makeQuoteHandle(0.055), self.dayCounter ) ) r_domestic_ts = ql.YieldTermStructureHandle( ql.FlatForward( today, - ql.QuoteHandle(ql.SimpleQuote(-0.01)), + ql.makeQuoteHandle(-0.01), self.dayCounter ) ) r_foreign_ts = ql.YieldTermStructureHandle( ql.FlatForward( today, - ql.QuoteHandle(ql.SimpleQuote(0.02)), + ql.makeQuoteHandle(0.02), self.dayCounter ) ) @@ -254,7 +329,7 @@ def testQuantoTermStructure(self): ql.BlackConstantVol( today, self.calendar, - ql.QuoteHandle(ql.SimpleQuote(0.25)), + ql.makeQuoteHandle(0.25), self.dayCounter ) ) @@ -262,12 +337,12 @@ def testQuantoTermStructure(self): ql.BlackConstantVol( today, self.calendar, - ql.QuoteHandle(ql.SimpleQuote(0.05)), + ql.makeQuoteHandle(0.05), self.dayCounter ) ) - rho = ql.QuoteHandle(ql.SimpleQuote(0.3)) - s_0 = ql.QuoteHandle(ql.SimpleQuote(100.0)) + rho = ql.makeQuoteHandle(0.3) + s_0 = ql.makeQuoteHandle(100.0) exercise = ql.EuropeanExercise(self.calendar.advance(today, 6, ql.Months)) payoff = ql.PlainVanillaPayoff(ql.Option.Call, 95.0) @@ -336,6 +411,7 @@ def testLazyObject(self): self.termStructure.unfreeze() + if __name__ == "__main__": print("testing QuantLib", ql.__version__) unittest.main(verbosity=2) diff --git a/Python/test/test_volatilities.py b/Python/test/test_volatilities.py index 48b72744..317ee78e 100644 --- a/Python/test/test_volatilities.py +++ b/Python/test/test_volatilities.py @@ -32,13 +32,10 @@ """ import unittest +import math import QuantLib_Risks as ql -if ql.XAD_ENABLED: - from xad import math -else: - import math -TOLERANCE = 1.e-10 +TOLERANCE = 1e-10 if ql.IborCoupon.usingAtParCoupons() else 1e-6 SABR_ATM_TOLERANCE = 3.0e-4 SABR_SPREAD_TOLERANCE = 12.0e-4 @@ -208,7 +205,7 @@ def build_linear_swaption_cube( swap_index_base, short_swap_index_base=None, vega_weighted_smile_fit=False): - vol_spreads = [[ql.QuoteHandle(ql.SimpleQuote(v)) for v in row] + vol_spreads = [[ql.makeQuoteHandle(v) for v in row] for row in vol_spreads] cube = ql.InterpolatedSwaptionVolatilityCube( ql.SwaptionVolatilityStructureHandle(volatility_matrix), @@ -227,10 +224,10 @@ def sabr_parameters_guess(number_of_options, number_of_swaps): n_elements = number_of_options * number_of_swaps guess = n_elements * [0] for n in range(n_elements): - guess[n] = (ql.QuoteHandle(ql.SimpleQuote(0.2)), - ql.QuoteHandle(ql.SimpleQuote(0.5)), - ql.QuoteHandle(ql.SimpleQuote(0.4)), - ql.QuoteHandle(ql.SimpleQuote(0.0))) + guess[n] = (ql.makeQuoteHandle(0.2), + ql.makeQuoteHandle(0.5), + ql.makeQuoteHandle(0.4), + ql.makeQuoteHandle(0.0)) return guess @@ -245,7 +242,7 @@ def build_sabr_swaption_cube( vega_weighted_smile_fit=False, is_parameter_fixed=(False, False, False, False), is_atm_calibrated=True): - v_spreads = [[ql.QuoteHandle(ql.SimpleQuote(v)) for v in row] + v_spreads = [[ql.makeQuoteHandle(v) for v in row] for row in vol_spreads] guess = sabr_parameters_guess( len(spread_opt_tenors), len(spread_swap_tenors)) @@ -595,7 +592,7 @@ def testLocalVolCalibration(self): today = ql.Settings.instance().evaluationDate - spot = ql.QuoteHandle(ql.SimpleQuote(100)) + spot = ql.makeQuoteHandle(100) dc = ql.Actual365Fixed() qTS = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.025, dc)) diff --git a/SWIG/asianoptions.i b/SWIG/asianoptions.i index fe6e5d6c..9b78eddc 100644 --- a/SWIG/asianoptions.i +++ b/SWIG/asianoptions.i @@ -11,7 +11,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -41,6 +41,11 @@ class ContinuousAveragingAsianOption : public OneAssetOption { Average::Type averageType, const ext::shared_ptr& payoff, const ext::shared_ptr& exercise); + ContinuousAveragingAsianOption( + Average::Type averageType, + Date startDate, + const ext::shared_ptr& payoff, + const ext::shared_ptr& exercise); }; %shared_ptr(DiscreteAveragingAsianOption) @@ -439,6 +444,8 @@ using QuantLib::ContinuousArithmeticAsianLevyEngine; %shared_ptr(ContinuousArithmeticAsianLevyEngine) class ContinuousArithmeticAsianLevyEngine : public PricingEngine { public: + ContinuousArithmeticAsianLevyEngine(const ext::shared_ptr& process, + const Handle& runningAverage); ContinuousArithmeticAsianLevyEngine(const ext::shared_ptr& process, const Handle& runningAverage, const Date& startDate); @@ -455,6 +462,20 @@ class FdBlackScholesAsianEngine : public PricingEngine { Size tGrid, Size xGrid, Size aGrid); }; + +%{ +using QuantLib::ChoiAsianEngine; +%} + +%shared_ptr(ChoiAsianEngine) +class ChoiAsianEngine : public PricingEngine { + public: + ChoiAsianEngine( + ext::shared_ptr process, + Real lambda = 15, + Size maxNrIntegrationSteps = 2 << 21); +}; + %{ using QuantLib::TurnbullWakemanAsianEngine; %} diff --git a/SWIG/barrieroptions.i b/SWIG/barrieroptions.i index 4bacfb8d..30644b5b 100644 --- a/SWIG/barrieroptions.i +++ b/SWIG/barrieroptions.i @@ -11,7 +11,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -34,7 +34,6 @@ struct Barrier { %{ using QuantLib::BarrierOption; -using QuantLib::DividendBarrierOption; using QuantLib::QuantoBarrierOption; %} @@ -79,8 +78,8 @@ class QuantoBarrierOption : public BarrierOption { using QuantLib::PartialBarrier; %} -struct PartialBarrier : public Barrier { - enum Range { Start, End, EndB1, EndB2 }; +struct PartialBarrier { + enum Range { Start = 0, EndB1 = 2, EndB2 = 3 }; }; %{ @@ -90,7 +89,8 @@ using QuantLib::PartialTimeBarrierOption; %shared_ptr(PartialTimeBarrierOption) class PartialTimeBarrierOption : public OneAssetOption { public: - PartialTimeBarrierOption(PartialBarrier::Type barrierType, + PartialTimeBarrierOption( + Barrier::Type barrierType, PartialBarrier::Range barrierRange, Real barrier, Real rebate, @@ -110,21 +110,7 @@ using QuantLib::AnalyticPartialTimeBarrierOptionEngine; class AnalyticPartialTimeBarrierOptionEngine : public PricingEngine { public: AnalyticPartialTimeBarrierOptionEngine ( - const ext::shared_ptr& process - ); -}; - - -%shared_ptr(DividendBarrierOption) -class DividendBarrierOption : public BarrierOption { - public: - DividendBarrierOption(Barrier::Type barrierType, - Real barrier, - Real rebate, - const ext::shared_ptr& payoff, - const ext::shared_ptr& exercise, - const std::vector& dividendDates, - const std::vector& dividends); + const ext::shared_ptr& process); }; @@ -599,4 +585,58 @@ class BinomialDoubleBarrierEngine : public PricingEngine { #endif + +%{ +using QuantLib::TwoAssetBarrierOption; +using QuantLib::AnalyticTwoAssetBarrierEngine; +%} + +%shared_ptr(TwoAssetBarrierOption) +class TwoAssetBarrierOption : public Option { + public: + TwoAssetBarrierOption(Barrier::Type barrierType, + Real barrier, + const ext::shared_ptr& payoff, + const ext::shared_ptr& exercise); +}; + +%shared_ptr(AnalyticTwoAssetBarrierEngine) +class AnalyticTwoAssetBarrierEngine : public PricingEngine { + public: + AnalyticTwoAssetBarrierEngine(ext::shared_ptr process1, + ext::shared_ptr process2, + Handle rho); +}; + + +%{ +using QuantLib::SoftBarrierOption; +using QuantLib::AnalyticSoftBarrierEngine; +%} + +%shared_ptr(SoftBarrierOption) +class SoftBarrierOption : public OneAssetOption { + public: + SoftBarrierOption(Barrier::Type barrierType, + Real barrier_lo, + Real barrier_hi, + const ext::shared_ptr& payoff, + const ext::shared_ptr& exercise); + + Volatility impliedVolatility( + Real price, + const ext::shared_ptr& process, + Real accuracy = 1.0e-4, + Size maxEvaluations = 100, + Volatility minVol = 1e-6, + Volatility maxVol = 4.0) const; +}; + +%shared_ptr(AnalyticSoftBarrierEngine) +class AnalyticSoftBarrierEngine : public PricingEngine { + public: + AnalyticSoftBarrierEngine(ext::shared_ptr process); +}; + + #endif diff --git a/SWIG/basketoptions.i b/SWIG/basketoptions.i index a7e7bfa9..d3139ec5 100644 --- a/SWIG/basketoptions.i +++ b/SWIG/basketoptions.i @@ -1,10 +1,10 @@ - /* Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 StatPro Italia srl Copyright (C) 2005 Dominic Thuillier Copyright (C) 2007 Joseph Wang Copyright (C) 2018, 2019 Matthias Lungwitz + Copyright (C) 2024 Klaus Spanderen This file is part of QuantLib, a free-software/open-source library for financial quantitative analysts and developers - http://quantlib.org/ @@ -13,7 +13,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -172,8 +172,8 @@ class MCAmericanBasketEngine : public PricingEngine { BigInteger seed = 0, Size nCalibrationSamples = Null(), Size polynomOrder = 2, - LsmBasisSystem::PolynomialType polynomType - = LsmBasisSystem::Monomial) { + LsmBasisSystem::PolynomialType polynomType + = LsmBasisSystem::Monomial) { return new MCAmericanBasketEngine(process, timeSteps, timeStepsPerYear, @@ -233,6 +233,8 @@ class MCAmericanBasketEngine : public PricingEngine { %{ using QuantLib::StulzEngine; using QuantLib::KirkEngine; +using QuantLib::BjerksundStenslandSpreadEngine; +using QuantLib::OperatorSplittingSpreadEngine; using QuantLib::Fd2dBlackScholesVanillaEngine; %} @@ -247,22 +249,109 @@ class StulzEngine : public PricingEngine { %shared_ptr(KirkEngine) class KirkEngine : public PricingEngine { public: - KirkEngine(const ext::shared_ptr& process1, - const ext::shared_ptr& process2, + KirkEngine(ext::shared_ptr process1, + ext::shared_ptr process2, Real correlation); }; +%shared_ptr(BjerksundStenslandSpreadEngine) +class BjerksundStenslandSpreadEngine : public PricingEngine { + public: + BjerksundStenslandSpreadEngine( + ext::shared_ptr process1, + ext::shared_ptr process2, + Real correlation); +}; + +%shared_ptr(OperatorSplittingSpreadEngine) +class OperatorSplittingSpreadEngine : public PricingEngine { + public: + enum Order {First, Second}; + OperatorSplittingSpreadEngine( + ext::shared_ptr process1, + ext::shared_ptr process2, + Real correlation, + Order order = Second); +}; + %shared_ptr(Fd2dBlackScholesVanillaEngine) class Fd2dBlackScholesVanillaEngine : public PricingEngine { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") Fd2dBlackScholesVanillaEngine; + #endif + public: + Fd2dBlackScholesVanillaEngine( + const ext::shared_ptr& p1, + const ext::shared_ptr& p2, + Real correlation, + Size xGrid = 100, Size yGrid = 100, + Size tGrid = 50, Size dampingSteps = 0, + const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer(), + bool localVol = false, + Real illegalLocalVolOverwrite = -Null()); +}; + +%{ +using QuantLib::ChoiBasketEngine; +using QuantLib::DengLiZhouBasketEngine; +using QuantLib::FdndimBlackScholesVanillaEngine; +%} + +#if defined(SWIGCSHARP) +SWIG_STD_VECTOR_ENHANCED( ext::shared_ptr ) +#endif +%template(GeneralizedBlackScholesProcessVector) +std::vector >; + + +%shared_ptr(ChoiBasketEngine) +class ChoiBasketEngine : public PricingEngine { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") ChoiBasketEngine; + #endif public: - Fd2dBlackScholesVanillaEngine(const ext::shared_ptr& p1, - const ext::shared_ptr& p2, - Real correlation, - Size xGrid = 100, Size yGrid = 100, - Size tGrid = 50, Size dampingSteps = 0, - const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer(), - bool localVol = false, - Real illegalLocalVolOverwrite = -Null()); + ChoiBasketEngine( + std::vector > processes, + Matrix rho, + Real lambda = 10.0, + Size maxNrIntegrationSteps = std::numeric_limits::max(), + bool calcfwdDelta = false, + bool controlVariate = false); +}; + +%shared_ptr(DengLiZhouBasketEngine) +class DengLiZhouBasketEngine : public PricingEngine { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") DengLiZhouBasketEngine; + #endif + public: + DengLiZhouBasketEngine( + std::vector > processes, + Matrix rho); +}; + +%shared_ptr(FdndimBlackScholesVanillaEngine) +class FdndimBlackScholesVanillaEngine : public PricingEngine { + public: + FdndimBlackScholesVanillaEngine( + std::vector > processes, + Matrix rho, Size xGrid, Size tGrid = 50, + Size dampingSteps = 0, + const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer()); + + %extend { + FdndimBlackScholesVanillaEngine( + std::vector > processes, + Matrix rho, + const std::vector& dim, + Size tGrid = 50, + Size dampingSteps = 0, + const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer()) { + return new FdndimBlackScholesVanillaEngine( + processes, rho, to_vector(dim), tGrid, dampingSteps, schemeDesc + ); + } + } }; %{ diff --git a/SWIG/blackformula.i b/SWIG/blackformula.i index 65def4c8..bea231ea 100644 --- a/SWIG/blackformula.i +++ b/SWIG/blackformula.i @@ -10,7 +10,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -29,6 +29,7 @@ using QuantLib::blackFormulaImpliedStdDevLiRS; using QuantLib::blackFormulaCashItmProbability; using QuantLib::blackFormulaAssetItmProbability; using QuantLib::bachelierBlackFormula; +using QuantLib::bachelierBlackFormulaImpliedVolChoi; using QuantLib::bachelierBlackFormulaImpliedVol; using QuantLib::bachelierBlackFormulaAssetItmProbability; %} @@ -112,6 +113,13 @@ Real bachelierBlackFormulaImpliedVol(Option::Type optionType, Real bachelierPrice, Real discount = 1.0); +Real bachelierBlackFormulaImpliedVolChoi(Option::Type optionType, + Real strike, + Real forward, + Real tte, + Real bachelierPrice, + Real discount = 1.0); + Real bachelierBlackFormulaAssetItmProbability( Option::Type optionType, Real strike, diff --git a/SWIG/bondfunctions.i b/SWIG/bondfunctions.i index 23efa337..b24191d4 100644 --- a/SWIG/bondfunctions.i +++ b/SWIG/bondfunctions.i @@ -10,7 +10,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -69,13 +69,16 @@ class BondFunctions { static Real cleanPrice(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); + static Real dirtyPrice(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlementDate = Date()); static Real bps(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); static Rate atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date(), - Real cleanPrice = Null()); + BondPrice price = {}); static Real cleanPrice(const Bond& bond, const InterestRate& yield, @@ -96,7 +99,7 @@ class BondFunctions { Frequency frequency, Date settlementDate = Date()); static Rate yield(const Bond& bond, - Real cleanPrice, + BondPrice price, const DayCounter& dayCounter, Compounding compounding, Frequency frequency, @@ -143,8 +146,23 @@ class BondFunctions { Compounding compounding, Frequency frequency, Date settlementDate = Date()); + + static Real cleanPrice(const Bond& bond, + const ext::shared_ptr& discount, + Spread zSpread, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date()); + static Real dirtyPrice(const Bond& bond, + const ext::shared_ptr& discount, + Spread zSpread, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date()); static Spread zSpread(const Bond& bond, - Real cleanPrice, + BondPrice price, const ext::shared_ptr& discountCurve, const DayCounter& dayCounter, Compounding compounding, @@ -158,18 +176,18 @@ class BondFunctions { %define DefineYieldFunctionSolver(SolverType) static Rate yield ## SolverType(SolverType solver, - const Bond& bond, - Real cleanPrice, - const DayCounter& dayCounter, - Compounding compounding, - Frequency frequency, - Date settlementDate = Date(), - Real accuracy = 1.0e-10, - Rate guess = 0.05) { + const Bond& bond, + BondPrice price, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date(), + Real accuracy = 1.0e-10, + Rate guess = 0.05) { return QuantLib::BondFunctions::yield( solver, bond, - cleanPrice, + price, dayCounter, compounding, frequency, diff --git a/SWIG/bonds.i b/SWIG/bonds.i index 608cc996..0da00bb6 100644 --- a/SWIG/bonds.i +++ b/SWIG/bonds.i @@ -15,7 +15,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -52,6 +52,7 @@ class BondPrice { BondPrice(Real amount, Type type); Real amount() const; Type type() const; + bool isValid() const; }; %shared_ptr(Bond) @@ -73,9 +74,12 @@ class Bond : public Instrument { // public functions Rate nextCouponRate(const Date& d = Date()); Rate previousCouponRate(const Date& d = Date()); + Date nextCashFlowDate(Date d = Date()) const; + Date previousCashFlowDate(Date d = Date()) const; // inspectors Natural settlementDays() const; Date settlementDate(Date d = Date()); + bool isTradable(Date d = Date()) const; Date startDate() const; Date maturityDate() const; Date issueDate() const; @@ -103,13 +107,14 @@ class Bond : public Instrument { Frequency freq, Real accuracy = 1.0e-8, Size maxEvaluations = 100); - Real yield(Real cleanPrice, + Real yield(BondPrice price, const DayCounter& dc, Compounding compounding, Frequency freq, const Date& settlement = Date(), Real accuracy = 1.0e-8, - Size maxEvaluations = 100); + Size maxEvaluations = 100, + Real guess = 0.05); Real accruedAmount(const Date& settlement = Date()); Real settlementValue() const; Real settlementValue(Real cleanPrice) const; diff --git a/SWIG/calendars.i b/SWIG/calendars.i index c79ea278..56fb28d7 100644 --- a/SWIG/calendars.i +++ b/SWIG/calendars.i @@ -13,7 +13,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -64,19 +64,16 @@ enum JointCalendarRule { JoinHolidays, JoinBusinessDays }; #if defined(SWIGPYTHON) %typemap(in) ext::optional %{ - if($input == Py_None) - $1 = ext::nullopt; - else if (PyInt_Check($input)) - $1 = (BusinessDayConvention) PyInt_AsLong($input); - else - $1 = (BusinessDayConvention) PyLong_AsLong($input); + if ($input == Py_None) + $1 = ext::nullopt; + else if (PyLong_Check($input)) + $1 = (BusinessDayConvention)PyLong_AsLong($input); + else + SWIG_exception(SWIG_TypeError, "int expected"); +%} +%typecheck (QL_TYPECHECK_BUSINESSDAYCONVENTION) ext::optional %{ + $1 = (PyLong_Check($input) || $input == Py_None) ? 1 : 0; %} -%typecheck (QL_TYPECHECK_BUSINESSDAYCONVENTION) ext::optional { -if (PyInt_Check($input) || PyLong_Check($input) || Py_None == $input) - $1 = 1; -else - $1 = 0; -} #endif class Calendar { @@ -84,10 +81,12 @@ class Calendar { Calendar(); public: bool isWeekend(Weekday w); + Date startOfMonth(const Date&); Date endOfMonth(const Date&); bool isBusinessDay(const Date&); bool isHoliday(const Date&); bool isEndOfMonth(const Date&); + bool isStartOfMonth(const Date&); void addHoliday(const Date&); void removeHoliday(const Date&); void resetAddedAndRemovedHolidays(); @@ -110,25 +109,23 @@ class Calendar { std::vector businessDayList(const Date& from, const Date& to); std::string name(); + bool empty(); %extend { std::string __str__() { return self->name()+" calendar"; } #if defined(SWIGPYTHON) || defined(SWIGJAVA) - bool __eq__(const Calendar& other) { + bool operator==(const Calendar& other) { return (*self) == other; } - bool __ne__(const Calendar& other) { + bool operator!=(const Calendar& other) { return (*self) != other; } + hash_t __hash__() { + return self->empty() ? 0 : std::hash()(self->name()); + } #endif } - #if defined(SWIGPYTHON) - %pythoncode %{ - def __hash__(self): - return hash(self.name()) - %} - #endif }; namespace std { @@ -230,7 +227,7 @@ namespace QuantLib { class Israel : public Calendar { public: - enum Market { Settlement, TASE }; + enum Market { Settlement, TASE, SHIR }; Israel(Market m = Settlement); }; @@ -248,9 +245,19 @@ namespace QuantLib { Mexico(Market m = BMV); }; - class NewZealand : public Calendar {}; + class NewZealand : public Calendar { + public: + enum Market { Wellington, Auckland }; + NewZealand(Market m = Wellington); + }; + class Norway : public Calendar {}; - class Poland : public Calendar {}; + + class Poland : public Calendar { + public: + enum Market { Settlement, WSE }; + Poland(Market m = Settlement); + }; class Romania : public Calendar { public: diff --git a/SWIG/calibratedmodel.i b/SWIG/calibratedmodel.i index 20688226..1e5c3e8f 100644 --- a/SWIG/calibratedmodel.i +++ b/SWIG/calibratedmodel.i @@ -14,7 +14,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS diff --git a/SWIG/calibrationhelpers.i b/SWIG/calibrationhelpers.i index 1c8db0af..a408efa4 100644 --- a/SWIG/calibrationhelpers.i +++ b/SWIG/calibrationhelpers.i @@ -14,7 +14,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -83,12 +83,13 @@ class SwaptionHelper : public BlackCalibrationHelper { const DayCounter& fixedLegDayCounter, const DayCounter& floatingLegDayCounter, const Handle& termStructure, - BlackCalibrationHelper::CalibrationErrorType errorType - = BlackCalibrationHelper::RelativePriceError, + CalibrationErrorType errorType = RelativePriceError, const Real strike = Null(), const Real nominal = 1.0, const VolatilityType type = ShiftedLognormal, - const Real shift = 0.0); + const Real shift = 0.0, + Natural settlementDays = Null(), + RateAveraging::Type averagingMethod = RateAveraging::Compound); SwaptionHelper(const Date& exerciseDate, const Period& length, const Handle& volatility, @@ -97,12 +98,13 @@ class SwaptionHelper : public BlackCalibrationHelper { const DayCounter& fixedLegDayCounter, const DayCounter& floatingLegDayCounter, const Handle& termStructure, - BlackCalibrationHelper::CalibrationErrorType errorType - = BlackCalibrationHelper::RelativePriceError, + CalibrationErrorType errorType = RelativePriceError, const Real strike = Null(), const Real nominal = 1.0, const VolatilityType type = ShiftedLognormal, - const Real shift = 0.0); + const Real shift = 0.0, + Natural settlementDays = Null(), + RateAveraging::Type averagingMethod = RateAveraging::Compound); SwaptionHelper(const Date& exerciseDate, const Date& endDate, const Handle& volatility, @@ -111,14 +113,15 @@ class SwaptionHelper : public BlackCalibrationHelper { const DayCounter& fixedLegDayCounter, const DayCounter& floatingLegDayCounter, const Handle& termStructure, - BlackCalibrationHelper::CalibrationErrorType errorType - = BlackCalibrationHelper::RelativePriceError, + CalibrationErrorType errorType = RelativePriceError, const Real strike = Null(), const Real nominal = 1.0, const VolatilityType type = ShiftedLognormal, - const Real shift = 0.0); + const Real shift = 0.0, + Natural settlementDays = Null(), + RateAveraging::Type averagingMethod = RateAveraging::Compound); - ext::shared_ptr underlyingSwap() const; + ext::shared_ptr underlying() const; ext::shared_ptr swaption() const; %extend { @@ -133,13 +136,13 @@ class SwaptionHelper : public BlackCalibrationHelper { return self->swaption()->exercise()->date(0); } Real swaptionStrike() { - return self->swaption()->underlyingSwap()->fixedRate(); + return self->swaption()->underlying()->fixedRate(); } Real swaptionNominal() { - return self->swaption()->underlyingSwap()->nominal(); + return self->swaption()->underlying()->nominal(); } Date swaptionMaturityDate() { - return self->swaption()->underlyingSwap()->fixedSchedule().dates().back(); + return self->swaption()->underlying()->fixedSchedule().dates().back(); } } }; diff --git a/SWIG/capfloor.i b/SWIG/capfloor.i index 0bdbbce8..7151cf5b 100644 --- a/SWIG/capfloor.i +++ b/SWIG/capfloor.i @@ -10,7 +10,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -24,6 +24,7 @@ %include marketelements.i %include termstructures.i %include cashflows.i +%include swap.i %include volatilities.i %{ @@ -31,6 +32,7 @@ using QuantLib::CapFloor; using QuantLib::Cap; using QuantLib::Floor; using QuantLib::Collar; +using QuantLib::MakeCapFloor; %} %shared_ptr(CapFloor) @@ -133,4 +135,68 @@ class BachelierCapFloorEngine : public PricingEngine { const Handle& vol); }; +#if defined(SWIGPYTHON) +%rename (_MakeCapFloor) MakeCapFloor; +#endif +class MakeCapFloor { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") MakeCapFloor; + #endif + public: + MakeCapFloor& withNominal(Real n); + + MakeCapFloor& withEffectiveDate(const Date&, bool firstCapletExcluded); + MakeCapFloor& withTenor(const Period&); + MakeCapFloor& withCalendar(const Calendar&); + MakeCapFloor& withConvention(BusinessDayConvention bdc); + MakeCapFloor& withTerminationDateConvention(BusinessDayConvention bdc); + MakeCapFloor& withRule(DateGeneration::Rule r); + MakeCapFloor& withEndOfMonth(bool flag = true); + MakeCapFloor& withFirstDate(const Date&); + MakeCapFloor& withNextToLastDate(const Date&); + MakeCapFloor& withDayCount(const DayCounter&); + + MakeCapFloor& asOptionlet(bool b = true); + + MakeCapFloor& withPricingEngine( + const ext::shared_ptr& engine); + + MakeCapFloor(CapFloor::Type capFloorType, + const Period& capFloorTenor, + const ext::shared_ptr& iborIndex, + doubleOrNull strike = Null(), + const Period& forwardStart = 0*Days); + + %extend { + ext::shared_ptr makeCapFloor() { + return (ext::shared_ptr)(* $self); + } + } +}; + +#if defined(SWIGPYTHON) +%pythoncode { +_MAKECAPFLOOR_METHODS = { + "nominal": "withNominal", + "effectiveDate": "withEffectiveDate", + "tenor": "withTenor", + "calendar": "withCalendar", + "convention": "withConvention", + "terminationDateConvention": "withTerminationDateConvention", + "rule": "withRule", + "endOfMonth": "withEndOfMonth", + "firstDate": "withFirstDate", + "nextToLastDate": "withNextToLastDate", + "dayCount": "withDayCount", + "asOptionlet": "asOptionlet", + "pricingEngine": "withPricingEngine", +} + +def MakeCapFloor(capFloorType, capFloorTenor, iborIndex, strike=None, forwardStart=Period(0, Days), **kwargs): + mv = _MakeCapFloor(capFloorType, capFloorTenor, iborIndex, strike, forwardStart) + _apply_kwargs("MakeCapFloor", _MAKECAPFLOOR_METHODS, mv, kwargs) + return mv.makeCapFloor() +} +#endif + #endif diff --git a/SWIG/cashflows.i b/SWIG/cashflows.i index a21f61b1..773da414 100644 --- a/SWIG/cashflows.i +++ b/SWIG/cashflows.i @@ -13,7 +13,7 @@ under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at - . + . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS @@ -70,8 +70,6 @@ using QuantLib::AmortizingPayment; using QuantLib::Coupon; using QuantLib::FixedRateCoupon; using QuantLib::FloatingRateCoupon; -using QuantLib::RateAveraging; -using QuantLib::OvernightIndexedCoupon; %} %shared_ptr(SimpleCashFlow) @@ -212,6 +210,38 @@ class FloatingRateCoupon : public Coupon { %} +%{ +using QuantLib::CappedFlooredCoupon; +%} + +%shared_ptr(CappedFlooredCoupon) +class CappedFlooredCoupon : public FloatingRateCoupon { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") CappedFlooredCoupon; + #endif + public: + CappedFlooredCoupon(const ext::shared_ptr& underlying, + Rate cap = Null(), + Rate floor = Null()); + Rate cap() const; + Rate floor() const; + Rate effectiveCap() const; + Rate effectiveFloor() const; + bool isCapped() const; + bool isFloored() const; + void setPricer(const ext::shared_ptr& p); +}; + + +// specialized floating-rate coupons + + +%{ +using QuantLib::RateAveraging; +using QuantLib::OvernightIndexedCoupon; +using QuantLib::CappedFlooredOvernightIndexedCoupon; +%} + struct RateAveraging { enum Type { Simple, @@ -222,6 +252,9 @@ struct RateAveraging { %shared_ptr(OvernightIndexedCoupon) class OvernightIndexedCoupon : public FloatingRateCoupon { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") OvernightIndexedCoupon; + #endif public: OvernightIndexedCoupon( const Date& paymentDate, @@ -235,48 +268,69 @@ class OvernightIndexedCoupon : public FloatingRateCoupon { const Date& refPeriodEnd = Date(), const DayCounter& dayCounter = DayCounter(), bool telescopicValueDates = false, - RateAveraging::Type averagingMethod = RateAveraging::Compound); + RateAveraging::Type averagingMethod = RateAveraging::Compound, + Natural lookbackDays = Null(), + Natural lockoutDays = 0, + bool applyObservationShift = false, + bool compoundSpread = false, + const Date& rateComputationStartDate = Date(), + const Date& rateComputationEndDate = Date()); const std::vector& fixingDates() const; + const std::vector& interestDates() const; const std::vector