From fb739d88e3d9877747506e46e9badcf61e775c2f Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:24:51 +0000 Subject: [PATCH 1/6] build: scikit-build-core, Python 3.10-3.14, xad 2.1, new CI One-step pip build of QuantLib + XAD + QuantLibAAD + SWIG module. Submodules: QuantLibAAD (replaces QuantLib-Risks-Cpp), xad v2.1.0, pybind11 v3.1.0 (same as xad-py). Requires xad==2.1.*. Wheels: Linux x86_64/aarch64 (glibc+musl), macOS x86_64/arm64, Windows. --- .gitattributes | 2 +- .github/dependabot.yml | 20 ++- .github/workflows/quantlib-risks.yml | 188 +++++++++++++-------------- .gitignore | 18 +-- .gitmodules | 9 +- CMakeLists.txt | 54 +++++--- CONTRIBUTING.md | 49 ++++++- Python/CMakeLists.txt | 38 +----- Python/QuantLib_Risks/CMakeLists.txt | 82 ++++-------- Python/README.md | 19 ++- Python/build_extensions.py.in | 88 ------------- Python/pyproject.toml.in | 96 -------------- cmake/PrebuiltQuantLib.cmake | 22 ++++ cmake/QLRBoost.cmake | 38 ++++++ cmake/QuantLibFromSource.cmake | 67 ++++++++++ lib/QuantLib-Risks-Cpp | 1 - lib/QuantLibAAD | 1 + lib/pybind11 | 1 + lib/xad | 2 +- pyproject.toml | 124 ++++++++++++++++++ tools/build-quantlib.cmake | 54 ++++++++ tools/prebuild_ql-risks.bat | 27 ---- tools/prebuild_ql-risks.sh | 153 ---------------------- 23 files changed, 557 insertions(+), 596 deletions(-) delete mode 100644 Python/build_extensions.py.in delete mode 100644 Python/pyproject.toml.in create mode 100644 cmake/PrebuiltQuantLib.cmake create mode 100644 cmake/QLRBoost.cmake create mode 100644 cmake/QuantLibFromSource.cmake delete mode 160000 lib/QuantLib-Risks-Cpp create mode 160000 lib/QuantLibAAD create mode 160000 lib/pybind11 create mode 100644 pyproject.toml create mode 100644 tools/build-quantlib.cmake delete mode 100644 tools/prebuild_ql-risks.bat delete mode 100755 tools/prebuild_ql-risks.sh 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..1b9ed562 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 || apk add --no-cache ccache + 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/CMakeLists.txt b/CMakeLists.txt index 5ecf3ea9..92f56824 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) +cmake_minimum_required(VERSION 3.18...4.2) 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) +set(QLR_VERSION ${PROJECT_VERSION}) +set(QLR_HEX_VERSION "0x013303f0") + +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/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/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/cmake/PrebuiltQuantLib.cmake b/cmake/PrebuiltQuantLib.cmake new file mode 100644 index 00000000..b65edf99 --- /dev/null +++ b/cmake/PrebuiltQuantLib.cmake @@ -0,0 +1,22 @@ +# Uses QuantLib, XAD and QuantLibAAD installed by a QLR_QUANTLIB_ONLY build in +# QLR_QUANTLIB_PREFIX. Defines QuantLib::QuantLib. + +file(TO_CMAKE_PATH "${QLR_QUANTLIB_PREFIX}" QLR_QUANTLIB_PREFIX) +list(PREPEND CMAKE_PREFIX_PATH "${QLR_QUANTLIB_PREFIX}") +find_package(QuantLibAAD CONFIG REQUIRED NO_DEFAULT_PATH PATHS "${QLR_QUANTLIB_PREFIX}") + +# guard against a stale prefix: XAD must match lib/xad and share the tape with xad-py +file(READ "${PROJECT_SOURCE_DIR}/lib/xad/VERSION" xad_src_version) +string(STRIP "${xad_src_version}" xad_src_version) +get_target_property(xad_includes XAD::xad INTERFACE_INCLUDE_DIRECTORIES) +set(xad_no_tls) +foreach(dir IN LISTS xad_includes) + if(EXISTS "${dir}/XAD/Config.hpp") + file(STRINGS "${dir}/XAD/Config.hpp" xad_no_tls REGEX "^#define XAD_NO_THREADLOCAL") + endif() +endforeach() +if(NOT XAD_VERSION STREQUAL xad_src_version OR NOT xad_no_tls) + message(FATAL_ERROR "XAD in ${QLR_QUANTLIB_PREFIX} (${XAD_VERSION}) does not match " + "lib/xad (${xad_src_version}) with XAD_NO_THREADLOCAL; rebuild it") +endif() +message(STATUS "Using prebuilt QuantLib ${QuantLib_VERSION} and XAD ${XAD_VERSION} from ${QLR_QUANTLIB_PREFIX}") diff --git a/cmake/QLRBoost.cmake b/cmake/QLRBoost.cmake new file mode 100644 index 00000000..696eed76 --- /dev/null +++ b/cmake/QLRBoost.cmake @@ -0,0 +1,38 @@ +# Boost headers for QuantLib: the system's (or BOOST_ROOT) if found, else the +# pinned release is downloaded. +# Sets QLR_BOOST_INCLUDE_DIR and QLR_BOOST_DOWNLOADED. + +set(QLR_BOOST_URL "https://archives.boost.io/release/1.90.0/source/boost_1_90_0.tar.bz2" + CACHE STRING "Boost source archive, downloaded if no Boost headers are found") +set(QLR_BOOST_SHA256 "49551aff3b22cbc5c5a9ed3dbc92f0e23ea50a0f7325b0d198b705e8ee3fc305" + CACHE STRING "SHA256 of QLR_BOOST_URL") + +find_path(QLR_BOOST_INCLUDE_DIR boost/version.hpp + HINTS ${BOOST_ROOT} ${Boost_ROOT} ENV BOOST_ROOT ENV Boost_ROOT + PATH_SUFFIXES include) + +set(QLR_BOOST_DOWNLOADED OFF) +if(NOT QLR_BOOST_INCLUDE_DIR) + set(boost_dir "${CMAKE_BINARY_DIR}/_deps/boost") + if(NOT EXISTS "${boost_dir}/boost/version.hpp") + message(STATUS "Boost not found, downloading ${QLR_BOOST_URL}") + get_filename_component(archive_name "${QLR_BOOST_URL}" NAME) + set(archive "${CMAKE_BINARY_DIR}/_deps/${archive_name}") + file(DOWNLOAD "${QLR_BOOST_URL}" "${archive}" + EXPECTED_HASH SHA256=${QLR_BOOST_SHA256} STATUS status) + list(GET status 0 code) + if(NOT code EQUAL 0) + message(FATAL_ERROR "Downloading ${QLR_BOOST_URL} failed: ${status}") + endif() + # headers only + set(extract_dir "${CMAKE_BINARY_DIR}/_deps/boost-extract") + file(REMOVE_RECURSE "${extract_dir}" "${boost_dir}") + file(ARCHIVE_EXTRACT INPUT "${archive}" DESTINATION "${extract_dir}" PATTERNS "*/boost/*") + file(GLOB top "${extract_dir}/boost_*") + file(RENAME "${top}" "${boost_dir}") + file(REMOVE_RECURSE "${extract_dir}" "${archive}") + endif() + set(QLR_BOOST_INCLUDE_DIR "${boost_dir}") + set(QLR_BOOST_DOWNLOADED ON) +endif() +message(STATUS "Boost headers: ${QLR_BOOST_INCLUDE_DIR}") diff --git a/cmake/QuantLibFromSource.cmake b/cmake/QuantLibFromSource.cmake new file mode 100644 index 00000000..9fdd2cb3 --- /dev/null +++ b/cmake/QuantLibFromSource.cmake @@ -0,0 +1,67 @@ +# Builds QuantLib as a static library with XAD and QuantLibAAD from lib/. +# Defines QuantLib::QuantLib. With QLR_QUANTLIB_ONLY, builds and installs only these. + +set(QLR_LIB_DIR "${PROJECT_SOURCE_DIR}/lib") + +include(QLRBoost) + +# XAD: the tape is shared with xad-py, so this must match its configuration +set(XAD_ENABLE_TESTS OFF CACHE BOOL "" FORCE) +set(XAD_NO_THREADLOCAL ON CACHE BOOL "" FORCE) +set(XAD_STATIC_MSVC_RUNTIME ON CACHE BOOL "" FORCE) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$") + set(XAD_SIMD_OPTION AVX CACHE STRING "XAD SIMD instruction set") +else() + # no arch flags, XAD would pick -mavx on aarch64 Linux + set(XAD_SIMD_OPTION NONE CACHE STRING "XAD SIMD instruction set") +endif() + +# QuantLib: static, no tests/examples/benchmark +set(BUILD_SHARED_LIBS OFF) +foreach(opt QL_BUILD_TEST_SUITE QL_BUILD_EXAMPLES QL_BUILD_BENCHMARK + QL_INSTALL_TEST_SUITE QL_INSTALL_EXAMPLES QL_INSTALL_BENCHMARK) + set(${opt} OFF CACHE BOOL "" FORCE) +endforeach() +set(QL_NULL_AS_FUNCTIONS ON CACHE BOOL "" FORCE) +set(QL_EXTERNAL_SUBDIRECTORIES "${QLR_LIB_DIR}/xad;${QLR_LIB_DIR}/QuantLibAAD" CACHE STRING "" FORCE) +set(QL_EXTRA_LINK_LIBRARIES QuantLibAAD CACHE STRING "" FORCE) +# QuantLib's find_package(Boost) (FindBoost module) uses the headers from QLRBoost +set(CMAKE_POLICY_DEFAULT_CMP0167 OLD) +set(Boost_NO_BOOST_CMAKE ON) +set(Boost_INCLUDE_DIR "${QLR_BOOST_INCLUDE_DIR}" CACHE PATH "" FORCE) +if(MSVC AND CMAKE_GENERATOR MATCHES "Visual Studio") + add_compile_options(/MP) # parallel compilation within the QuantLib project +endif() + +add_subdirectory("${QLR_LIB_DIR}/QuantLib" "${CMAKE_BINARY_DIR}/QuantLib") +# QuantLibAAD always adds its examples (which need a newer QuantLib) and test-suite +foreach(dir Examples test-suite) + if(EXISTS "${QLR_LIB_DIR}/QuantLibAAD/${dir}/CMakeLists.txt") + set_property(DIRECTORY "${QLR_LIB_DIR}/QuantLibAAD/${dir}" PROPERTY EXCLUDE_FROM_ALL ON) + endif() +endforeach() + +if(NOT TARGET QuantLib::QuantLib) + add_library(QuantLib::QuantLib ALIAS ql_library) +endif() + +if(QLR_BOOST_DOWNLOADED) + # downloaded headers live in the build tree: build-only include path, + # installed next to QuantLib's headers instead + foreach(prop INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + get_target_property(dirs ql_library ${prop}) + set(new_dirs) + foreach(dir IN LISTS dirs) + if(dir STREQUAL QLR_BOOST_INCLUDE_DIR) + set(dir "$") + endif() + list(APPEND new_dirs "${dir}") + endforeach() + if(dirs) + set_target_properties(ql_library PROPERTIES ${prop} "${new_dirs}") + endif() + endforeach() + if(QLR_QUANTLIB_ONLY) + install(DIRECTORY "${QLR_BOOST_INCLUDE_DIR}/boost" DESTINATION "${QL_INSTALL_INCLUDEDIR}") + endif() +endif() diff --git a/lib/QuantLib-Risks-Cpp b/lib/QuantLib-Risks-Cpp deleted file mode 160000 index 093ea495..00000000 --- a/lib/QuantLib-Risks-Cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 093ea495bab5a405a3f7f5622f2d4e8976ba3553 diff --git a/lib/QuantLibAAD b/lib/QuantLibAAD new file mode 160000 index 00000000..74a892e5 --- /dev/null +++ b/lib/QuantLibAAD @@ -0,0 +1 @@ +Subproject commit 74a892e528db7bbb50d587c268a687a3a8fb032e diff --git a/lib/pybind11 b/lib/pybind11 new file mode 160000 index 00000000..97bf890d --- /dev/null +++ b/lib/pybind11 @@ -0,0 +1 @@ +Subproject commit 97bf890db679505a14dfe547a5e77bb2bd05dc90 diff --git a/lib/xad b/lib/xad index 8461e93e..71b381ad 160000 --- a/lib/xad +++ b/lib/xad @@ -1 +1 @@ -Subproject commit 8461e93e1936e357488f0e8a54519cdb5e02da0a +Subproject commit 71b381ad4a322d7ed5eb8b2b4b79a20c92c646c6 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..8d646966 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,124 @@ +############################################################################## +# +# QuantLib-Risks - package and build configuration +# +# 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 . +# +############################################################################## + +[build-system] +# swig 4.5 drops the PyInt_* macros used in SWIG/ +requires = ["scikit-build-core>=0.11", "swig>=4.4,<4.5"] +build-backend = "scikit_build_core.build" + +[project] +name = "QuantLib-Risks" +# keep in sync with CMakeLists.txt +version = "1.33.3" +description = "Fast risks calculations in QuantLib" +readme = "Python/README.md" +requires-python = ">=3.10" +license = { text = "AGPL-3.0-or-later" } +authors = [ + { name = "Auto Differentiation Dev Team", email = "dev@auto-differentiation.com" }, + { name = "QuantLib Team", email = "quantlib-users@lists.sourceforge.net" }, +] +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.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", +] +# the binary module shares xad's tape and Real type: same XAD/pybind11 build +dependencies = ["xad==2.1.*"] + +[project.urls] +Homepage = "https://auto-differentiation.github.io" +Repository = "https://github.com/auto-differentiation/QuantLib-Risks-Py" +Documentation = "https://auto-differentiation.github.io/quantlib-risks/python/" +Download = "https://pypi.org/project/QuantLib-Risks/#files" +Tracker = "https://github.com/auto-differentiation/QuantLib-Risks-Py/issues" + +[dependency-groups] +test = ["pytest"] + +[tool.scikit-build] +minimum-version = "build-system.requires" +cmake.version = ">=3.18" +build.verbose = true +# the package is installed by CMake (Python/QuantLib_Risks/CMakeLists.txt) +wheel.packages = [] +install.components = ["python"] +# submodule parts not needed to build +sdist.exclude = [ + ".github", + "binder", + "build", + "lib/QuantLib/Docs", + "lib/QuantLib/Examples", + "lib/QuantLib/test-suite", + "lib/QuantLib/fuzz-test-suite", + "lib/xad/docs", + "lib/xad/samples", + "lib/xad/test", + "lib/pybind11/docs", + "lib/pybind11/tests", +] + +[tool.cibuildwheel] +build = ["cp310-*", "cp311-*", "cp312-*", "cp313-*", "cp314-*"] +skip = ["cp3??t-*"] # no free-threaded builds +archs = ["native"] # one arch per runner +build-verbosity = 1 +# QuantLib (with XAD and QuantLibAAD) is built once per platform and reused +# by all wheels through QLR_QUANTLIB_PREFIX +before-all = "cmake -DPREFIX={project}/build/quantlib-prefix -P {project}/tools/build-quantlib.cmake" +config-settings = { "cmake.define.QLR_QUANTLIB_PREFIX" = "{project}/build/quantlib-prefix" } +test-groups = ["test"] +# importlib mode tests the installed package; Python/test would clash with the stdlib test package +test-command = "pytest -p no:cacheprovider --import-mode=importlib {project}/Python/test" + +[tool.cibuildwheel.macos] +# one target for the QuantLib build (before-all) and all wheels (raised to 11.0 on arm64) +environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } diff --git a/tools/build-quantlib.cmake b/tools/build-quantlib.cmake new file mode 100644 index 00000000..dee64314 --- /dev/null +++ b/tools/build-quantlib.cmake @@ -0,0 +1,54 @@ +############################################################################## +# Builds and installs QuantLib + XAD + QuantLibAAD into PREFIX, for wheel +# builds with -DQLR_QUANTLIB_PREFIX= (cibuildwheel before-all): +# +# cmake -DPREFIX= [-DBUILD_DIR=] -P tools/build-quantlib.cmake +# +# 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 . +# +############################################################################## + +cmake_minimum_required(VERSION 3.19) + +if(NOT PREFIX) + message(FATAL_ERROR "usage: cmake -DPREFIX= [-DBUILD_DIR=] -P ${CMAKE_SCRIPT_MODE_FILE}") +endif() +get_filename_component(source_dir "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) +if(NOT BUILD_DIR) + set(BUILD_DIR "${source_dir}/build/quantlib") +endif() +# relative to the working directory (cibuildwheel passes {project} as "." on macOS/Windows) +foreach(dir PREFIX BUILD_DIR) + file(TO_CMAKE_PATH "${${dir}}" ${dir}) + get_filename_component(${dir} "${${dir}}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") +endforeach() + +if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) + set(jobs $ENV{CMAKE_BUILD_PARALLEL_LEVEL}) +else() + cmake_host_system_information(RESULT jobs QUERY NUMBER_OF_LOGICAL_CORES) +endif() + +foreach(step + "-S;${source_dir};-B;${BUILD_DIR};-DQLR_QUANTLIB_ONLY=ON;-DCMAKE_BUILD_TYPE=Release;-DCMAKE_INSTALL_PREFIX=${PREFIX}" + "--build;${BUILD_DIR};--config;Release;--parallel;${jobs}" + "--install;${BUILD_DIR};--config;Release") + execute_process(COMMAND ${CMAKE_COMMAND} ${step} COMMAND_ERROR_IS_FATAL ANY) +endforeach() diff --git a/tools/prebuild_ql-risks.bat b/tools/prebuild_ql-risks.bat deleted file mode 100644 index e8c6d04a..00000000 --- a/tools/prebuild_ql-risks.bat +++ /dev/null @@ -1,27 +0,0 @@ -:: ############################################################################# -:: -:: -:: 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 . -:: -:: ############################################################################# - -pip install delvewheel -call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64 -vcvars_ver=14.3 -"C:\Program Files\Git\bin\bash.exe" tools/prebuild_ql-risks.sh || exit 1 \ No newline at end of file diff --git a/tools/prebuild_ql-risks.sh b/tools/prebuild_ql-risks.sh deleted file mode 100755 index 722eac6d..00000000 --- a/tools/prebuild_ql-risks.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/bash - -############################################################################## -# -# -# 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 . -# -############################################################################## - -# exit on errors immediately -set -e - -if [ "$CMAKE_INSTALL_PREFIX" == "" ] ; then - export CMAKE_INSTALL_PREFIX=$(pwd)/build/prefix -fi - -if [ "$QL_PRESET" == "" ] ; then - export QL_PRESET=linux-xad-gcc-ninja-release -fi - -if [ "$CCACHE_EXE" != "" ] ; then - export CCACHE_OPTION="-DCMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_EXE" -fi - -if [ "$QLR_PYTHON_VERSION" == "cp38" ] ; then - export PYTHON_OPTION="-DQLR_PYTHON_VERSION=3.8" -elif [ "$QLR_PYTHON_VERSION" == "cp39" ] ; then - export PYTHON_OPTION="-DQLR_PYTHON_VERSION=3.9" -elif [ "$QLR_PYTHON_VERSION" == "cp310" ] ; then - export PYTHON_OPTION="-DQLR_PYTHON_VERSION=3.10" -elif [ "$QLR_PYTHON_VERSION" == "cp311" ] ; then - export PYTHON_OPTION="-DQLR_PYTHON_VERSION=3.11" -elif [ "$QLR_PYTHON_VERSION" == "cp312" ] ; then - export PYTHON_OPTION="-DQLR_PYTHON_VERSION=3.12" -fi - -export QL_DIR=$(pwd)/lib/QuantLib -export QLRISKS_DIR=$(pwd)/lib/QuantLib-Risks-Cpp -export XAD_DIR=$(pwd)/lib/XAD -export QLSWIG_DIR=$(pwd) - -echo "Step 0: Package setup" -case "$(uname -sr)" in - Darwin*) - machine="macos" - ;; - - Linux*Microsoft*) - machine="linux" - ;; - - Linux*) - machine="linux" - ;; - - CYGWIN*|MINGW*|MINGW32*|MSYS*) - machine="windows" - ;; - - *) - echo 'Other OS' - exit 1 - ;; -esac - -if [ "$machine" == "linux" ] && [ "$CIBUILDWHEEL" == "1" ] ; then - # Linux builds in a container, so we need to install boost there - curl -O -L https://boostorg.jfrog.io/artifactory/main/release/1.84.0/source/boost_1_84_0.tar.gz - tar xfz boost_*.tar.gz - cd boost_*/ - mv boost /usr/local/include/ - cd .. - - if which yum ; then - yum -y install ninja-build ccache - else - apk add --no-cache ninja ccache - fi - - export CCACHE_OPTION="-DCMAKE_CXX_COMPILER_LAUNCHER=ccache" - ccache -o cache_dir="/host$HOST_CCACHE_DIR" -fi - -echo "Step 1: Build QuantLib with XAD support" -cd "$QL_DIR" -# this is required in v1.33 - remove after moving to 1.34 -git config user.email "41898282+github-actions[bot]@users.noreply.github.com" -git config user.name "github-actions[bot]" -git log | grep -q "Uses Real in GemoetricBrownianMotionProcess instead of double" || (git fetch --all && git cherry-pick 6bb9c1f18ff6d4c47f06a66136fb83411207e67c) -if [ "$machine" != "macos" ] ; then - cp -f "$QLRISKS_DIR/presets/CMakeUserPresets.json" . - cmake --preset "$QL_PRESET" \ - -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" \ - -DXAD_ENABLE_TESTS=OFF \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - $CCACHE_OPTION -else - # we don't have working presets for Intel Mac - mkdir -p build/$QL_PRESET - cmake -B build/$QL_PRESET -S . -GNinja \ - -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" \ - -DXAD_ENABLE_TESTS=OFF \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - $CCACHE_OPTION \ - -DCMAKE_BUILD_TYPE=Release \ - -DQLXAD_DISABLE_AAD=OFF \ - -DQL_EXTERNAL_SUBDIRECTORIES="$XAD_DIR;$QLRISKS_DIR" \ - -DQL_EXTRA_LINK_LIBRARIES="QuantLib-Risks" \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DXAD_NO_THREADLOCAL=ON -fi - -cd "build/$QL_PRESET" -cmake --build . -cmake --install . - -echo "Step 2: Build Python bindings" -cd "$QLSWIG_DIR" -mkdir -p "build/$QL_PRESET" -cd "build/$QL_PRESET" -cmake -G Ninja ../.. \ - -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" \ - -DCMAKE_BUILD_TYPE=Release \ - $CCACHE_OPTION $PYTHON_OPTION -cmake --build . - -echo "Now ready to build the pypi package in $QLSWIG_DIR/build/$QL_PRESET/Python ..." - - - - - From 8a35533865e965b2401ed4acd6cfd82dc7cdcf14 Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:43:57 +0000 Subject: [PATCH 2/6] tools: sync-swig.sh, XAD changes as SWIG/patches --- SWIG/patches/creditdefaultswap.i.patch | 12 ++ SWIG/patches/date.i.patch | 12 ++ SWIG/patches/fdm.i.patch | 154 +++++++++++++++++++++++ SWIG/patches/functions.i.patch | 93 ++++++++++++++ SWIG/patches/linearalgebra.i.patch | 63 ++++++++++ SWIG/patches/null.i.patch | 88 +++++++++++++ SWIG/patches/ode.i.patch | 37 ++++++ SWIG/patches/options.i.patch | 12 ++ SWIG/patches/piecewiseyieldcurve.i.patch | 63 ++++++++++ SWIG/patches/ql.i.patch | 13 ++ SWIG/patches/quantlib.i.patch | 26 ++++ SWIG/patches/timebasket.i.patch | 15 +++ SWIG/patches/types.i.patch | 59 +++++++++ SWIG/patches/vectors.i.patch | 46 +++++++ SWIG/vectors.i | 2 +- tools/sync-swig.sh | 115 +++++++++++++++++ 16 files changed, 809 insertions(+), 1 deletion(-) create mode 100644 SWIG/patches/creditdefaultswap.i.patch create mode 100644 SWIG/patches/date.i.patch create mode 100644 SWIG/patches/fdm.i.patch create mode 100644 SWIG/patches/functions.i.patch create mode 100644 SWIG/patches/linearalgebra.i.patch create mode 100644 SWIG/patches/null.i.patch create mode 100644 SWIG/patches/ode.i.patch create mode 100644 SWIG/patches/options.i.patch create mode 100644 SWIG/patches/piecewiseyieldcurve.i.patch create mode 100644 SWIG/patches/ql.i.patch create mode 100644 SWIG/patches/quantlib.i.patch create mode 100644 SWIG/patches/timebasket.i.patch create mode 100644 SWIG/patches/types.i.patch create mode 100644 SWIG/patches/vectors.i.patch create mode 100755 tools/sync-swig.sh diff --git a/SWIG/patches/creditdefaultswap.i.patch b/SWIG/patches/creditdefaultswap.i.patch new file mode 100644 index 00000000..9afb37d5 --- /dev/null +++ b/SWIG/patches/creditdefaultswap.i.patch @@ -0,0 +1,12 @@ +diff --git a/SWIG/creditdefaultswap.i b/SWIG/creditdefaultswap.i +--- a/SWIG/creditdefaultswap.i ++++ b/SWIG/creditdefaultswap.i +@@ -110,7 +110,7 @@ class CreditDefaultSwap : public Instrument { + if (result) + return *result; + else +- return Null(); ++ return Null(); + } + } + bool settlesAccrual() const; diff --git a/SWIG/patches/date.i.patch b/SWIG/patches/date.i.patch new file mode 100644 index 00000000..25b234c3 --- /dev/null +++ b/SWIG/patches/date.i.patch @@ -0,0 +1,12 @@ +diff --git a/SWIG/date.i b/SWIG/date.i +--- a/SWIG/date.i ++++ b/SWIG/date.i +@@ -922,4 +922,8 @@ struct ASX { + const Date& referenceDate = Date()); + }; + ++#ifdef QL_XAD ++%include date_extra.i ++#endif ++ + #endif diff --git a/SWIG/patches/fdm.i.patch b/SWIG/patches/fdm.i.patch new file mode 100644 index 00000000..d1ff7e92 --- /dev/null +++ b/SWIG/patches/fdm.i.patch @@ -0,0 +1,154 @@ +diff --git a/SWIG/fdm.i b/SWIG/fdm.i +--- a/SWIG/fdm.i ++++ b/SWIG/fdm.i +@@ -98,9 +98,17 @@ class FdmBlackScholesMesher : public Fdm1dMesher { + Volatility vol); + }; + ++#ifndef QL_XAD ++ + %template(Concentrating1dMesherPoint) ext::tuple; + %template(Concentrating1dMesherPointVector) std::vector >; + ++#else ++ ++%include fdm_extra.i ++ ++#endif ++ + + %shared_ptr(Concentrating1dMesher) + class Concentrating1dMesher : public Fdm1dMesher { +@@ -420,8 +428,20 @@ class FdmLinearOpCompositeProxy : public FdmLinearOpComposite { + } + + void setTime(Time t1, Time t2) { ++ #ifdef QL_XAD ++ PyObject* t1o = make_PyObject(t1); ++ PyObject* t2o = make_PyObject(t2); ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_,"setTime","OO", t1o, t2o); ++ Py_XDECREF(t1o); ++ Py_XDECREF(t2o); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ #else + PyObject* pyResult + = PyObject_CallMethod(callback_,"setTime","dd", t1, t2); ++ #endif + + QL_ENSURE(pyResult != NULL, + "failed to call setTime() on Python object"); +@@ -454,11 +474,25 @@ class FdmLinearOpCompositeProxy : public FdmLinearOpComposite { + PyObject* pyArray = SWIG_NewPointerObj( + SWIG_as_voidptr(&r), SWIGTYPE_p_Array, 0); + ++ #ifdef QL_XAD ++ PyObject* so = make_PyObject(s); ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_, "solve_splitting", "kOO", ++ (unsigned long)(direction), pyArray, so); ++ ++ Py_XDECREF(pyArray); ++ Py_XDECREF(so); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != nullptr, "Error calling function callback"); ++ #else + PyObject* pyResult + = PyObject_CallMethod(callback_, "solve_splitting", "kOd", + (unsigned long)(direction), pyArray, s); + + Py_XDECREF(pyArray); ++ #endif + + return extractArray(pyResult, "solve_splitting"); + } +@@ -466,12 +500,25 @@ class FdmLinearOpCompositeProxy : public FdmLinearOpComposite { + Array preconditioner(const Array& r, Real s) const { + PyObject* pyArray = SWIG_NewPointerObj( + SWIG_as_voidptr(&r), SWIGTYPE_p_Array, 0); ++ #ifdef QL_XAD ++ PyObject* so = make_PyObject(s); ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_, "preconditioner", "OO",pyArray, so); + ++ Py_XDECREF(pyArray); ++ Py_XDECREF(so); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != nullptr, "Error calling function callback"); ++ ++ #else + PyObject* pyResult + = PyObject_CallMethod(callback_, "preconditioner", "Od",pyArray, s); + + Py_XDECREF(pyArray); + ++ #endif + return extractArray(pyResult, "preconditioner"); + } + +@@ -1145,10 +1192,22 @@ class FdmStepConditionProxy : public StepCondition { + PyObject* pyArray = SWIG_NewPointerObj( + SWIG_as_voidptr(&a), SWIGTYPE_p_Array, 0); + ++ #ifdef QL_XAD ++ PyObject* to = make_PyObject(t); ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_, "applyTo", "OO",pyArray, to); ++ ++ Py_XDECREF(pyArray); ++ Py_XDECREF(to); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ #else + PyObject* pyResult + = PyObject_CallMethod(callback_, "applyTo", "Od",pyArray, t); + + Py_XDECREF(pyArray); ++ #endif + } + + private: +@@ -1255,6 +1314,28 @@ class FdmInnerValueCalculatorProxy : public FdmInnerValueCalculator { + PyObject* pyIter = SWIG_NewPointerObj( + SWIG_as_voidptr(&iter), SWIGTYPE_p_FdmLinearOpIterator, 0); + ++#ifdef QL_XAD ++ PyObject* to = make_PyObject(t); ++ ++#if !defined(PY_VERSION_HEX) || PY_VERSION_HEX < 0x03040000 ++ std::vector cstr( ++ methodName.c_str(), methodName.c_str() + methodName.size() + 1); ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_, &cstr[0], "OO",pyIter, to); ++#else ++ PyObject* pyResult ++ = PyObject_CallMethod(callback_, methodName.c_str(), "OO", pyIter, to); ++#endif ++ ++ Py_XDECREF(pyIter); ++ Py_XDECREF(to); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != NULL, "failed to call innerValue function on Python object"); ++ ++ const Real result = make_Real(pyResult); ++#else + #if !defined(PY_VERSION_HEX) || PY_VERSION_HEX < 0x03040000 + std::vector cstr( + methodName.c_str(), methodName.c_str() + methodName.size() + 1); +@@ -1270,6 +1351,7 @@ class FdmInnerValueCalculatorProxy : public FdmInnerValueCalculator { + QL_ENSURE(pyResult != NULL, "failed to call innerValue function on Python object"); + + const Real result = PyFloat_AsDouble(pyResult); ++#endif + + Py_XDECREF(pyResult); + diff --git a/SWIG/patches/functions.i.patch b/SWIG/patches/functions.i.patch new file mode 100644 index 00000000..70c7270d --- /dev/null +++ b/SWIG/patches/functions.i.patch @@ -0,0 +1,93 @@ +diff --git a/SWIG/functions.i b/SWIG/functions.i +--- a/SWIG/functions.i ++++ b/SWIG/functions.i +@@ -50,18 +50,42 @@ class UnaryFunction { + Py_XDECREF(function_); + } + Real operator()(Real x) const { ++#ifdef QL_XAD ++ PyObject* xo = make_PyObject(x); ++ PyObject* pyResult = PyObject_CallFunctionObjArgs(function_, xo, nullptr); ++ Py_XDECREF(xo); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != NULL, "failed to call Python function"); ++ Real result = make_Real(pyResult); ++#else + PyObject* pyResult = PyObject_CallFunction(function_,"d",x); + QL_ENSURE(pyResult != NULL, "failed to call Python function"); + Real result = PyFloat_AsDouble(pyResult); ++#endif + Py_XDECREF(pyResult); + return result; + } + Real derivative(Real x) const { ++#ifdef QL_XAD ++ PyObject* xo = make_PyObject(x); ++ PyObject* pyResult = ++ PyObject_CallMethod(function_,"derivative", "O", xo); ++ Py_XDECREF(xo); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != NULL, ++ "failed to call derivative() on Python object"); ++ Real result = make_Real(pyResult); ++#else + PyObject* pyResult = + PyObject_CallMethod(function_,"derivative","d",x); + QL_ENSURE(pyResult != NULL, + "failed to call derivative() on Python object"); + Real result = PyFloat_AsDouble(pyResult); ++#endif + Py_XDECREF(pyResult); + return result; + } +@@ -90,9 +114,21 @@ class BinaryFunction { + Py_XDECREF(function_); + } + Real operator()(Real x, Real y) const { ++#ifdef QL_XAD ++ PyObject *xo = make_PyObject(x), *yo = make_PyObject(y); ++ PyObject* pyResult = PyObject_CallFunctionObjArgs(function_,xo, yo, nullptr); ++ Py_XDECREF(xo); ++ Py_XDECREF(yo); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ QL_ENSURE(pyResult != NULL, "failed to call Python function"); ++ Real result = make_Real(pyResult); ++#else + PyObject* pyResult = PyObject_CallFunction(function_,"dd",x,y); + QL_ENSURE(pyResult != NULL, "failed to call Python function"); + Real result = PyFloat_AsDouble(pyResult); ++#endif + Py_XDECREF(pyResult); + return result; + } +@@ -122,12 +158,24 @@ class PyCostFunction : public CostFunction { + } + Real value(const Array& x) const { + PyObject* tuple = PyTuple_New(x.size()); ++#ifdef QL_XAD ++ for (Size i=0; i(); } +-double nullDouble() { return Null(); } ++Real nullDouble() { return Null(); } + %} + + #if defined(SWIGPYTHON) +@@ -57,33 +58,45 @@ double nullDouble() { return Null(); } + + %typemap(in) doubleOrNull { + if ($input == Py_None) +- $1 = Null(); ++ $1 = Null(); + else if (PyFloat_Check($input)) + $1 = PyFloat_AsDouble($input); ++#ifdef QL_XAD ++ else if (check_Real($input)) ++ $1 = make_Real($input); ++#endif + else + SWIG_exception(SWIG_TypeError,"double expected"); + } + %typecheck(SWIG_TYPECHECK_DOUBLE) doubleOrNull { ++#ifdef QL_XAD ++ $1 = ($input == Py_None || PyFloat_Check($input) || check_Real($input)) ? 1 : 0; ++#else + $1 = ($input == Py_None || PyFloat_Check($input)) ? 1 : 0; ++#endif + } + %typemap(out) doubleOrNull { +- if ($1 == Null()) { ++ if ($1 == Null()) { + Py_INCREF(Py_None); + $result = Py_None; + } else { ++#ifdef QL_XAD ++ $result = make_PyObject($1); ++#else + $result = PyFloat_FromDouble($1); ++#endif + } + } + + #elif defined(SWIGJAVA) + + typedef int intOrNull; +-typedef double doubleOrNull; ++typedef Real doubleOrNull; + + #elif defined(SWIGCSHARP) + + typedef int intOrNull; +-typedef double doubleOrNull; ++typedef Real doubleOrNull; + + #elif defined(SWIGR) + +@@ -117,7 +130,7 @@ typedef double doubleOrNull; + + %typemap(in) doubleOrNull { + $1 = ($1_ltype) REAL($input)[0]; +- if (R_IsNA($1)) $1 = Null(); ++ if (R_IsNA($1)) $1 = Null(); + } + + %typemap(rtypecheck) doubleOrNull %{ +@@ -126,7 +139,7 @@ typedef double doubleOrNull; + + + %typemap(out) doubleOrNull { +- if ($1 == Null()) ++ if ($1 == Null()) + $result = Rf_ScalarLogical(NA_LOGICAL); + else + $result = Rf_ScalarReal($1); diff --git a/SWIG/patches/ode.i.patch b/SWIG/patches/ode.i.patch new file mode 100644 index 00000000..6d99ebb7 --- /dev/null +++ b/SWIG/patches/ode.i.patch @@ -0,0 +1,37 @@ +diff --git a/SWIG/ode.i b/SWIG/ode.i +--- a/SWIG/ode.i ++++ b/SWIG/ode.i +@@ -85,6 +85,25 @@ class OdeFct { + + const std::vector operator()(Real x, const std::vector& y) const { + PyObject* pyY = PyList_New(y.size()); ++#ifdef QL_XAD ++ for (Size i=0; i < y.size(); ++i) ++ PyList_SetItem(pyY, i, make_PyObject(y[i])); ++ ++ PyObject* xo = make_PyObject(x); ++ PyObject* pyResult = PyObject_CallFunctionObjArgs(function_, xo, pyY, nullptr); ++ Py_XDECREF(pyY); ++ Py_XDECREF(xo); ++ if (PyErr_Occurred()) { ++ PyErr_Print(); ++ } ++ ++ QL_ENSURE(pyResult != NULL && PyList_Check(pyResult), ++ "failed to call Python function"); ++ ++ std::vector retVal(y.size()); ++ for (Size i=0; i < y.size(); ++i) ++ retVal[i] = make_Real(PyList_GET_ITEM(pyResult, i)); ++#else + for (Size i=0; i < y.size(); ++i) + PyList_SetItem(pyY, i, PyFloat_FromDouble(y[i])); + +@@ -99,6 +118,7 @@ class OdeFct { + for (Size i=0; i < y.size(); ++i) + retVal[i] = PyFloat_AsDouble(PyList_GET_ITEM(pyResult, i)); + ++#endif + Py_XDECREF(pyResult); + + return retVal; diff --git a/SWIG/patches/options.i.patch b/SWIG/patches/options.i.patch new file mode 100644 index 00000000..72dfa38d --- /dev/null +++ b/SWIG/patches/options.i.patch @@ -0,0 +1,12 @@ +diff --git a/SWIG/options.i b/SWIG/options.i +--- a/SWIG/options.i ++++ b/SWIG/options.i +@@ -428,7 +428,7 @@ class AnalyticHestonEngine : public PricingEngine { + Real andersenPiterbargEpsilon = 1e-8); + + Size numberOfEvaluations() const; +-#if defined(SWIGCSHARP) || defined(SWIGPYTHON) ++#if (defined(SWIGCSHARP) || defined(SWIGPYTHON)) && !defined(QL_XAD) + std::complex chF(const std::complex& z, Time t) const; + std::complex lnChF(const std::complex& z, Time t) const; + #else diff --git a/SWIG/patches/piecewiseyieldcurve.i.patch b/SWIG/patches/piecewiseyieldcurve.i.patch new file mode 100644 index 00000000..9f0893a7 --- /dev/null +++ b/SWIG/patches/piecewiseyieldcurve.i.patch @@ -0,0 +1,63 @@ +diff --git a/SWIG/piecewiseyieldcurve.i b/SWIG/piecewiseyieldcurve.i +--- a/SWIG/piecewiseyieldcurve.i ++++ b/SWIG/piecewiseyieldcurve.i +@@ -45,14 +45,14 @@ using QuantLib::PiecewiseYieldCurve; + + %{ + struct _IterativeBootstrap { +- double accuracy, minValue, maxValue; ++ Real accuracy, minValue, maxValue; + Size maxAttempts; + Real maxFactor, minFactor; + bool dontThrow; + Size dontThrowSteps, maxEvaluations; +- _IterativeBootstrap(double accuracy = Null(), +- double minValue = Null(), +- double maxValue = Null(), ++ _IterativeBootstrap(Real accuracy = Null(), ++ Real minValue = Null(), ++ Real maxValue = Null(), + Size maxAttempts = 1, + Real maxFactor = 2.0, + Real minFactor = 2.0, +@@ -81,9 +81,9 @@ struct _IterativeBootstrap { + #if !defined(SWIGJAVA) && !defined(SWIGCSHARP) + %feature("kwargs") _IterativeBootstrap; + #endif +- _IterativeBootstrap(doubleOrNull accuracy = Null(), +- doubleOrNull minValue = Null(), +- doubleOrNull maxValue = Null(), ++ _IterativeBootstrap(doubleOrNull accuracy = Null(), ++ doubleOrNull minValue = Null(), ++ doubleOrNull maxValue = Null(), + Size maxAttempts = 1, + Real maxFactor = 2.0, + Real minFactor = 2.0, +@@ -206,22 +206,22 @@ class AdditionalDates { + struct _GlobalBootstrap { + std::vector > additionalHelpers; + std::vector additionalDates; +- double accuracy; +- _GlobalBootstrap(double accuracy = Null()) ++ Real accuracy; ++ _GlobalBootstrap(Real accuracy = Null()) + : accuracy(accuracy) {} + _GlobalBootstrap(const std::vector >& additionalHelpers, + const std::vector& additionalDates, +- double accuracy = Null()) ++ Real accuracy = Null()) + : additionalHelpers(additionalHelpers), additionalDates(additionalDates), accuracy(accuracy) {} + }; + %} + + %rename(GlobalBootstrap) _GlobalBootstrap; + struct _GlobalBootstrap { +- _GlobalBootstrap(doubleOrNull accuracy = Null()); ++ _GlobalBootstrap(doubleOrNull accuracy = Null()); + _GlobalBootstrap(const std::vector >& additionalHelpers, + const std::vector& additionalDates, +- doubleOrNull accuracy = Null()); ++ doubleOrNull accuracy = Null()); + }; + + diff --git a/SWIG/patches/ql.i.patch b/SWIG/patches/ql.i.patch new file mode 100644 index 00000000..f8f73d77 --- /dev/null +++ b/SWIG/patches/ql.i.patch @@ -0,0 +1,13 @@ +diff --git a/SWIG/ql.i b/SWIG/ql.i +--- a/SWIG/ql.i ++++ b/SWIG/ql.i +@@ -108,6 +108,9 @@ GNU autoconf configure script. + QL_DEPRECATED_DISABLE_WARNING + %} + ++#ifdef QL_XAD ++%include tape.i ++#endif + %include common.i + %include vectors.i + %include tuple.i diff --git a/SWIG/patches/quantlib.i.patch b/SWIG/patches/quantlib.i.patch new file mode 100644 index 00000000..cae2cce6 --- /dev/null +++ b/SWIG/patches/quantlib.i.patch @@ -0,0 +1,26 @@ +diff --git a/SWIG/quantlib.i b/SWIG/quantlib.i +--- a/SWIG/quantlib.i ++++ b/SWIG/quantlib.i +@@ -21,9 +21,9 @@ + #if defined(SWIGCSHARP) + %module(directors="1") NQuantLibc + #elif defined(SWIGJAVA) +-%module(directors="1") QuantLib ++%module(directors="1") QuantLib_Risks + #else +-%module QuantLib ++%module QuantLib_Risks + #endif + + %include exception.i +@@ -43,8 +43,8 @@ + #if defined(SWIGPYTHON) + %{ + #include +-const int __hexversion__ = QL_HEX_VERSION; +-const char* __version__ = QL_VERSION; ++const int __hexversion__ = QLR_HEX_VERSION; ++const char* __version__ = QLR_VERSION; + %} + + const int __hexversion__; diff --git a/SWIG/patches/timebasket.i.patch b/SWIG/patches/timebasket.i.patch new file mode 100644 index 00000000..2f55a1eb --- /dev/null +++ b/SWIG/patches/timebasket.i.patch @@ -0,0 +1,15 @@ +diff --git a/SWIG/timebasket.i b/SWIG/timebasket.i +--- a/SWIG/timebasket.i ++++ b/SWIG/timebasket.i +@@ -54,7 +54,11 @@ class TimeBasket { + PyTuple_SetItem(item,0, + SWIG_NewPointerObj((void *) d, + $descriptor(Date *),1)); ++#ifdef QL_XAD ++ PyTuple_SetItem(item,1,make_PyObject(i->second)); ++#else + PyTuple_SetItem(item,1,PyFloat_FromDouble(i->second)); ++#endif + PyList_SetItem(itemList,j,item); + } + return itemList; diff --git a/SWIG/patches/types.i.patch b/SWIG/patches/types.i.patch new file mode 100644 index 00000000..d01eca6d --- /dev/null +++ b/SWIG/patches/types.i.patch @@ -0,0 +1,59 @@ +diff --git a/SWIG/types.i b/SWIG/types.i +--- a/SWIG/types.i ++++ b/SWIG/types.i +@@ -42,7 +42,13 @@ typedef int Integer; + typedef long BigInteger; + typedef unsigned int Natural; + typedef unsigned long BigNatural; ++#ifdef QL_XAD ++typedef xad::AReal Real; ++#define XAD_ENABLED 1 ++#else + typedef double Real; ++#define XAD_ENABLED 0 ++#endif + + typedef Real Decimal; + typedef Real Time; +@@ -65,4 +71,40 @@ typedef size_t Size; + typedef std::size_t Size; + #endif + ++#ifdef QL_XAD ++ ++%{ ++ #include "converters.hpp" ++%} ++ ++%typemap(in) Real, xad::AReal { ++ try { ++ $1 = make_Real($input); ++ } catch(...) { ++ SWIG_exception(SWIG_TypeError, "active Real, float, or long expected"); ++ } ++} ++ ++%typemap(in) const Real& (Real temp) { ++ try { ++ temp = make_Real($input); ++ $1 = &temp; ++ } catch(...) { ++ SWIG_exception(SWIG_TypeError, "active Real, float, or long expected"); ++ } ++ ++} ++ ++%define QL_TYPECHECK_REALOBJ 4990 %enddef ++ ++%typemap(out) Real, xad::AReal { ++ $result = make_PyObject($1); ++} ++ ++%typecheck(QL_TYPECHECK_REALOBJ) Real, xad::AReal, const Real& ++{ ++ $1 = PyFloat_Check($input) || PyLong_Check($input) || check_Real($input) ? 1 : 0; ++} + #endif ++ ++#endif +\ No newline at end of file diff --git a/SWIG/patches/vectors.i.patch b/SWIG/patches/vectors.i.patch new file mode 100644 index 00000000..d66c5986 --- /dev/null +++ b/SWIG/patches/vectors.i.patch @@ -0,0 +1,46 @@ +diff --git a/SWIG/vectors.i b/SWIG/vectors.i +--- a/SWIG/vectors.i ++++ b/SWIG/vectors.i +@@ -26,7 +26,7 @@ + %include date.i + + #if defined(SWIGCSHARP) +-SWIG_STD_VECTOR_ENHANCED( std::pair ) ++SWIG_STD_VECTOR_ENHANCED( std::pair ) + #endif + + %{ +@@ -42,19 +42,23 @@ namespace std { + + %template(IntVector) vector; + %template(UnsignedIntVector) vector; ++#ifndef QL_XAD + %template(DoubleVector) vector; ++#endif + %template(StrVector) vector; + %template(BoolVector) vector; + ++#ifndef QL_XAD + %template(DoubleVectorVector) vector>; + + %template(DoublePair) pair; + %template(DoublePairVector) vector>; + %template(PairDoubleVector) pair,vector>; ++#endif + %template(UnsignedIntPair) pair; + %template(UnsignedIntPairVector) vector>; + +-#if !defined(SWIGR) ++#if !defined(SWIGR) && !defined(QL_XAD) + %template(NodePair) pair; + %template(NodeVector) vector >; + #endif +@@ -74,4 +78,8 @@ namespace std { + #endif + } + ++#ifdef QL_XAD ++%include vectors_extra.i ++#endif ++ + #endif diff --git a/SWIG/vectors.i b/SWIG/vectors.i index b37594d2..7b757a8f 100644 --- a/SWIG/vectors.i +++ b/SWIG/vectors.i @@ -51,7 +51,7 @@ namespace std { #ifndef QL_XAD %template(DoubleVectorVector) vector>; - template(DoublePair) pair; + %template(DoublePair) pair; %template(DoublePairVector) vector>; %template(PairDoubleVector) pair,vector>; #endif diff --git a/tools/sync-swig.sh b/tools/sync-swig.sh new file mode 100755 index 00000000..482e20b7 --- /dev/null +++ b/tools/sync-swig.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +############################################################################## +# Syncs SWIG/ with a QuantLib-SWIG release and re-applies the XAD changes: +# +# tools/sync-swig.sh [] +# copies SWIG/*.i of into SWIG/ and applies SWIG/patches/*.patch; +# failed hunks are left in SWIG/*.rej +# tools/sync-swig.sh --update-patches [] +# regenerates SWIG/patches/ from SWIG/ against , after resolving +# +# Without a checkout, is cloned from GitHub. The XAD-only files +# (tape.i, date_extra.i, fdm_extra.i, vectors_extra.i) are kept as they are. +# +# 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-2026 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 . +# +############################################################################## + +set -euo pipefail + +xad_only="tape.i date_extra.i fdm_extra.i vectors_extra.i" +url=https://github.com/lballabio/QuantLib-SWIG.git + +update=0 +if [ "${1:-}" = "--update-patches" ]; then + update=1 + shift +fi +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + sed -n '3,12p' "$0" | sed 's/^# \{0,2\}//' + exit 2 +fi +tag=$1 +root=$(cd "$(dirname "$0")/.." && pwd) +swig=$root/SWIG +patches=$swig/patches + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +if [ $# -eq 2 ]; then + upstream=$(cd "$2" && pwd) + git -C "$upstream" rev-parse -q --verify "$tag^{commit}" >/dev/null || + git -C "$upstream" fetch -q origin tag "$tag" +else + upstream=$tmp/QuantLib-SWIG + git clone -q --depth 1 --branch "$tag" "$url" "$upstream" +fi + +# pristine upstream SWIG/*.i of +mkdir -p "$tmp/a/SWIG" +git -C "$upstream" archive "$tag" SWIG | tar -x -C "$tmp/a" +find "$tmp/a/SWIG" -mindepth 1 ! -name '*.i' -exec rm -rf {} + + +is_xad_only() { + case " $xad_only " in *" $1 "*) return 0 ;; esac + return 1 +} + +if [ $update -eq 1 ]; then + rm -f "$patches"/*.patch + mkdir -p "$patches" + for f in "$swig"/*.i; do + name=$(basename "$f") + is_xad_only "$name" && continue + [ -f "$tmp/a/SWIG/$name" ] || { echo "not in $tag: SWIG/$name" >&2; exit 1; } + mkdir -p "$tmp/b/SWIG" + cp "$f" "$tmp/b/SWIG/$name" + (cd "$tmp" && git diff --no-index --no-prefix --no-color \ + "a/SWIG/$name" "b/SWIG/$name" || true) | + sed '/^index /d' > "$patches/$name.patch" + [ -s "$patches/$name.patch" ] || rm "$patches/$name.patch" + done + echo "SWIG/patches updated against $tag" + exit 0 +fi + +for f in "$swig"/*.i; do + name=$(basename "$f") + is_xad_only "$name" || rm "$f" +done +cp "$tmp"/a/SWIG/*.i "$swig/" +rm -f "$swig"/*.rej "$swig"/*.orig + +failed=0 +for p in "$patches"/*.patch; do + [ -e "$p" ] || continue + if ! git -C "$root" apply --reject --whitespace=nowarn "$p" 2>"$tmp/err"; then + echo "conflict: $(basename "$p")" >&2 + sed 's/^/ /' "$tmp/err" >&2 + failed=1 + fi +done + +if [ $failed -ne 0 ]; then + echo "resolve SWIG/*.rej, then run: $0 --update-patches $tag" >&2 + exit 1 +fi +echo "SWIG/ synced with QuantLib-SWIG $tag" From f8ee163a2c15ffff1aef11fc56bd6da81de63364 Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:54:42 +0000 Subject: [PATCH 3/6] deps: QuantLib v1.41, sync SWIG with QuantLib-SWIG v1.41 --- SWIG/asianoptions.i | 23 +- SWIG/barrieroptions.i | 80 +++- SWIG/basketoptions.i | 117 +++++- SWIG/blackformula.i | 10 +- SWIG/bondfunctions.i | 44 ++- SWIG/bonds.i | 11 +- SWIG/calendars.i | 55 +-- SWIG/calibratedmodel.i | 2 +- SWIG/calibrationhelpers.i | 31 +- SWIG/capfloor.i | 68 +++- SWIG/cashflows.i | 482 +++++++++++++++++++---- SWIG/cliquetoptions.i | 2 +- SWIG/common.i | 155 ++++++-- SWIG/convertiblebonds.i | 2 +- SWIG/credit.i | 2 +- SWIG/creditdefaultswap.i | 5 +- SWIG/currencies.i | 20 +- SWIG/date.i | 218 +++++----- SWIG/daycounters.i | 16 +- SWIG/defaultprobability.i | 46 ++- SWIG/discountcurve.i | 6 +- SWIG/distributions.i | 2 +- SWIG/dividends.i | 7 +- SWIG/exchangerates.i | 2 +- SWIG/exercise.i | 2 +- SWIG/fdm.i | 338 +++++----------- SWIG/fdm_extra.i | 4 +- SWIG/fittedbondcurve.i | 62 ++- SWIG/forward.i | 29 +- SWIG/forwardcurve.i | 18 +- SWIG/fra.i | 18 +- SWIG/functions.i | 170 +++----- SWIG/futures.i | 38 +- SWIG/gaussian1dmodel.i | 2 +- SWIG/grid.i | 2 +- SWIG/indexes.i | 78 ++-- SWIG/inflation.i | 446 ++++++++++++--------- SWIG/instruments.i | 2 +- SWIG/integrals.i | 2 +- SWIG/interestrate.i | 2 +- SWIG/interpolation.i | 120 ++---- SWIG/lazyobject.i | 2 +- SWIG/linearalgebra.i | 173 ++++---- SWIG/lmm.i | 2 +- SWIG/localvolatilities.i | 2 +- SWIG/lookbackoptions.i | 2 +- SWIG/marketelements.i | 106 ++++- SWIG/money.i | 26 +- SWIG/montecarlo.i | 2 +- SWIG/null.i | 60 +-- SWIG/observer.i | 66 ++-- SWIG/ode.i | 82 ++-- SWIG/old_volatility.i | 30 +- SWIG/operators.i | 2 +- SWIG/optimizers.i | 13 +- SWIG/options.i | 209 +++++++--- SWIG/parameter.i | 2 +- SWIG/patches/date.i.patch | 2 +- SWIG/patches/fdm.i.patch | 187 +++------ SWIG/patches/functions.i.patch | 119 +++--- SWIG/patches/inflation.i.patch | 15 + SWIG/patches/linearalgebra.i.patch | 22 +- SWIG/patches/marketelements.i.patch | 54 +++ SWIG/patches/null.i.patch | 32 +- SWIG/patches/ode.i.patch | 54 ++- SWIG/patches/options.i.patch | 2 +- SWIG/patches/piecewiseyieldcurve.i.patch | 43 +- SWIG/patches/ql.i.patch | 2 +- SWIG/patches/timebasket.i.patch | 6 +- SWIG/payoffs.i | 2 +- SWIG/piecewiseyieldcurve.i | 97 ++++- SWIG/ql.i | 16 +- SWIG/quantlib.i | 20 +- SWIG/randomnumbers.i | 26 +- SWIG/ratehelpers.i | 261 ++++++++++-- SWIG/rounding.i | 2 +- SWIG/sampledcurve.i | 45 --- SWIG/scheduler.i | 19 +- SWIG/settings.i | 77 +++- SWIG/shortratemodels.i | 30 +- SWIG/slv.i | 2 +- SWIG/spreaddiscountcurve.i | 37 ++ SWIG/spreadoption.i | 2 +- SWIG/statistics.i | 2 +- SWIG/stochasticprocess.i | 12 +- SWIG/swap.i | 427 +++++++++----------- SWIG/swaption.i | 88 ++++- SWIG/swingoption.i | 2 +- SWIG/termstructures.i | 51 ++- SWIG/timebasket.i | 25 +- SWIG/timeseries.i | 13 +- SWIG/tracing.i | 2 +- SWIG/tuple.i | 18 +- SWIG/types.i | 4 +- SWIG/vectors.i | 2 +- SWIG/volatilities.i | 218 +++++++--- SWIG/volatilitymodels.i | 2 +- SWIG/zerocurve.i | 6 +- lib/QuantLib | 2 +- 99 files changed, 3434 insertions(+), 2132 deletions(-) create mode 100644 SWIG/patches/inflation.i.patch create mode 100644 SWIG/patches/marketelements.i.patch delete mode 100644 SWIG/sampledcurve.i create mode 100644 SWIG/spreaddiscountcurve.i 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