Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ the underlying QuantLib-SWIG changelog.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Colab notebooks in `notebooks/`: option Greeks and curve risk in one adjoint sweep

## [1.44.0] - 2026-11-12

First release as QuantLibAAD.
Expand Down Expand Up @@ -59,6 +65,7 @@ First release as QuantLibAAD.
Initial release, matching QuantLib Python bindings version 1.33


[Unreleased]: https://github.com/auto-differentiation/QuantLibAAD-Py/compare/v1.44.0...HEAD
[1.44.0]: https://github.com/auto-differentiation/QuantLibAAD-Py/compare/v1.33.3...v1.44.0

[1.33.3]: https://github.com/auto-differentiation/QuantLib-Risks-Py/compare/v1.33.2...v1.33.3
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ is a deprecated alias that installs `QuantLibAAD` and keeps `import QuantLib_Ris
with a `DeprecationWarning`. Please switch to `pip install QuantLibAAD` and
`import QuantLibAAD`. Anyone pinned to `QuantLib-Risks==1.33.3` must also pin `xad<2`.

## Notebooks

Run these notebooks on Google Colab, with nothing to install:

| Notebook | What it shows |
|---|---|
| [Quickstart](notebooks/01_quickstart.ipynb): a European option's Greeks in one adjoint sweep, checked against QuantLib's analytic Greeks and bumping | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/auto-differentiation/QuantLibAAD-Py/blob/main/notebooks/01_quickstart.ipynb) |
| [Curve risk](notebooks/02_curve_risk.ipynb): a swap book's sensitivities to every quote of a bootstrapped curve in one sweep | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/auto-differentiation/QuantLibAAD-Py/blob/main/notebooks/02_curve_risk.ipynb) |

## Getting Help

For documentation and other resources, see https://auto-differentiation.github.io/quantlib-aad/python/ .
Expand Down
241 changes: 241 additions & 0 deletions notebooks/01_quickstart.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# QuantLibAAD quickstart: option Greeks in one adjoint sweep\n",
"\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/auto-differentiation/QuantLibAAD-Py/blob/main/notebooks/01_quickstart.ipynb)\n",
"\n",
"[QuantLibAAD](https://github.com/auto-differentiation/QuantLibAAD-Py) is QuantLib for Python built with\n",
"[XAD](https://github.com/auto-differentiation/xad), a fast automatic differentiation (AD) library. You write\n",
"ordinary QuantLib code; when its inputs are XAD numbers, XAD records the calculation and can give the derivatives\n",
"of the result with respect to every input.\n",
"\n",
"This notebook prices a European call with QuantLib and gets all its first-order Greeks:\n",
"\n",
"1. **Adjoint mode**: all sensitivities from one recording and one backward sweep.\n",
"2. **Check** against QuantLib's own analytic Greeks.\n",
"3. **Check** against bumping the inputs."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%pip install -q QuantLibAAD"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A QuantLib pricer\n",
"\n",
"A one-year call struck at 8 on a stock at 7, priced with the Black-Scholes analytic engine. Spot, dividend\n",
"yield, risk-free rate and volatility each enter through a `SimpleQuote`, the inputs we want sensitivities to.\n",
"The same function works with plain floats and with XAD numbers."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"price = 0.0303344207\n"
]
}
],
"source": [
"import QuantLibAAD as ql\n",
"from xad.adj_1st import Tape\n",
"\n",
"today = ql.Date(15, ql.May, 1998)\n",
"ql.Settings.instance().evaluationDate = today\n",
"day_counter = ql.Actual365Fixed()\n",
"\n",
"inputs = {\"spot\": 7.0, \"dividend\": 0.05, \"rate\": 0.05, \"vol\": 0.10}\n",
"\n",
"\n",
"def make_option(spot, dividend, rate, vol):\n",
" def flat_curve(r):\n",
" return ql.YieldTermStructureHandle(ql.FlatForward(today, ql.QuoteHandle(ql.SimpleQuote(r)), day_counter))\n",
"\n",
" process = ql.BlackScholesMertonProcess(\n",
" ql.QuoteHandle(ql.SimpleQuote(spot)),\n",
" flat_curve(dividend),\n",
" flat_curve(rate),\n",
" ql.BlackVolTermStructureHandle(\n",
" ql.BlackConstantVol(today, ql.TARGET(), ql.QuoteHandle(ql.SimpleQuote(vol)), day_counter)),\n",
" )\n",
" option = ql.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Call, 8.0),\n",
" ql.EuropeanExercise(ql.Date(17, ql.May, 1999)))\n",
" option.setPricingEngine(ql.AnalyticEuropeanEngine(process))\n",
" return option\n",
"\n",
"\n",
"def price_option(spot, dividend, rate, vol):\n",
" return make_option(spot, dividend, rate, vol).NPV()\n",
"\n",
"\n",
"print(f\"price = {price_option(**inputs):.10f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. All Greeks in one adjoint sweep\n",
"\n",
"Register the inputs on a tape, run the unchanged pricer, then seed the price and sweep the tape backwards once.\n",
"Every input's derivative comes out of that one sweep, however many inputs there are."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"price = 0.0303344207\n",
"d price / d spot = +0.0950998668\n",
"d price / d dividend = -0.6693467337\n",
"d price / d rate = +0.6388460970\n",
"d price / d vol = +1.1714772740\n"
]
}
],
"source": [
"def price_and_greeks(inputs):\n",
" args = {name: ql.Real(value) for name, value in inputs.items()}\n",
" with Tape() as tape:\n",
" tape.registerInputs(list(args.values()))\n",
" tape.newRecording()\n",
" npv = price_option(**args)\n",
" tape.registerOutput(npv)\n",
" npv.derivative = 1.0\n",
" tape.computeAdjoints()\n",
" return npv.value, {name: arg.derivative for name, arg in args.items()}\n",
"\n",
"\n",
"price, greeks = price_and_greeks(inputs)\n",
"\n",
"print(f\"price = {price:.10f}\")\n",
"for name, greek in greeks.items():\n",
" print(f\"d price / d {name:8} = {greek:+.10f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Check against QuantLib's analytic Greeks\n",
"\n",
"The analytic engine also returns closed-form Greeks: delta, dividend rho, rho and vega."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"d price / d XAD adjoint analytic abs diff\n",
"spot 0.0950998668 0.0950998668 6.4e-16\n",
"dividend -0.6693467337 -0.6693467337 5.3e-15\n",
"rate 0.6388460970 0.6388460970 5.3e-15\n",
"vol 1.1714772740 1.1714772740 4.7e-15\n"
]
}
],
"source": [
"option = make_option(**inputs)\n",
"analytic = {\"spot\": option.delta(), \"dividend\": option.dividendRho(), \"rate\": option.rho(), \"vol\": option.vega()}\n",
"\n",
"print(f\"{'d price / d':12} {'XAD adjoint':>14} {'analytic':>14} {'abs diff':>10}\")\n",
"for name in inputs:\n",
" print(f\"{name:12} {greeks[name]:14.10f} {analytic[name]:14.10f} {abs(greeks[name] - analytic[name]):10.1e}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Check against bumping\n",
"\n",
"Central differences reprice twice per input and are accurate only to the bump size squared."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"d price / d XAD adjoint bumping abs diff\n",
"spot 0.0950998668 0.0950998671 2.7e-10\n",
"dividend -0.6693467337 -0.6693467338 9.0e-11\n",
"rate 0.6388460970 0.6388460971 8.3e-11\n",
"vol 1.1714772740 1.1714772741 4.5e-11\n"
]
}
],
"source": [
"h = 1e-6\n",
"bumped = {}\n",
"for name in inputs:\n",
" up, down = dict(inputs), dict(inputs)\n",
" up[name] += h\n",
" down[name] -= h\n",
" bumped[name] = (price_option(**up) - price_option(**down)) / (2 * h)\n",
"\n",
"print(f\"{'d price / d':12} {'XAD adjoint':>14} {'bumping':>14} {'abs diff':>10}\")\n",
"for name in inputs:\n",
" print(f\"{name:12} {greeks[name]:14.10f} {bumped[name]:14.10f} {abs(greeks[name] - bumped[name]):10.1e}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The adjoint Greeks match QuantLib's closed forms to machine precision, and bumping to its truncation and\n",
"rounding error. Bumping needs two extra pricings per input; the adjoint sweep gives them all at once, which\n",
"matters when a price depends on whole curves and surfaces.\n",
"\n",
"## Next steps\n",
"\n",
"- [Curve risk](https://colab.research.google.com/github/auto-differentiation/QuantLibAAD-Py/blob/main/notebooks/02_curve_risk.ipynb): a swap book's sensitivities to every quote of a bootstrapped curve in one sweep.\n",
"- [Python examples](https://github.com/auto-differentiation/QuantLibAAD-Py/tree/main/Python/examples) and the [QuantLibAAD documentation](https://auto-differentiation.github.io/quantlib-aad/python/).\n",
"- [XAD for Python](https://github.com/auto-differentiation/xad-py): automatic differentiation of your own Python code."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading
Loading