dal-python 2026.9.25__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dal/__init__.py +11 -0
- dal/_dal.cp314-win_amd64.pyd +0 -0
- dal/api.py +148 -0
- dal/dal.py +2 -0
- dal_python-2026.9.25.dist-info/METADATA +1057 -0
- dal_python-2026.9.25.dist-info/RECORD +7 -0
- dal_python-2026.9.25.dist-info/WHEEL +5 -0
dal/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Import the C extension first to avoid circular imports when dal.py
|
|
4
|
+
# executes "from . import _dal" during package initialization.
|
|
5
|
+
from . import _dal # noqa: F401
|
|
6
|
+
from .dal import *
|
|
7
|
+
from .api import *
|
|
8
|
+
|
|
9
|
+
__author__ = 'The Derivatives Algorithms Group'
|
|
10
|
+
__email__ = 'wegamekinglc@hotmail.com'
|
|
11
|
+
__version__ = "2026.9.25"
|
|
Binary file
|
dal/api.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import json as _json
|
|
2
|
+
|
|
3
|
+
from . import dal as _bindings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def Product_New(events_dates: list, events: list[str], *, settings=None):
|
|
7
|
+
wrapped = []
|
|
8
|
+
for row, value in enumerate(events_dates, 1):
|
|
9
|
+
if isinstance(value, str) and "\0" in value:
|
|
10
|
+
raise RuntimeError(
|
|
11
|
+
f"InvalidSetting: Product_New; events_dates / dates/events row={row}; "
|
|
12
|
+
f"value={value!r}; expected text without NUL"
|
|
13
|
+
)
|
|
14
|
+
try:
|
|
15
|
+
wrapped.append(value if isinstance(value, _bindings.Cell_) else _bindings.Cell_(value))
|
|
16
|
+
except TypeError as error:
|
|
17
|
+
raise TypeError(
|
|
18
|
+
f"InvalidSetting: Product_New; events_dates / dates/events row={row}; "
|
|
19
|
+
f"type={type(value).__name__}; expected Cell_ or a Cell_-convertible value"
|
|
20
|
+
) from error
|
|
21
|
+
return _bindings.Product_New(wrapped, events, settings=settings)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def Product_Describe(product):
|
|
25
|
+
return _json.loads(_bindings.Product_Describe(product))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ScriptValuation_Explain(product, modelData, *, valuation=None):
|
|
29
|
+
return _json.loads(_bindings.ScriptValuation_Explain(product, modelData, valuation=valuation))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ScriptSimulation_Explain(product, modelData, num_path, *, valuation=None, simulation=None):
|
|
33
|
+
return _json.loads(
|
|
34
|
+
_bindings.ScriptSimulation_Explain(product, modelData, num_path, valuation=valuation, simulation=simulation)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Settings whose target fields expect DAL value types — plain Python str
|
|
39
|
+
# must be wrapped before setattr so pybind11 can convert them correctly.
|
|
40
|
+
# DAL objects (String_, CollateralType_ etc.) pass through as-is.
|
|
41
|
+
_DAL_TYPE_CONVERTERS = {
|
|
42
|
+
'curve_name': lambda v: _bindings.String_(v) if isinstance(v, str) else v,
|
|
43
|
+
'target_collateral': lambda v: _bindings.CollateralType_(v) if isinstance(v, str) else v,
|
|
44
|
+
'target_tenor': lambda v: _bindings.PeriodLength_(v) if isinstance(v, str) else v,
|
|
45
|
+
'libor_basis': lambda v: _bindings.DayBasis_(v) if isinstance(v, str) else v,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_OPTIONAL_SETTING_ATTRS = {
|
|
49
|
+
'curve_name': 'curveName_',
|
|
50
|
+
'target_collateral': 'targetCollateral_',
|
|
51
|
+
'target_tenor': 'targetTenor_',
|
|
52
|
+
'calibrate_discount': 'calibrateDiscountCurve_',
|
|
53
|
+
'libor_basis': 'liborBasis_',
|
|
54
|
+
'smoothing_weight': 'smoothingWeight_',
|
|
55
|
+
'tolerance': 'tolerance_',
|
|
56
|
+
'fit_tolerance': 'fitTolerance_',
|
|
57
|
+
'max_evaluations': 'maxEvaluations_',
|
|
58
|
+
'max_restarts': 'maxRestarts_',
|
|
59
|
+
'initial_guess': 'initialGuess_',
|
|
60
|
+
'solve_mode': 'solveMode_',
|
|
61
|
+
'parameterization': 'parameterization_',
|
|
62
|
+
'log_df_scheme': 'logDfScheme_',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _apply_optional_setting(spec, name, value):
|
|
67
|
+
"""Apply a single optional setting to a spec builder if the value is not None."""
|
|
68
|
+
if name not in _OPTIONAL_SETTING_ATTRS:
|
|
69
|
+
valid = ', '.join(sorted(_OPTIONAL_SETTING_ATTRS))
|
|
70
|
+
raise ValueError(f"Unknown calibration setting {name!r}. Supported settings: {valid}")
|
|
71
|
+
if value is None:
|
|
72
|
+
return
|
|
73
|
+
attr = _OPTIONAL_SETTING_ATTRS[name]
|
|
74
|
+
convert = _DAL_TYPE_CONVERTERS.get(name)
|
|
75
|
+
setattr(spec, attr, convert(value) if convert else value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _build_calibration_spec(today, ccy, instruments, knot_dates, settings, base_curve=None):
|
|
79
|
+
"""Build a CurveCalibrationSpec_ with sensible defaults and optional overrides."""
|
|
80
|
+
spec = _bindings.CurveCalibrationSpecBuilder_()
|
|
81
|
+
spec.today_ = today
|
|
82
|
+
spec.ccy_ = ccy if isinstance(ccy, _bindings.String_) else _bindings.String_(ccy)
|
|
83
|
+
|
|
84
|
+
spec.curveName_ = _bindings.String_("calibrated")
|
|
85
|
+
spec.calibrateDiscountCurve_ = True
|
|
86
|
+
spec.smoothingWeight_ = 1.0
|
|
87
|
+
spec.tolerance_ = 1e-8
|
|
88
|
+
spec.fitTolerance_ = 1e-6
|
|
89
|
+
spec.maxEvaluations_ = 200
|
|
90
|
+
spec.maxRestarts_ = 20
|
|
91
|
+
spec.initialGuess_ = 0.05
|
|
92
|
+
|
|
93
|
+
spec.instruments_ = instruments
|
|
94
|
+
spec.knotDates_ = knot_dates
|
|
95
|
+
if base_curve is not None:
|
|
96
|
+
spec.baseCurve_ = base_curve
|
|
97
|
+
|
|
98
|
+
if settings:
|
|
99
|
+
for key, value in settings.items():
|
|
100
|
+
_apply_optional_setting(spec, key, value)
|
|
101
|
+
|
|
102
|
+
return spec
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def calibrate_curve(
|
|
106
|
+
today,
|
|
107
|
+
ccy,
|
|
108
|
+
instruments,
|
|
109
|
+
knot_dates,
|
|
110
|
+
settings=None,
|
|
111
|
+
jacobian_mode=None,
|
|
112
|
+
base_curve=None,
|
|
113
|
+
):
|
|
114
|
+
"""High-level single-curve calibration with sensible defaults.
|
|
115
|
+
|
|
116
|
+
Only discount-curve calibration (calibrate_discount=True) is supported here;
|
|
117
|
+
forward-curve calibration needs a preloaded discount curve, so build a
|
|
118
|
+
CurveCalibrationSpecBuilder_ directly (set discountCurves_) and call
|
|
119
|
+
dal.CalibrateSingleCurve.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
today: Date_ for the calibration date
|
|
123
|
+
ccy: Currency string (e.g. "USD")
|
|
124
|
+
instruments: List of YCInstrument_ handles
|
|
125
|
+
knot_dates: List of Date_ knot points
|
|
126
|
+
settings: Optional dict of override settings. Supported keys:
|
|
127
|
+
curve_name, target_collateral, target_tenor, calibrate_discount
|
|
128
|
+
(must be True), libor_basis, smoothing_weight, tolerance,
|
|
129
|
+
fit_tolerance, max_evaluations, max_restarts, initial_guess,
|
|
130
|
+
solve_mode, parameterization, log_df_scheme
|
|
131
|
+
jacobian_mode: CurveJacobianMode enum (None = default without Jacobian)
|
|
132
|
+
base_curve: Optional discount curve multiplied into the calibrated curve.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
CalibrationResult_ with curve_ and diagnostics_
|
|
136
|
+
"""
|
|
137
|
+
if settings and settings.get("calibrate_discount") is False:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"calibrate_curve() only supports discount-curve calibration "
|
|
140
|
+
"(calibrate_discount=True). For forward-curve calibration, build a "
|
|
141
|
+
"CurveCalibrationSpecBuilder_ with discountCurves_ and call "
|
|
142
|
+
"dal.CalibrateSingleCurve directly."
|
|
143
|
+
)
|
|
144
|
+
spec = _build_calibration_spec(today, ccy, instruments, knot_dates, settings, base_curve)
|
|
145
|
+
if jacobian_mode is not None:
|
|
146
|
+
return _bindings.CalibrateSingleCurve(spec.Build(), jacobian_mode)
|
|
147
|
+
else:
|
|
148
|
+
return _bindings.CalibrateSingleCurve(spec.Build())
|
dal/dal.py
ADDED
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dal-python
|
|
3
|
+
Version: 2026.9.25
|
|
4
|
+
Summary: Python bindings for the DAL quantitative finance library
|
|
5
|
+
Author-Email: The Derivatives Algorithms Group <wegamekinglc@hotmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Programming Language :: C++
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Project-URL: Documentation, https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/tree/master/dal-python
|
|
23
|
+
Project-URL: Repository, https://github.com/wegamekinglc/Derivatives-Algorithms-Lib
|
|
24
|
+
Requires-Python: <3.15,>=3.9
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
27
|
+
Requires-Dist: numpy>=1.24; extra == "test"
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# dal-python
|
|
31
|
+
|
|
32
|
+
Python bindings for the Derivatives Algorithms Library (DAL) — a high-performance C++17 quantitative finance library with Automatic Adjoint Differentiation (AAD) support.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Black-Scholes and Dupire models** for equity derivatives pricing
|
|
37
|
+
- **Monte Carlo simulation** with pseudo-random and Sobol sequence generators
|
|
38
|
+
- **AAD Greeks** — compute pathwise sensitivities (delta, vega, rho, etc.) in a single simulation
|
|
39
|
+
- **Script engine** — define exotic payoffs using a domain-specific language
|
|
40
|
+
- **Named FIX valuation** — explicit dates, immutable history snapshots, and contract/valuation diagnostics
|
|
41
|
+
- **Curve calibration** — single-curve, multi-curve, staged XCCY, and joint domestic/foreign/basis calibration with resettable and MTM instruments plus AAD analytic Jacobians
|
|
42
|
+
- **Rate cashflow pricing** — typed planning, batch PV, and AAD node sensitivities for deposit, FRA, future, OIS, IRS, basis-swap, and cross-currency trades
|
|
43
|
+
- **Type-safe wrappers** for `Date_`, `Matrix_`, `Cell_`, and vector types
|
|
44
|
+
|
|
45
|
+
## Prerequisites
|
|
46
|
+
|
|
47
|
+
- **CPython 3.9-3.14** with development headers (`Requires-Python: >=3.9,<3.15`)
|
|
48
|
+
- **uv** — fast Python package manager ([install guide](https://docs.astral.sh/uv/getting-started/installation/))
|
|
49
|
+
- **pybind11 3.1.0** — installed automatically for isolated package builds;
|
|
50
|
+
local helpers install it before non-isolated builds. Older repository builds
|
|
51
|
+
can fall back to the pinned `dal-cpp/externals/pybind11` submodule, so run
|
|
52
|
+
`git submodule update --init --recursive` on fresh clones
|
|
53
|
+
- **CMake 3.21+** and a C++17 compiler (GCC 13+, Clang 18+, or MSVC 2022)
|
|
54
|
+
- **DAL C++ staged install** — build core/public first; the canonical workflow is
|
|
55
|
+
in the [installation guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/installation.md#python-bindings)
|
|
56
|
+
|
|
57
|
+
### Building the C++ Library
|
|
58
|
+
|
|
59
|
+
The Python bindings depend on a compiled DAL C++ staging prefix. Build it first:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd /path/to/Derivatives-Algorithms-Lib
|
|
63
|
+
./build_linux.sh
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This produces `build/stage/Release-linux/`, containing the installed core/public
|
|
67
|
+
libraries, headers, and CMake package metadata.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
### Development Install (Recommended)
|
|
72
|
+
|
|
73
|
+
Clone the repository and install in editable mode:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd Derivatives-Algorithms-Lib/dal-python
|
|
77
|
+
|
|
78
|
+
# Create a virtual environment with uv
|
|
79
|
+
uv venv --python ">=3.9,<3.15"
|
|
80
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
81
|
+
|
|
82
|
+
# Install dependencies and build the extension
|
|
83
|
+
uv pip install -e ".[test]" "--config-settings=cmake.define.DAL_INSTALL_PREFIX=/absolute/path/to/Derivatives-Algorithms-Lib/build/stage/<platform-preset>"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use an absolute staged-prefix path and replace `<platform-preset>` with the
|
|
87
|
+
preset that built DAL, such as `Release-linux` or `Release-windows`. Standalone
|
|
88
|
+
`dal-python` reads the installed CMake packages and automatically applies their
|
|
89
|
+
configuration-aware MSVC runtime contract to `_dal`.
|
|
90
|
+
|
|
91
|
+
### Workspace Build and Test
|
|
92
|
+
|
|
93
|
+
To provision Python test dependencies and run the bindings through the workspace
|
|
94
|
+
CTest integration:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bash ../build_linux.sh --full
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The workspace script creates or reuses `dal-python/.venv`, builds the extension,
|
|
101
|
+
and runs the configured C++/public/Python tests.
|
|
102
|
+
|
|
103
|
+
### Selecting a CPython Version
|
|
104
|
+
|
|
105
|
+
The local helpers accept an exact supported minor. On POSIX entry points use
|
|
106
|
+
`--python`; on PowerShell entry points use `-Python`:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# From the repository root; --python also enables the Python bindings.
|
|
110
|
+
bash ./build_linux.sh --python 3.9
|
|
111
|
+
|
|
112
|
+
# From dal-python/.
|
|
113
|
+
./build_sdist.sh --python 3.9
|
|
114
|
+
./build_wheel.sh --python 3.9
|
|
115
|
+
./run_tests.sh --python 3.9
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
# From dal-python/.
|
|
120
|
+
.\build_wheel.ps1 -Python 3.9
|
|
121
|
+
.\run_tests.ps1 -Python 3.9
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The accepted values are 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14. When the selector is
|
|
125
|
+
omitted, the helpers resolve a CPython in `>=3.9,<3.15`. A reused `.venv` must
|
|
126
|
+
already use the selected CPython minor; mismatches fail without replacing the
|
|
127
|
+
environment.
|
|
128
|
+
|
|
129
|
+
## Building Distribution Packages
|
|
130
|
+
|
|
131
|
+
For production deployment, you can build pre-compiled binary wheels or source distributions.
|
|
132
|
+
Official PyPI releases contain precompiled wheels only.
|
|
133
|
+
|
|
134
|
+
### Building a Binary Wheel
|
|
135
|
+
|
|
136
|
+
Binary wheels contain the compiled C++ extension and can be installed without requiring compilation:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
DAL_INSTALL_PREFIX=/absolute/path/to/build/stage/Release-linux ./build_wheel.sh
|
|
140
|
+
DAL_INSTALL_PREFIX=/absolute/path/to/build/stage/Release-linux ./build_wheel.sh --python 3.9
|
|
141
|
+
DAL_INSTALL_PREFIX=/absolute/path/to/build/stage/Release-linux ./build_wheel.sh --clean
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The platform- and interpreter-tagged wheel is created under `dist/`.
|
|
145
|
+
|
|
146
|
+
Install the wheel:
|
|
147
|
+
```bash
|
|
148
|
+
uv pip install dist/dal_python-*.whl
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Note:** Binary wheels are platform-specific. DAL keeps native-CPU tuning off by
|
|
152
|
+
default so distributable builds use the compiler's portable baseline. Do not set
|
|
153
|
+
`DAL_ENABLE_NATIVE_ARCH=ON` for a wheel that must run on unknown machines.
|
|
154
|
+
|
|
155
|
+
### Building a Source Distribution
|
|
156
|
+
|
|
157
|
+
Source distributions allow users to build from source on any platform:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
./build_sdist.sh # Build source distribution
|
|
161
|
+
./build_sdist.sh --python 3.9 # Select an exact supported CPython
|
|
162
|
+
./build_sdist.sh --clean # Clean build artifacts before building
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The source archive is created under `dist/`.
|
|
166
|
+
|
|
167
|
+
Install from source (requires C++ build tools):
|
|
168
|
+
```bash
|
|
169
|
+
pip install dist/dal_python-2026.9.25.tar.gz \
|
|
170
|
+
"--config-settings=cmake.define.DAL_INSTALL_PREFIX=/absolute/path/to/Derivatives-Algorithms-Lib/build/stage/<platform-preset>"
|
|
171
|
+
# or
|
|
172
|
+
uv pip install dist/dal_python-2026.9.25.tar.gz \
|
|
173
|
+
"--config-settings=cmake.define.DAL_INSTALL_PREFIX=/absolute/path/to/Derivatives-Algorithms-Lib/build/stage/<platform-preset>"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Requirements for building from source:**
|
|
177
|
+
- C++17 compiler (GCC 13+, Clang 18+, or MSVC 2022)
|
|
178
|
+
- CMake 3.21+
|
|
179
|
+
- pybind11 3.1.0 (declared as an isolated build requirement and installed
|
|
180
|
+
automatically; Python 3.14 builds require pybind11 3.0 or newer)
|
|
181
|
+
- CPython 3.9-3.14 development headers
|
|
182
|
+
- DAL staged install containing the `dal-public`/`dal-cpp` CMake packages and
|
|
183
|
+
platform libraries
|
|
184
|
+
|
|
185
|
+
## PyPI Binary Release
|
|
186
|
+
|
|
187
|
+
Release tags build and test this wheel matrix:
|
|
188
|
+
|
|
189
|
+
| Operating system | Architecture | Wheel platform tag | CPython versions |
|
|
190
|
+
|------------------|--------------|-------------------------|------------------|
|
|
191
|
+
| Linux | x86-64 | `manylinux_2_28_x86_64` | 3.9-3.14 |
|
|
192
|
+
| Windows | x86-64 | `win_amd64` | 3.9-3.14 |
|
|
193
|
+
| macOS 14+ | x86-64 | `macosx_*_x86_64` | 3.9-3.14 |
|
|
194
|
+
| macOS 14+ | Apple Silicon | `macosx_*_arm64` | 3.9-3.14 |
|
|
195
|
+
|
|
196
|
+
Every release artifact is a CPython-specific native wheel. Python/ABI tags run
|
|
197
|
+
from `cp39-cp39` through `cp314-cp314`; DAL does not publish `abi3` or universal
|
|
198
|
+
wheels. An annotated `dal-python-v<version>` tag on the current `master` commit
|
|
199
|
+
builds all six supported interpreters on four platform/architecture targets, for 24 wheels.
|
|
200
|
+
After each wheel is built, cibuildwheel runs the installed-wheel Python unit
|
|
201
|
+
suite. The separate Python wheel CI runs on path-matched pull requests: it
|
|
202
|
+
builds `cp39` and `cp314` on all four targets (eight wheels), runs the same unit
|
|
203
|
+
suite, and smoke-tests the installed `cp39` wheels. Pull requests and manual
|
|
204
|
+
dispatches do not run the release workflow.
|
|
205
|
+
|
|
206
|
+
Linux filenames always include `manylinux_2_28_x86_64` and may also contain
|
|
207
|
+
unique compatible PEP 600 x86-64 components for glibc baselines no newer than
|
|
208
|
+
2.28. Mixed platform families, other architectures, raw Linux, legacy manylinux,
|
|
209
|
+
and musllinux tags are rejected. macOS wheels use one native architecture tag;
|
|
210
|
+
mixed or universal2 tags are rejected. Linux ARM, PyPy, free-threaded CPython,
|
|
211
|
+
and source distributions are outside the current PyPI release contract.
|
|
212
|
+
|
|
213
|
+
Before building, the workflow checks that the annotated tag points to the
|
|
214
|
+
current `master` commit and that the version is unused on PyPI. Before upload,
|
|
215
|
+
the publish job rechecks the tag target, PyPI version, complete wheel matrix,
|
|
216
|
+
and package metadata. It uploads the same downloaded wheel files that passed
|
|
217
|
+
those checks. The toolchain is pinned (full action SHAs,
|
|
218
|
+
exact build dependency versions, named manylinux/runner images), but
|
|
219
|
+
byte-for-byte reproducibility across independent rebuilds is not currently
|
|
220
|
+
enforced.
|
|
221
|
+
|
|
222
|
+
### One-time PyPI setup
|
|
223
|
+
|
|
224
|
+
Configure a Trusted Publisher on the existing `dal-python` PyPI project with:
|
|
225
|
+
|
|
226
|
+
| Field | Value |
|
|
227
|
+
|--------------------|------------------------------|
|
|
228
|
+
| PyPI project | `dal-python` |
|
|
229
|
+
| GitHub owner | `wegamekinglc` |
|
|
230
|
+
| GitHub repository | `Derivatives-Algorithms-Lib` |
|
|
231
|
+
| Workflow filename | `dal-python-release.yml` |
|
|
232
|
+
| GitHub environment | `pypi` |
|
|
233
|
+
|
|
234
|
+
Create the matching `pypi` environment in the GitHub repository and require a
|
|
235
|
+
manual deployment approval if the repository plan supports it. The workflow uses
|
|
236
|
+
OIDC short-lived credentials; do not add a long-lived PyPI API token.
|
|
237
|
+
|
|
238
|
+
### Release procedure
|
|
239
|
+
|
|
240
|
+
1. Choose a new PEP 440 version that does not exist on PyPI. Update both
|
|
241
|
+
`pyproject.toml` and `src/dal/__init__.py`.
|
|
242
|
+
2. Review and merge the version and release-note changes to `master` after the
|
|
243
|
+
normal pull-request checks pass.
|
|
244
|
+
3. Tag that reviewed `master` commit and push only the tag:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
git tag -a dal-python-v<version> -m "Release dal-python <version>"
|
|
248
|
+
git push origin dal-python-v<version>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
4. The tag run builds and unit-tests every wheel, validates the complete wheel
|
|
252
|
+
set and unused PyPI version, then publishes those wheel files through the
|
|
253
|
+
`pypi` environment. Confirm that PyPI lists all 24 wheels.
|
|
254
|
+
|
|
255
|
+
PyPI versions and files are immutable. Never use a skip-existing option to repair
|
|
256
|
+
an incomplete release; correct the issue, increment the version, and run the full
|
|
257
|
+
process again. Local `build_wheel.*` scripts are for diagnostics and private
|
|
258
|
+
deployment only; their output is not a PyPI release artifact.
|
|
259
|
+
|
|
260
|
+
## Usage
|
|
261
|
+
|
|
262
|
+
### Basic Pricing Example
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import dal
|
|
266
|
+
|
|
267
|
+
# Set evaluation date
|
|
268
|
+
dal.EvaluationDate_Set(dal.Date_(2022, 9, 25))
|
|
269
|
+
|
|
270
|
+
# Define model parameters
|
|
271
|
+
spot, vol, rate, div = 100.0, 0.2, 0.05, 0.02
|
|
272
|
+
model = dal.BSModelData_New(spot=spot, vol=vol, rate=rate, div=div)
|
|
273
|
+
|
|
274
|
+
# Define a European call option
|
|
275
|
+
strike = 100.0
|
|
276
|
+
maturity = dal.Date_(2023, 9, 25)
|
|
277
|
+
product = dal.Product_New(
|
|
278
|
+
["STRIKE", dal.Cell_(maturity)],
|
|
279
|
+
[str(strike), "call pays MAX(spot() - STRIKE, 0.0)"]
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Price using Monte Carlo (65,536 paths, Sobol sequences)
|
|
283
|
+
result = dal.MonteCarlo_Value(product, model, 2**16, "sobol")
|
|
284
|
+
print(f"Call PV: {result['PV']:.4f}")
|
|
285
|
+
# Output: Call PV: 9.2259
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Computing AAD Greeks
|
|
289
|
+
|
|
290
|
+
Enable AAD to compute pathwise sensitivities in a single simulation:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
result = dal.MonteCarlo_Value(
|
|
294
|
+
product, model,
|
|
295
|
+
2**14, # num_paths
|
|
296
|
+
"sobol", # method
|
|
297
|
+
False, # use_bb
|
|
298
|
+
True # enable_aad
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
print(f"PV: {result['PV']:.6f}")
|
|
302
|
+
for key in sorted(result.keys()):
|
|
303
|
+
if key.startswith('d_'):
|
|
304
|
+
print(f" {key}: {result[key]:.6f}")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Output:
|
|
308
|
+
```
|
|
309
|
+
PV: 9.223019
|
|
310
|
+
d_STRIKE: -0.494542
|
|
311
|
+
d_div: -58.677195
|
|
312
|
+
d_rate: 49.454176
|
|
313
|
+
d_spot: 0.586772
|
|
314
|
+
d_vol: 37.873346
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Historical and Future FIX
|
|
318
|
+
|
|
319
|
+
The complete [FIX settings example](examples/012.fix_settings.py) constructs a
|
|
320
|
+
zero-volatility Black-Scholes model, an explicit valuation date and a midnight
|
|
321
|
+
history snapshot. Run it from the repository root with the current `dal` package:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
python dal-python/examples/012.fix_settings.py
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Its script first assigns `x = SCALE * FIX(EQ[DAL196_TEST])` on 2026-09-11,
|
|
328
|
+
then pays `x + FIX(EQ[DAL196_TEST], 2026-09-15)` on 2026-09-22. With valuation
|
|
329
|
+
date 2026-09-12, SCALE 2, history 80, model spot 100 and zero rates, the result
|
|
330
|
+
is `PV=260` and `d_SCALE=80`. The example checks both numbers, the result keys,
|
|
331
|
+
and both diagnostic schemas, including under optimized Python.
|
|
332
|
+
|
|
333
|
+
The index inside `FIX(index[,date])` is unquoted script syntax; the containing
|
|
334
|
+
Python string still uses quotes. The optional date is a literal `YYYY-MM-DD`;
|
|
335
|
+
omitting it uses the event date. With valuation date `D`, event date `E`, and
|
|
336
|
+
fixing date `F`:
|
|
337
|
+
|
|
338
|
+
- `F < D` requires exact history; a missing fixing raises `MissingFixing`.
|
|
339
|
+
- `F = D` uses the model by default; `RequireHistorical` requires today's history.
|
|
340
|
+
- `F > D` uses the model, regardless of future values in the snapshot.
|
|
341
|
+
- `F > E` raises `LookAheadObservation`, including in an unused branch.
|
|
342
|
+
|
|
343
|
+
Wholly expired products still validate syntax, dates and settings, but skip
|
|
344
|
+
history reads and return zero. Empty or no-PAYS products fail valuation.
|
|
345
|
+
|
|
346
|
+
Historical EQ/FX observations can coexist. A model-sourced FIX, including today
|
|
347
|
+
under `Model`, is always bound to the script's own future FIX index by name; the
|
|
348
|
+
`model_bindings` settings argument was removed. Two or more distinct future FIX
|
|
349
|
+
indices fail with `MultipleModelIndices`. Future FX, IR, composite, and
|
|
350
|
+
delivery-suffixed EQ remain unsupported. `default_index` gives legacy `SPOT()`
|
|
351
|
+
an identity; it does not affect the model binding. Unbound future-only `SPOT()`
|
|
352
|
+
remains supported. Historical SPOT requires a default, and mixing SPOT with FIX
|
|
353
|
+
requires one too. `SPOT(index)` and `FIX()` are invalid.
|
|
354
|
+
|
|
355
|
+
Snapshot keys are native `DateTime_` values. Use `dal.DateTime_(date, 0)` for
|
|
356
|
+
midnight; a quote at 11:00 cannot satisfy a daily FIX. Python `datetime` objects
|
|
357
|
+
are not automatically converted. Snapshot construction copies the nested input
|
|
358
|
+
dictionary, and the resulting native handle is immutable.
|
|
359
|
+
|
|
360
|
+
`fixings=None` captures required global history afresh on each call.
|
|
361
|
+
`dal.MarketFixingSnapshot_New({})` is an explicit empty snapshot: missing history
|
|
362
|
+
fails even if the global store contains it. Global capture copies sequences
|
|
363
|
+
one by one and is not an atomic snapshot across sequences; exclude concurrent
|
|
364
|
+
fixing writes during capture. Reusing an explicit snapshot can retain history
|
|
365
|
+
80 after global history changes to 90. It fixes historical input only; every
|
|
366
|
+
Value or Explain prepares again with its current date and model inputs, without
|
|
367
|
+
caching future prices.
|
|
368
|
+
|
|
369
|
+
### Working with Dates
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
import dal
|
|
373
|
+
|
|
374
|
+
# Create dates
|
|
375
|
+
d = dal.Date_(2022, 9, 25)
|
|
376
|
+
print(d) # 2022-09-25
|
|
377
|
+
|
|
378
|
+
# Date arithmetic
|
|
379
|
+
d2 = d.AddDays(30)
|
|
380
|
+
print(f"Year: {dal.Year(d)}, Month: {dal.Month(d)}, Day: {dal.Day(d)}")
|
|
381
|
+
|
|
382
|
+
# Date comparisons
|
|
383
|
+
d3 = dal.Date_(2022, 10, 25)
|
|
384
|
+
print(d < d3) # True
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Random Number Generation
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
# Pseudo-random generator (MRG32k32a algorithm)
|
|
391
|
+
pseudo = dal.PseudoRSG_New(42, 3) # seed=42, ndim=3
|
|
392
|
+
uniform_samples = dal.PseudoRSG_Get_Uniform(pseudo, 1000) # Returns DoubleMatrix_
|
|
393
|
+
normal_samples = dal.PseudoRSG_Get_Normal(pseudo, 1000)
|
|
394
|
+
|
|
395
|
+
# Sobol quasi-random sequences (better convergence for MC)
|
|
396
|
+
sobol = dal.SobolRSG_New(0, 3) # i_path=0, ndim=3
|
|
397
|
+
sobol_samples = dal.SobolRSG_Get_Uniform(sobol, 1000)
|
|
398
|
+
precise_sobol = dal.SobolRSG_New(
|
|
399
|
+
0, 3, precise=True, polish=True
|
|
400
|
+
) # opt in to the precise-CDF Newton correction
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Dupire Local Volatility Model
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# Define a local volatility surface with flat 20% vol
|
|
407
|
+
spots = [80.0, 90.0, 100.0, 110.0, 120.0]
|
|
408
|
+
times = [0.5, 1.0, 2.0]
|
|
409
|
+
vols = dal.DoubleMatrix_(len(spots), len(times), 0.2) # Fill with 20% vol
|
|
410
|
+
|
|
411
|
+
dupire_model = dal.DupireModelData_New(
|
|
412
|
+
spot=100.0,
|
|
413
|
+
rate=0.05,
|
|
414
|
+
repo=0.01,
|
|
415
|
+
spots=spots,
|
|
416
|
+
times=times,
|
|
417
|
+
vols=vols
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`DoubleMatrix_` also accepts rectangular nested sequences and supports mutable
|
|
422
|
+
`matrix[i, j]` access, so non-flat surfaces can be populated directly.
|
|
423
|
+
|
|
424
|
+
## API Reference
|
|
425
|
+
|
|
426
|
+
### Core Types
|
|
427
|
+
|
|
428
|
+
- `dal.Date_(year, month, day)` — Date object with arithmetic operations
|
|
429
|
+
- `dal.String_(value)` — String wrapper
|
|
430
|
+
- `dal.Cell_(value)` — Polymorphic value container (bool, double, Date, String)
|
|
431
|
+
- `dal.DoubleVector()` — Vector of doubles
|
|
432
|
+
- `dal.DoubleMatrix_(rows, cols, fill=0.0)` or `dal.DoubleMatrix_(nested_rows)` — mutable 2D matrix of doubles
|
|
433
|
+
|
|
434
|
+
### Models
|
|
435
|
+
|
|
436
|
+
- `dal.BSModelData_New(spot, vol, rate, div)` — Black-Scholes model
|
|
437
|
+
- `dal.DupireModelData_New(spot, rate, repo, spots, times, vols)` — Dupire local vol model
|
|
438
|
+
|
|
439
|
+
### Products
|
|
440
|
+
|
|
441
|
+
- `dal.Product_New(events_dates, events, *, settings=None)` — Create a script product; `settings` is a `ScriptProductSettings_` or `None`
|
|
442
|
+
- `dal.Product_Describe(product)` — Return a contract dictionary with schema `dal.script-product/2`
|
|
443
|
+
- `dal.Product_Debug(product)` — Return the legacy human-readable product structure as a string
|
|
444
|
+
- `dal.Product_DebugJson(product)` — Legacy JSON string (schema `dal.script-product/1`); rejects FIX and nonempty defaults with `DebugSchemaUnsupported`
|
|
445
|
+
- `dal.Product_DebugTree(product, ascii=False, width=125)` — Width-aware Unicode (or ASCII) product tree
|
|
446
|
+
|
|
447
|
+
### Valuation
|
|
448
|
+
|
|
449
|
+
- `dal.MonteCarlo_Value(product, modelData, num_path, method="sobol", use_bb=False, enable_aad=False, smooth=0.01, compiled=None)` — Monte Carlo pricing with optional AAD Greeks
|
|
450
|
+
- `dal.MonteCarlo_ValueWithSettings(product, modelData, num_path, *, valuation=None, simulation=None)` — Price with `ScriptValuationSettings_` and `MonteCarloSettings_`
|
|
451
|
+
- `dal.ScriptValuation_Explain(product, modelData, *, valuation=None)` — Return a dictionary describing one default price preparation
|
|
452
|
+
- `dal.ScriptSimulation_Explain(product, modelData, num_path, *, valuation=None, simulation=None)` — Run the full double valuation and return the `dal.script-simulation/1` exercise diagnostics dictionary
|
|
453
|
+
|
|
454
|
+
**Parameters:**
|
|
455
|
+
- `product` — Script product (from `Product_New`)
|
|
456
|
+
- `modelData` — Model data (from `BSModelData_New` or `DupireModelData_New`)
|
|
457
|
+
- `num_path` — Integer or valid `__index__` value in `1..2147483647`, excluding booleans and enums; floats such as `1.0` are rejected in both Value entries
|
|
458
|
+
- `method` — Random generator: `"sobol"` (default), `"mrg32"`, or `"irn"`
|
|
459
|
+
- `use_bb` — Use Brownian bridge construction (default `False`)
|
|
460
|
+
- `enable_aad` — Enable AAD for pathwise Greeks (default `False`)
|
|
461
|
+
- `smooth` — Finite, strictly positive fuzzy smoothing width (default `0.01`), validated even without AAD
|
|
462
|
+
- `compiled` — `True` selects the compiled evaluator; `None`/`False` uses tree-walk
|
|
463
|
+
|
|
464
|
+
**Returns:** Dictionary with keys:
|
|
465
|
+
- `"PV"` — Present value
|
|
466
|
+
- `"d_spot"`, `"d_vol"`, `"d_rate"`, `"d_div"` — Black-Scholes model AAD Greeks (only if `enable_aad=True`)
|
|
467
|
+
- `"d_<name>"` — AAD sensitivity to a named product constant, such as `"d_STRIKE"`
|
|
468
|
+
when the product declares a `STRIKE` constant
|
|
469
|
+
|
|
470
|
+
Both Value entries return `dict[str, float]` containing only `PV` and optional
|
|
471
|
+
`d_` parameter risks. PV is a path mean and risks are already normalized;
|
|
472
|
+
neither should be divided by the path count again. Historical fixing values
|
|
473
|
+
carry no fixing-risk keys. AAD preserves historical parameter dependencies,
|
|
474
|
+
uses hard historical decisions, and smooths future comparisons. Its PV can
|
|
475
|
+
differ from exact non-AAD pricing near a future discontinuity; `compiled` only
|
|
476
|
+
selects the evaluator implementation.
|
|
477
|
+
|
|
478
|
+
### Script Settings and Copies
|
|
479
|
+
|
|
480
|
+
The constructor signatures are below (`*` makes every field keyword-only).
|
|
481
|
+
Default construction followed by assignment to the same snake_case properties
|
|
482
|
+
is also supported.
|
|
483
|
+
|
|
484
|
+
```text
|
|
485
|
+
ScriptProductSettings_(*, default_index="")
|
|
486
|
+
ScriptValuationSettings_(*, evaluation_date=None, today_fixing="Model",
|
|
487
|
+
fixings=None)
|
|
488
|
+
MonteCarloSettings_(*, method="sobol", use_bb=False, enable_aad=False,
|
|
489
|
+
smooth=0.01, compiled=None, lsmc_basis_degree=3,
|
|
490
|
+
lsmc_training_paths=None)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
| Field | Accepted input / default | Property result |
|
|
494
|
+
|------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------|
|
|
495
|
+
| `default_index` | `str` or `String_`; empty means unbound | `str`, preserving spelling |
|
|
496
|
+
| `evaluation_date` | Valid DAL `Date_`, or `None` to capture global date at each call | A date copy or `None` |
|
|
497
|
+
| `today_fixing` | Policy enum or exact `Model` / `RequireHistorical` string; default `Model` | `TodayFixingPolicy_` member |
|
|
498
|
+
| `fixings` | `MarketFixingSnapshot_`, or `None` for global capture | Immutable snapshot handle or `None` |
|
|
499
|
+
| `method` | `str` / `String_`: `sobol`, `mrg32`, `irn` (case-insensitive); default `sobol` | `str`, preserving spelling |
|
|
500
|
+
| `use_bb`, `enable_aad` | Python `bool` only; default `False` | `bool` |
|
|
501
|
+
| `smooth` | Finite positive Python `int` / `float`, excluding bool and enums; default `0.01` | `float` |
|
|
502
|
+
| `compiled` | Python `bool` or `None`; default `None` selects tree | `bool` or `None` |
|
|
503
|
+
| `lsmc_basis_degree` | Integer or valid `__index__` in `1..8`, excluding bool, enums, floats; default `3` | `int` |
|
|
504
|
+
| `lsmc_training_paths` | Positive integer or valid `__index__` up to `2**31-1`, excluding bool, enums, floats; `None` uses `num_path` | `int` or `None` |
|
|
505
|
+
|
|
506
|
+
For exercise products, training and pricing counts can be set independently:
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
simulation = dal.MonteCarloSettings_(lsmc_training_paths=16_384)
|
|
510
|
+
result = dal.MonteCarlo_ValueWithSettings(
|
|
511
|
+
product, model, 262_144, simulation=simulation
|
|
512
|
+
)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
This fits the exercise policy on 16,384 paths and values it on the next 262,144
|
|
516
|
+
paths. Fixing `lsmc_training_paths` keeps the fitted policy unchanged when
|
|
517
|
+
`num_path` changes. The setting has no effect on products without `EXERCISE`.
|
|
518
|
+
|
|
519
|
+
The policy enum members are `dal.TodayFixingPolicy_.MODEL` and
|
|
520
|
+
`dal.TodayFixingPolicy_.REQUIREHISTORICAL`. Policy strings also accept DAL
|
|
521
|
+
`String_`, but must match the exact spelling and case with no extra whitespace.
|
|
522
|
+
Unknown names list the two allowed policies. `evaluation_date` does not accept
|
|
523
|
+
date strings, numeric serials, Python `datetime`, `DateTime_`, or `Cell_`.
|
|
524
|
+
An explicit date neither reads nor changes the global date.
|
|
525
|
+
|
|
526
|
+
For `default_index`, `method`, and the string form of `today_fixing`, ordinary
|
|
527
|
+
`str` subclasses and DAL `String_` are accepted. Python enum values, including
|
|
528
|
+
`str, enum.Enum` and `enum.StrEnum` members, raise `TypeError` in constructors
|
|
529
|
+
and setters; the native `TodayFixingPolicy_` members above remain valid policies.
|
|
530
|
+
Event text accepts string-derived enum members under its usual text validation
|
|
531
|
+
rules.
|
|
532
|
+
|
|
533
|
+
Settings parameters accept their corresponding native settings object or `None`
|
|
534
|
+
(fresh defaults), not an entire settings dictionary.
|
|
535
|
+
|
|
536
|
+
`copy.copy` and `copy.deepcopy` create independent settings values; both share
|
|
537
|
+
the immutable snapshot handle. Ordinary Python assignment aliases the object.
|
|
538
|
+
Changing a returned date copy does not update settings: assign the property to
|
|
539
|
+
replace it. Failed setters preserve the old value. Product construction copies the table and settings; Value and Explain
|
|
540
|
+
copy settings and native handles while holding the GIL, then release it for
|
|
541
|
+
native work. Workers use native data and never call Python callbacks or read
|
|
542
|
+
mutable Python dictionaries. Avoid modifying inputs during their conversion.
|
|
543
|
+
Native valuations still serialize through DAL's valuation/mutation barrier.
|
|
544
|
+
|
|
545
|
+
### Script Compatibility and Errors
|
|
546
|
+
|
|
547
|
+
The high-level product keyword remains `events_dates`; the low-level
|
|
548
|
+
`dal._dal.Product_New` keyword is `dates` and its date-table elements must
|
|
549
|
+
already be `Cell_`. The high-level wrapper preserves existing cells and wraps
|
|
550
|
+
only non-Cell values. Use DAL dates for event rows and strings for definitions
|
|
551
|
+
or schedules; numeric cells do not gain an Excel-date interpretation.
|
|
552
|
+
Event text accepts `str` or `String_`. Text with embedded NUL is rejected.
|
|
553
|
+
|
|
554
|
+
Legacy `MonteCarlo_Value` retains all valid three-to-eight positional calls
|
|
555
|
+
and the original keywords/defaults. Its valid flag and float conversions are
|
|
556
|
+
preserved. New settings cannot be mixed into that call; flat `method`,
|
|
557
|
+
`compiled`, and other simulation options belong inside `MonteCarloSettings_`
|
|
558
|
+
when using `MonteCarlo_ValueWithSettings`.
|
|
559
|
+
|
|
560
|
+
Unknown/duplicate keywords, extra positional arguments, wrong settings types,
|
|
561
|
+
and invalid input types raise `TypeError`; unknown settings attributes raise
|
|
562
|
+
`AttributeError`. Invalid values and native failures raise `RuntimeError`, with
|
|
563
|
+
identifiers such as `InvalidPathCount`, `InvalidSetting`, `InvalidSmoothing`,
|
|
564
|
+
`InvalidTodayFixingPolicy`, `InvalidFixingDate`, `MissingFixing`,
|
|
565
|
+
`InvalidLsmcBasisDegree`, and
|
|
566
|
+
`MultipleModelIndices`, plus field
|
|
567
|
+
and constraint context. Script errors retain source row/position and index/date
|
|
568
|
+
details. Validation may occur at construction/assignment (types, policy, date,
|
|
569
|
+
smoothing), description (syntax/default index), or preparation (history).
|
|
570
|
+
Empty or no-PAYS products can be described but Value/Explain reject them with
|
|
571
|
+
`InvalidScriptStructure`. Valid wholly expired products return zero only after
|
|
572
|
+
validation; errors never become successful `PV=0` results.
|
|
573
|
+
|
|
574
|
+
### Script Diagnostics
|
|
575
|
+
|
|
576
|
+
High-level `dal.Product_Describe` and `dal.ScriptValuation_Explain` return ordinary
|
|
577
|
+
dictionaries. Their low-level counterparts in `dal._dal` (also re-exported by
|
|
578
|
+
`dal.dal`) return the C++ JSON as `str`; the high-level wrappers apply `json.loads`
|
|
579
|
+
without renaming keys or converting date strings into DAL dates.
|
|
580
|
+
`dal.ScriptSimulation_Explain(product, modelData, num_path, *, valuation=None,
|
|
581
|
+
simulation=None)` follows the same split and returns the `dal.script-simulation/1`
|
|
582
|
+
dictionary. Unlike the valuation Explain it runs the full double valuation with
|
|
583
|
+
`num_path` pricing paths plus a separate block of `lsmc_training_paths`
|
|
584
|
+
training paths (defaulting to `num_path`)
|
|
585
|
+
(path generation plus workers plus the exercise regressions),
|
|
586
|
+
requires the same integer path count as the Value entries, rejects
|
|
587
|
+
`enable_aad=True` settings with `UnsupportedExecutionMode`, and reports the
|
|
588
|
+
simulation echo with `lsmc_basis_degree` and `lsmc_training_paths` (null when
|
|
589
|
+
unset), the explicit pricing count `n_paths`, and one
|
|
590
|
+
`exercise_events` entry per exercise date (degree, regressor index,
|
|
591
|
+
in-the-money condition-true count, coefficients, degenerate flag/reason,
|
|
592
|
+
exercise rate).
|
|
593
|
+
Regression counts describe the training block; exercise rates describe the
|
|
594
|
+
pricing block. Both blocks use deterministic Sobol points and do not overlap.
|
|
595
|
+
Products without `EXERCISE` return an empty `exercise_events` list. The
|
|
596
|
+
[early-exercise example](examples/013.exercise_bermudan.py) prices the
|
|
597
|
+
Bermudan and weekly-exercise puts and reads the diagnostic. The example runs
|
|
598
|
+
3 x 2^18-path LSMC valuations by default; set `DAL_EXAMPLE_NPATHS` to a
|
|
599
|
+
smaller path count for a quicker smoke run.
|
|
600
|
+
|
|
601
|
+
- **Describe**, schema `dal.script-product/2`, parses all contract syntax with
|
|
602
|
+
original/canonical identities, input rows, events, source positions and nodes.
|
|
603
|
+
It has no market I/O, model, global-date read or valuation phase. Success does
|
|
604
|
+
not establish that the product can be priced.
|
|
605
|
+
- **Explain**, schema `dal.script-valuation/1`, prepares independently on every
|
|
606
|
+
call. It may read history, initialize a model and replay past state, but starts
|
|
607
|
+
no workers and generates no paths. Its fixed simulation is exact non-AAD,
|
|
608
|
+
Sobol, no bridge, smoothing `0.01`, tree. It accepts no path count or simulation
|
|
609
|
+
settings and does not describe a preceding compiled/AAD call or cache the next
|
|
610
|
+
Value. Its requests/uses, history IDs, model slots, live-event/sample mappings
|
|
611
|
+
and numeraire requests come from that preparation.
|
|
612
|
+
|
|
613
|
+
JSON null/bool/array/object values become Python None/bool/list/dict. Date strings,
|
|
614
|
+
policy/source names, and schema versions retain the
|
|
615
|
+
[C++ diagnostic contract](../docs/methodology/script_engine.md#product-archive-and-diagnostics).
|
|
616
|
+
`request_id` and `history_value_id` address different arrays; use `live_events`
|
|
617
|
+
to map all-event IDs to future-event indices. Diagnostics are not loadable
|
|
618
|
+
product archives. Python provides no public script-product serializer or pickle
|
|
619
|
+
API; `Product_DebugJson` remains the separate legacy JSON-string interface.
|
|
620
|
+
|
|
621
|
+
### Random Generators
|
|
622
|
+
|
|
623
|
+
- `dal.PseudoRSG_New(seed, ndim=1)` — Pseudo-random generator (MRG32k32a)
|
|
624
|
+
- `dal.SobolRSG_New(i_path, ndim=1, precise=False, polish=False)` — Sobol
|
|
625
|
+
quasi-random generator; `polish` enables the Newton correction and `precise`
|
|
626
|
+
selects its CDF, so the precise-CDF correction requires both flags to be `True`
|
|
627
|
+
- `dal.PseudoRSG_Get_Uniform(rsg, num_paths)` — Uniform samples [0, 1]
|
|
628
|
+
- `dal.PseudoRSG_Get_Normal(rsg, num_paths)` — Standard normal samples
|
|
629
|
+
- `dal.SobolRSG_Get_Uniform(rsg, num_paths)` — Sobol uniform samples
|
|
630
|
+
- `dal.SobolRSG_Get_Normal(rsg, num_paths)` — Sobol normal samples
|
|
631
|
+
|
|
632
|
+
### Global State
|
|
633
|
+
|
|
634
|
+
- `dal.EvaluationDate_Set(date)` — Set the process-wide evaluation date; waits
|
|
635
|
+
for an in-progress native valuation or scoped override
|
|
636
|
+
- `dal.EvaluationDate_Get()` — Read the stable process-wide evaluation date;
|
|
637
|
+
remains available while valuation runs
|
|
638
|
+
|
|
639
|
+
Both bindings release the GIL before entering native synchronization.
|
|
640
|
+
|
|
641
|
+
## Testing
|
|
642
|
+
|
|
643
|
+
Build and run the full workspace suite:
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
bash ../build_linux.sh --full
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
After an editable install, run focused Python tests directly:
|
|
650
|
+
|
|
651
|
+
```bash
|
|
652
|
+
python -m pytest tests -k "test_date" -v
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Tests are located in `tests/` and cover:
|
|
656
|
+
|
|
657
|
+
- Date arithmetic and comparisons
|
|
658
|
+
- Vector and matrix operations
|
|
659
|
+
- Model construction (BS, Dupire)
|
|
660
|
+
- Monte Carlo pricing accuracy vs Black-Scholes analytical formulas
|
|
661
|
+
- AAD Greek computation and validation
|
|
662
|
+
- Random number generator properties
|
|
663
|
+
- Curve construction plus single and staged multi-curve calibration
|
|
664
|
+
- Rate cashflow planning, pricing, and node sensitivities
|
|
665
|
+
- Staged XCCY basis calibration, sensitivity matrices, axes, and availability metadata
|
|
666
|
+
- Resettable/MTM XCCY construction with immutable fixing snapshots
|
|
667
|
+
- Joint domestic/foreign/basis XCCY calibration, including matrix and named-range contracts
|
|
668
|
+
|
|
669
|
+
## Performance Benchmarks
|
|
670
|
+
|
|
671
|
+
The [Python benchmark suite](benchmarks/README.md) provides 90 public-interface
|
|
672
|
+
workloads mapped to the C++ benchmark inventory: RNG, script construction and MC,
|
|
673
|
+
single/multi-curve and XCCY calibration, node risk, and quote-risk provenance/aggregation.
|
|
674
|
+
It records raw samples, workload sizes, native-module identity, and a Markdown summary.
|
|
675
|
+
|
|
676
|
+
From `dal-python/`, using a current installed wheel or editable build:
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
python benchmarks/run_benchmarks.py --smoke
|
|
680
|
+
python benchmarks/run_benchmarks.py --samples 10 --warmups 2
|
|
681
|
+
python benchmarks/run_benchmarks.py --group rate_risk_perf --filter generic
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
The normal pytest suite checks every workload at smoke scale without a speed
|
|
685
|
+
threshold. Linux CI also gates all 90 full-scale cases against independent base/head
|
|
686
|
+
builds, using two rounds of ten interleaved processes and a strict 4% threshold in
|
|
687
|
+
both rounds. The coverage map explicitly records unbound C++ kernels and fixture
|
|
688
|
+
differences; Python timings include binding and result-conversion costs.
|
|
689
|
+
|
|
690
|
+
The Linux gate also checks 31 comparison workloads against DAL,
|
|
691
|
+
QuantLib-Python and rateslib with independent numerical oracles. Its report covers
|
|
692
|
+
discount queries, IRS PV and DV01, Monte Carlo vanilla/barrier prices and
|
|
693
|
+
Delta/Vega/Rho, plus single, staged/joint multi-curve and XCCY calibration.
|
|
694
|
+
Rateslib equity MC and QuantLib simultaneous joint calibration are explicitly
|
|
695
|
+
unsupported; every other case must complete successfully.
|
|
696
|
+
Node risk uses DAL reverse AAD, rateslib forward AD and QuantLib finite differences,
|
|
697
|
+
with each algorithm identified in the evidence. Third-party dependencies are pinned
|
|
698
|
+
separately for benchmarks; they are not DAL runtime dependencies. See the
|
|
699
|
+
[comparison methodology and commands](benchmarks/README.md#third-party-comparison).
|
|
700
|
+
|
|
701
|
+
## Project Structure
|
|
702
|
+
|
|
703
|
+
```
|
|
704
|
+
dal-python/
|
|
705
|
+
├── CMakeLists.txt # Build configuration
|
|
706
|
+
├── pyproject.toml # Python package metadata (scikit-build-core)
|
|
707
|
+
├── build_sdist.sh # Source distribution helper
|
|
708
|
+
├── build_wheel.sh # Wheel build helpers (POSIX and PowerShell)
|
|
709
|
+
├── build_wheel.ps1
|
|
710
|
+
├── run_tests.sh # Standalone binding test helpers (POSIX and PowerShell)
|
|
711
|
+
├── run_tests.ps1
|
|
712
|
+
├── examples/ # Numbered end-to-end Python examples
|
|
713
|
+
├── benchmarks/ # Public-interface performance runner and C++ coverage map
|
|
714
|
+
├── scripts/ # Release verification and installed-wheel smoke helpers
|
|
715
|
+
├── src/
|
|
716
|
+
│ ├── bindings/
|
|
717
|
+
│ │ ├── module.cpp # pybind11 module definition
|
|
718
|
+
│ │ ├── bindings.h # shared binding helpers
|
|
719
|
+
│ │ ├── core.cpp # core types (Date_, String_, Cell_, vectors, DoubleMatrix_)
|
|
720
|
+
│ │ ├── global.cpp # Handle_<T> opaque types, EvaluationDate_Get/Set
|
|
721
|
+
│ │ ├── models.cpp # model types (BSModelData_, etc.)
|
|
722
|
+
│ │ ├── random.cpp # random number generators
|
|
723
|
+
│ │ ├── script.cpp # scripting engine bindings
|
|
724
|
+
│ │ ├── calendar.cpp # holiday calendars and business-day conventions
|
|
725
|
+
│ │ ├── curve.cpp # curve calibration, instruments, and interpolation
|
|
726
|
+
│ │ └── value.cpp # Monte Carlo valuation (MonteCarlo_Value)
|
|
727
|
+
│ └── dal/
|
|
728
|
+
│ ├── __init__.py # Package initialization
|
|
729
|
+
│ └── api.py # High-level Python API wrappers
|
|
730
|
+
├── tests/
|
|
731
|
+
│ ├── conftest.py # Pytest fixtures
|
|
732
|
+
│ └── test_*.py # Test modules
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
## Architecture
|
|
736
|
+
|
|
737
|
+
The Python bindings are generated by pybind11 from domain-organized binding files. The build process:
|
|
738
|
+
|
|
739
|
+
1. **CMake** configures the build and locates the DAL C++ libraries plus either
|
|
740
|
+
the isolated pybind11 build requirement or the pinned repository fallback
|
|
741
|
+
2. **C++ compiler** builds `_dal.cpython-*.so` extension module from the domain-organized `src/bindings/*.cpp` files
|
|
742
|
+
3. **scikit-build-core** packages everything into an installable wheel
|
|
743
|
+
|
|
744
|
+
When consuming an installed DAL package under MSVC, CMake applies the package's
|
|
745
|
+
`DAL_CPP_MSVC_RUNTIME_LIBRARY` value to `_dal` through
|
|
746
|
+
`dal_cpp_apply_msvc_runtime`. The helper is a no-op on other toolchains.
|
|
747
|
+
|
|
748
|
+
The hand-written Python code in `src/dal/` provides:
|
|
749
|
+
- `__init__.py` — Re-exports all pybind11-generated symbols
|
|
750
|
+
- `api.py` — Convenience wrappers (e.g., `Product_New` with automatic type conversion, `calibrate_curve(...)` for curve calibration)
|
|
751
|
+
|
|
752
|
+
## Curve Calibration
|
|
753
|
+
|
|
754
|
+
The `curve` bindings (`dal-python/src/bindings/curve.cpp`) expose the supported Python
|
|
755
|
+
curve-construction and calibration workflows:
|
|
756
|
+
|
|
757
|
+
- **Instrument builders** — `Deposit_New`, `FRA_New`, `Future_New`, `Swap_New`, `OISSwap_New`, `BasisSwap_New`, `CrossCurrencySwap_New`
|
|
758
|
+
- **Curve factories** — `DiscountPWLF_New`, `DiscountZeroRate_New`
|
|
759
|
+
- **Calibration entry points** — `CalibrateSingleCurve`, `CalibrateMultiCurveBundle`, `CalibrateXccyMarket`, `CalibrateJointXccyMarket`
|
|
760
|
+
- **Enums** — `CurveParameterization` (`PIECEWISE_LINEAR_FWD`, `PIECEWISE_CONSTANT_FWD`, `ZERO_RATE`, `LOG_DISCOUNT`), `CurveSolveMode` (`EXACT`, `APPROXIMATE`), `CurveJacobianMode` (`ANALYTIC`, `BUMPED`), `LogDfScheme` (`LOG_LINEAR`, `LOG_CUBIC_NATURAL`, `MIXED`), `XccyNotionalMode` (`FIXED`, `RESETTABLE`, `MARK_TO_MARKET`)
|
|
761
|
+
- **Spec builders** — `CurveCalibrationSpecBuilder_`, `CrossCurrencyCalibrationSpecBuilder_`, and `JointXccyCalibrationSpecBuilder_`
|
|
762
|
+
|
|
763
|
+
The `dal.calibrate_curve(...)` helper in `api.py` wraps the common single-curve path with Python-friendly defaults. The underlying C++ methodology is documented in the [yield-curve guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/methodology/yield_curve.md) and [Jacobian guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/methodology/yield_curve_jacobian.md).
|
|
764
|
+
|
|
765
|
+
### Continuously Compounded Zero-Rate Curves
|
|
766
|
+
|
|
767
|
+
Build a persistent zero-rate curve directly with future-only nodes:
|
|
768
|
+
|
|
769
|
+
```python
|
|
770
|
+
today = dal.Date_(2026, 1, 2)
|
|
771
|
+
node_dates = [dal.Date_(2027, 1, 2), dal.Date_(2028, 1, 2)]
|
|
772
|
+
|
|
773
|
+
curve = dal.DiscountZeroRate_New(
|
|
774
|
+
"usd_zero",
|
|
775
|
+
"USD",
|
|
776
|
+
today,
|
|
777
|
+
node_dates,
|
|
778
|
+
[0.02, 0.025],
|
|
779
|
+
day_count=dal.DayBasis_("ACT_365F"),
|
|
780
|
+
log_df_scheme=dal.LogDfScheme.LOG_LINEAR,
|
|
781
|
+
)
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
Each continuously compounded decimal rate $z_i$ is mapped to
|
|
785
|
+
`logDF_i = -z_i * YearFrac(today, node_date_i)`. The anchor log DF is fixed at zero
|
|
786
|
+
and has no zero-rate parameter. `LOG_LINEAR`, `LOG_CUBIC_NATURAL`, and `MIXED` all
|
|
787
|
+
interpolate the mapped log DFs. Before the anchor, `LOG_LINEAR` and `MIXED` clamp the
|
|
788
|
+
log DF to zero, while `LOG_CUBIC_NATURAL` extends its first cubic segment. Beyond the
|
|
789
|
+
last node, every scheme uses the last two mapped log-DF nodes as a secant. The returned
|
|
790
|
+
`DiscountZeroRate_` exposes read-only `anchor_date`, `node_dates`, `zero_rates`,
|
|
791
|
+
`day_count`, and `log_df_scheme` properties.
|
|
792
|
+
|
|
793
|
+
For calibration, select `CurveParameterization.ZERO_RATE` and supply strictly-future
|
|
794
|
+
knots. `initialGuess_` is a decimal continuously compounded zero rate copied to every
|
|
795
|
+
node. Both low-level `CalibrateSingleCurve` and the convenience helper use the analytic
|
|
796
|
+
AAD Jacobian when the normal single-discount-curve eligibility gates are met:
|
|
797
|
+
|
|
798
|
+
```python
|
|
799
|
+
result = dal.calibrate_curve(
|
|
800
|
+
today,
|
|
801
|
+
"USD",
|
|
802
|
+
instruments,
|
|
803
|
+
node_dates,
|
|
804
|
+
settings={
|
|
805
|
+
"parameterization": dal.CurveParameterization.ZERO_RATE,
|
|
806
|
+
"log_df_scheme": dal.LogDfScheme.LOG_CUBIC_NATURAL,
|
|
807
|
+
"initial_guess": 0.02,
|
|
808
|
+
},
|
|
809
|
+
jacobian_mode=dal.CurveJacobianMode.ANALYTIC,
|
|
810
|
+
base_curve=base_curve, # optional: zero rates are spread coordinates over this base
|
|
811
|
+
)
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
Python exposes single, staged multi-curve, staged XCCY basis, and simultaneous
|
|
815
|
+
joint XCCY calibration. A base curve is multiplied into the calibrated component;
|
|
816
|
+
it is not a replacement for the pricing discount curve required by a forward-curve stage.
|
|
817
|
+
Staged XCCY supports both the backward-compatible
|
|
818
|
+
`CalibrateXccyMarket(spec)` call and `CalibrateXccyMarket(spec, options)`.
|
|
819
|
+
`CrossCurrencyCalibrationOptions_` defaults to `ANALYTIC` with
|
|
820
|
+
`compute_forward_jacobian = True` and
|
|
821
|
+
`compute_eff_jacobian_inverse = True`; trailing-underscore property names are
|
|
822
|
+
available alongside the snake-case names.
|
|
823
|
+
|
|
824
|
+
The matrices remain on `result.diagnostics`. `diagnostics.jacobian` has
|
|
825
|
+
instrument rows and basis-parameter columns;
|
|
826
|
+
`diagnostics.eff_jacobian_inverse` has the reversed axes.
|
|
827
|
+
`instrument_names` follows input order and may contain duplicate labels.
|
|
828
|
+
`parameter_knot_dates` follows the spec's knot order and labels the
|
|
829
|
+
piecewise-constant basis curve's right-forward parameters. The diagnostics also
|
|
830
|
+
publish `residual_tolerance`, `jacobian_scaling == "unscaled"`,
|
|
831
|
+
`eff_jacobian_inverse_scaling == "solver_scaled"`, and independent
|
|
832
|
+
`jacobian_availability` / `eff_jacobian_inverse_availability` values:
|
|
833
|
+
`available`, `not_requested`, or `not_available_for_mode`.
|
|
834
|
+
|
|
835
|
+
For a raw decimal quote-bump vector `dq`, the solver-scaled effective inverse
|
|
836
|
+
`E` maps parameters as `dx = E * dq / residual_tolerance`. An unavailable
|
|
837
|
+
matrix is empty; inspect its availability property to distinguish an explicit
|
|
838
|
+
opt-out from a mode limitation.
|
|
839
|
+
|
|
840
|
+
### Resettable and Joint XCCY Calibration
|
|
841
|
+
|
|
842
|
+
Use `CrossCurrencySwapConfigBuilder_` to set the currency pair, notionals, leg
|
|
843
|
+
conventions, `notional_mode`, `fx_reset`, and explicit `domestic_rate_fixing` /
|
|
844
|
+
`foreign_rate_fixing` identities. `MarketFixingSnapshot_New` takes a nested
|
|
845
|
+
dictionary whose keys are index names and whose values map `DateTime_` objects to
|
|
846
|
+
observations. One immutable snapshot can hold domestic rate, foreign rate, and FX
|
|
847
|
+
fixings for an already-started swap:
|
|
848
|
+
|
|
849
|
+
```python
|
|
850
|
+
snapshot = dal.MarketFixingSnapshot_New({
|
|
851
|
+
"USD-JOINT-3M": {historical_fixing: 0.040},
|
|
852
|
+
"EUR-JOINT-3M": {historical_fixing: 0.030},
|
|
853
|
+
"FX[EUR/USD]": {historical_fixing: 1.20},
|
|
854
|
+
})
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
`JointCurrencyCurveSpec_` holds the ordered domestic or foreign
|
|
858
|
+
`JointCurveDeclaration_` objects. `XccyBasisCurveDeclaration_` holds configured
|
|
859
|
+
XCCY instruments and basis knots. Assemble those groups with
|
|
860
|
+
`JointXccyCalibrationSpecBuilder_`, then call
|
|
861
|
+
`CalibrateJointXccyMarket(builder.build())`. The result exposes the domestic and
|
|
862
|
+
foreign curve blocks, `fx_forward_curve`, basis curve, retained snapshot, group
|
|
863
|
+
diagnostics, full market/model/residual vectors, analytic Jacobian, effective
|
|
864
|
+
inverse, and named `parameter_ranges` / `residual_ranges`. Pass
|
|
865
|
+
`JointXccyCalibrationOptions_` to select `ANALYTIC` or `BUMPED` and to disable
|
|
866
|
+
either diagnostic matrix. The `eff_jacobian_inverse` matrix has shape
|
|
867
|
+
`totalParameters x totalResiduals` and is the weighted inverse of the solver's
|
|
868
|
+
tolerance-scaled Jacobian. Transforming a raw decimal quote bump therefore
|
|
869
|
+
requires division by the spec's `tolerance_`; see the
|
|
870
|
+
[Jacobian methodology](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/methodology/yield_curve_jacobian.md#joint-xccy-jacobian-layout).
|
|
871
|
+
|
|
872
|
+
The runnable [joint XCCY calibration example](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/dal-python/examples/007.xccy_joint_calibration.py)
|
|
873
|
+
uses an explicit fixing snapshot for a started MTM trade. It prints convergence,
|
|
874
|
+
the maximum absolute residual, Jacobian dimensions, named parameter and residual
|
|
875
|
+
half-open ranges, and every FX-forward date and value. With the `dal` package
|
|
876
|
+
installed in the active environment, run it from the repository root:
|
|
877
|
+
|
|
878
|
+
```bash
|
|
879
|
+
python dal-python/examples/007.xccy_joint_calibration.py
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
## Rate Cashflow Pricing and Node Risk
|
|
883
|
+
|
|
884
|
+
Typed rate trades price and produce AAD node sensitivities against a
|
|
885
|
+
component-keyed market. Every pricing and sensitivity function is keyword-only,
|
|
886
|
+
returns read-only results, and releases the GIL around native work. A complete
|
|
887
|
+
deposit example, mirroring the fixtures in `tests/test_curve_pricing.py`:
|
|
888
|
+
|
|
889
|
+
```python
|
|
890
|
+
import dal
|
|
891
|
+
|
|
892
|
+
today, maturity = dal.Date_(2026, 1, 15), dal.Date_(2027, 1, 15)
|
|
893
|
+
|
|
894
|
+
# 1) Curve and market: curves are registered by component key; trade terms
|
|
895
|
+
# address them through their *_component_key fields.
|
|
896
|
+
curve = dal.DiscountPWC_New("usd", "USD", [maturity], [0.04])
|
|
897
|
+
market = dal.RatePricingMarket_(
|
|
898
|
+
valuation_time=dal.DateTime_(today, 10, 30),
|
|
899
|
+
result_currency="USD",
|
|
900
|
+
curve_components={"discount": curve, "forecast": curve},
|
|
901
|
+
fixings=dal.MarketFixingSnapshot_New({}),
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# 2) Index convention, terms, and trade
|
|
905
|
+
index = dal.RateIndexConvention_New(
|
|
906
|
+
dal.PeriodLength_New("3M"), dal.DayBasis_New("ACT_365F"), dal.CollateralType_OIS())
|
|
907
|
+
terms = dal.DepositTradeTerms_(
|
|
908
|
+
notional=100.0, contract_rate=0.05, lend=True,
|
|
909
|
+
index=index, discount_component_key="discount")
|
|
910
|
+
trade = dal.RateTradeDefinition_(
|
|
911
|
+
instrument_id="deposit-1", instrument_type=dal.RateInstrumentType.DEPOSIT,
|
|
912
|
+
trade_date=today, start_date=today, maturity_date=maturity,
|
|
913
|
+
currency="USD", terms=terms)
|
|
914
|
+
|
|
915
|
+
# 3) Single-trade sensitivity (a deposit depends only on the discount component,
|
|
916
|
+
# so a "forecast" request returns reason="TRADE_DOES_NOT_DEPEND_ON_COMPONENT")
|
|
917
|
+
r = dal.RateTradeNodeSensitivities(trade=trade, market=market, component_key="discount")
|
|
918
|
+
assert r.eligible and len(r.gradient) == 1 and r.reason == ""
|
|
919
|
+
|
|
920
|
+
# 4) Batch: component_keys must be a list — a tuple raises TypeError before any
|
|
921
|
+
# native work starts. Deterministic trade-major then key order.
|
|
922
|
+
cells = dal.RateTradeNodeSensitivitiesBatch(
|
|
923
|
+
trades=[trade], market=market, component_keys=["discount", "forecast"])
|
|
924
|
+
for c in cells:
|
|
925
|
+
print(c.instrument_id, c.component_key, c.result.eligible, c.result.reason)
|
|
926
|
+
|
|
927
|
+
# 5) Portfolio aggregation
|
|
928
|
+
agg = dal.AggregateRatePortfolioNodeRisk(
|
|
929
|
+
trades=[trade], market=market, component_keys=["discount"])
|
|
930
|
+
print(agg.policy) # UnconvertedByActualPvCcy
|
|
931
|
+
comp = agg.components[0] # .component_key / .node_count / .node_dates /
|
|
932
|
+
# .node_components / .values
|
|
933
|
+
print(dict(agg.pv_by_actual_pv_ccy)) # {'USD': ...}
|
|
934
|
+
print(agg.meta[0].reason, agg.meta[0].actual_pv_ccy)
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
Native per-trade pricing and sensitivity failures are returned as data. Invalid
|
|
938
|
+
Python argument types or shapes can still raise before native execution.
|
|
939
|
+
A trade that fails passive pricing — for example
|
|
940
|
+
`notional=float("nan")` — keeps `PriceRateTrades` field-level detail in
|
|
941
|
+
`result[0].error`, while every sensitivity call returns the canonical read-only
|
|
942
|
+
four-field result: `eligible=False`, `pv=0.0`, `gradient=[]`, and a stable
|
|
943
|
+
`reason` token (`"TRADE_VALIDATION_FAILED"` here). In a batch, failed entries
|
|
944
|
+
are isolated per (trade, component) cell; the remaining entries are unaffected.
|
|
945
|
+
|
|
946
|
+
All seven families — deposit, FRA, future, OIS, IRS, basis swap, and XCCY —
|
|
947
|
+
share this call pattern and differ only in their terms class. The per-family
|
|
948
|
+
terms fields, the addressable components, and the C++ and Excel equivalents are
|
|
949
|
+
in the [public API guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/public-api.md#c-rate-cashflow-pricing).
|
|
950
|
+
|
|
951
|
+
For repeated valuation, `PreparedRateTrades_New(trades=trades)` owns an immutable
|
|
952
|
+
copy of the portfolio and prepares IRS/OIS/basis coupon geometry once. Pass the
|
|
953
|
+
current market on every call:
|
|
954
|
+
|
|
955
|
+
```python
|
|
956
|
+
prepared = dal.PreparedRateTrades_New(trades=[trade])
|
|
957
|
+
prices = dal.PreparedRateTrades_Get_Prices(prepared=prepared, market=market)
|
|
958
|
+
cells = dal.PreparedRateTrades_Get_NodeSensitivities(
|
|
959
|
+
prepared=prepared, market=market, component_keys=["discount"])
|
|
960
|
+
assert prepared.size == 1
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
The results and failure rules match the ordinary APIs. Market, fixing, PV and AAD
|
|
964
|
+
values are recomputed; only trade geometry is retained. Create a new prepared
|
|
965
|
+
object when trade terms or calendar definitions change. Concurrent calls may
|
|
966
|
+
share a prepared object with independent immutable markets. The
|
|
967
|
+
[prepared IRS example](examples/011.prepared_rate_pricing.py) changes market
|
|
968
|
+
rates and checks every PV and node derivative against ordinary pricing.
|
|
969
|
+
|
|
970
|
+
## Quote-Space DV01
|
|
971
|
+
|
|
972
|
+
Freeze calibration provenance with
|
|
973
|
+
`BuildSingleCurveQuoteRiskProvenance`,
|
|
974
|
+
`BuildJointXccyQuoteRiskProvenance`,
|
|
975
|
+
`BuildStagedXccyBasisQuoteRiskProvenance`, or
|
|
976
|
+
`BuildJointMultiCurveQuoteRiskProvenance`, then call
|
|
977
|
+
`AggregateRatePortfolioQuoteRisk(trades=..., market=..., provenances=...)`.
|
|
978
|
+
All five functions are keyword-only, release the GIL around native work, and
|
|
979
|
+
return read-only results.
|
|
980
|
+
|
|
981
|
+
Each bucket contains its calibration/axis identity, ordered quote coordinate,
|
|
982
|
+
actual PV currency, `d_pv_d_decimal_quote`, and `dv01`. The former is price per
|
|
983
|
+
`+1.0` decimal quote move; `dv01` is price per `+1 bp` and therefore equals the
|
|
984
|
+
former times `1e-4`. Axis/state schemes are
|
|
985
|
+
`dal.quote-risk-axis/1+jcs+sha256` and
|
|
986
|
+
`dal.quote-risk-state/1+jcs+sha256` for the three existing domains. Generic joint
|
|
987
|
+
provenance uses the corresponding `/2+jcs+sha256` schemes; fingerprint values begin with `sha256:`.
|
|
988
|
+
Aggregation verifies the current market state and performs neither quote bumps
|
|
989
|
+
nor recalibration.
|
|
990
|
+
|
|
991
|
+
The policy is `UnconvertedByActualPvCcy`: PV and quote-risk buckets remain
|
|
992
|
+
separate by each trade's actual PV currency, without FX conversion. Ordinary
|
|
993
|
+
staged multi-curve chain rules are not supported. `JointMultiCurveCalibrationSpec_`,
|
|
994
|
+
`JointMultiCurveCalibrationOptions_`, and `CalibrateJointMultiCurveBundle`
|
|
995
|
+
provide a reachable generic joint calibration with read-only results and owning
|
|
996
|
+
curve maps. Its inverse request defaults to false; enabling it for an
|
|
997
|
+
underdetermined EXACT system selects a fixed initial-Jacobian subspace and can
|
|
998
|
+
change the selected solution. See the
|
|
999
|
+
[generic joint example](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/dal-python/examples/010.generic_joint_quote_risk.py)
|
|
1000
|
+
and [mapping contract](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/methodology/generic_joint_quote_risk.md).
|
|
1001
|
+
The runnable
|
|
1002
|
+
[single-curve example](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/dal-python/examples/009.quote_risk.py)
|
|
1003
|
+
prints the policy, both fingerprints, and all buckets; the
|
|
1004
|
+
[joint XCCY example](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/dal-python/examples/007.xccy_joint_calibration.py)
|
|
1005
|
+
constructs joint provenance.
|
|
1006
|
+
|
|
1007
|
+
## Troubleshooting
|
|
1008
|
+
|
|
1009
|
+
### "Cannot find DAL::public" during build
|
|
1010
|
+
|
|
1011
|
+
Ensure `DAL_INSTALL_PREFIX` points to the correct staged DAL installation:
|
|
1012
|
+
|
|
1013
|
+
```text
|
|
1014
|
+
<stage>/lib/cmake/dal-public/dal-publicConfig.cmake
|
|
1015
|
+
<stage>/lib/cmake/dal-cpp/dal-cppConfig.cmake
|
|
1016
|
+
<stage>/include/dal/
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
The library files beside the package metadata use the platform's native suffix,
|
|
1020
|
+
such as `.a` on Linux or `.lib` on Windows; do not diagnose the prefix by
|
|
1021
|
+
assuming one suffix.
|
|
1022
|
+
|
|
1023
|
+
### "ImportError: No module named _dal"
|
|
1024
|
+
|
|
1025
|
+
The extension module failed to build. Check the build logs:
|
|
1026
|
+
|
|
1027
|
+
```bash
|
|
1028
|
+
uv pip install --reinstall -e . -v "--config-settings=cmake.define.DAL_INSTALL_PREFIX=/absolute/path/to/build/stage/<platform-preset>"
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
Replace `<platform-preset>` with the stage produced by the active compiler and
|
|
1032
|
+
configuration.
|
|
1033
|
+
|
|
1034
|
+
### Tests fail with "ModuleNotFoundError"
|
|
1035
|
+
|
|
1036
|
+
Ensure you're using the virtual environment:
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
uv run --no-sync python -c "import dal; print(dal.__version__)"
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
## License
|
|
1043
|
+
|
|
1044
|
+
MIT License. See the repository [LICENSE](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/LICENSE).
|
|
1045
|
+
|
|
1046
|
+
## Contributing
|
|
1047
|
+
|
|
1048
|
+
Follow the repository [contributor guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/CONTRIBUTING.md). Binding changes
|
|
1049
|
+
should include Python tests and updates to the
|
|
1050
|
+
[public API guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/public-api.md) when the supported surface changes.
|
|
1051
|
+
|
|
1052
|
+
## See Also
|
|
1053
|
+
|
|
1054
|
+
- [DAL C++ Library](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib) — Workspace overview
|
|
1055
|
+
- [Installation guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/installation.md) — Canonical setup commands
|
|
1056
|
+
- [Public API guide](https://github.com/wegamekinglc/Derivatives-Algorithms-Lib/blob/master/docs/public-api.md) — C++, Python, and Excel entry points
|
|
1057
|
+
- [pybind11 Documentation](https://pybind11.readthedocs.io/) — pybind11 binding syntax
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
dal/__init__.py,sha256=iDfJa7nvriQpy_zYr2zcddleGkrIB9mQL9gHdF4-U68,342
|
|
2
|
+
dal/_dal.cp314-win_amd64.pyd,sha256=IUdSsoJdXrd5-KG-woTHiBfI6ubTfTdrsz1hAUww91E,8000000
|
|
3
|
+
dal/api.py,sha256=ruk1aFwsBvUja93HOLUCDtr7C4RZ1GvvWkZVqlDTSU0,5872
|
|
4
|
+
dal/dal.py,sha256=b0-41lZPe9kLS9tlVBJJkYQEzp83e5PXxnrrJ8_23hc,89
|
|
5
|
+
dal_python-2026.9.25.dist-info/METADATA,sha256=CHc3vqlb5OLettIWYAKIduRONshmAXo7hv-EQU0jofo,51900
|
|
6
|
+
dal_python-2026.9.25.dist-info/WHEEL,sha256=itfEIwM024rWDKIUM5IIFma0LGtWvvW7z4i7fizC-AU,105
|
|
7
|
+
dal_python-2026.9.25.dist-info/RECORD,,
|