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 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,2 @@
1
+ # Auto-generated pybind11 shim -- re-exports all symbols from _dal
2
+ from ._dal import *
@@ -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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 1.0.3
3
+ Root-Is-Purelib: false
4
+ Tag: cp314-cp314-win_amd64
5
+