From cef04f54bcb9faf9d400d8c3e52351d613e9500e Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:29:54 +0000 Subject: [PATCH 1/6] notebooks: quickstart, option Greeks in one adjoint sweep --- notebooks/01_quickstart.ipynb | 241 ++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 notebooks/01_quickstart.ipynb diff --git a/notebooks/01_quickstart.ipynb b/notebooks/01_quickstart.ipynb new file mode 100644 index 00000000..cfaac767 --- /dev/null +++ b/notebooks/01_quickstart.ipynb @@ -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 +} From f73d784d091d30eeaa31fd92da1c93c4d8fc0407 Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:32:24 +0000 Subject: [PATCH 2/6] notebooks: curve risk, a swap book's sensitivities to every quote --- notebooks/02_curve_risk.ipynb | 302 ++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 notebooks/02_curve_risk.ipynb diff --git a/notebooks/02_curve_risk.ipynb b/notebooks/02_curve_risk.ipynb new file mode 100644 index 00000000..1f74b091 --- /dev/null +++ b/notebooks/02_curve_risk.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Curve risk: a swap book's sensitivities to every quote\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/02_curve_risk.ipynb)\n", + "\n", + "A yield curve is bootstrapped from market quotes, and a book of swaps is priced off it. Its interest-rate risk is\n", + "the sensitivity of the book's value to each quote. Bumping needs one bootstrap and book revaluation per quote.\n", + "With QuantLibAAD, the bootstrap and the pricing are recorded once and **one adjoint sweep gives the sensitivity\n", + "to every quote**.\n", + "\n", + "The market data and the book are those of the C++\n", + "[AdjointTradeRisk example](https://github.com/auto-differentiation/QuantLibAAD/tree/main/Examples/AdjointTradeRisk):\n", + "45 quotes and 200 swaps." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -q QuantLibAAD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Market quotes\n", + "\n", + "Ten deposits (overnight to 6 months), five FRAs (1x7 to 5x11) and thirty swaps (1 to 30 years) against\n", + "6-month Euribor." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import QuantLibAAD as ql\n", + "from xad.adj_1st import Tape\n", + "\n", + "today = ql.Date(2, ql.January, 2015)\n", + "ql.Settings.instance().evaluationDate = today\n", + "calendar = ql.TARGET()\n", + "start = calendar.advance(today, 2, ql.Days)\n", + "\n", + "deposit_tenors = [ql.Period(1, ql.Days)] * 3 + [ql.Period(1, ql.Weeks)] + [ql.Period(m, ql.Months) for m in range(1, 7)]\n", + "names = ([\"deposit ON\", \"deposit TN\", \"deposit SN\", \"deposit 1W\"] + [f\"deposit {m}M\" for m in range(1, 7)]\n", + " + [f\"FRA {m}x{m + 6}\" for m in range(1, 6)] + [f\"swap {y}Y\" for y in range(1, 31)])\n", + "quotes = ([0.0010 + i * 0.0002 for i in range(10)] + [0.0030 + i * 0.0005 for i in range(5)]\n", + " + [0.0060 + i * 0.0001 for i in range(30)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The bootstrap\n", + "\n", + "A curve linear in zero rates through one node per quote. AAD differentiates the bootstrap's solver iterations,\n", + "so its sensitivities are only as accurate as the solver: we tighten the solver accuracy from its default." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def bootstrap(quotes):\n", + " quote = [ql.QuoteHandle(ql.SimpleQuote(q)) for q in quotes]\n", + " helpers = [\n", + " ql.DepositRateHelper(quote[i], tenor, min(i, 2), calendar, ql.ModifiedFollowing, False, ql.Actual360())\n", + " for i, tenor in enumerate(deposit_tenors)\n", + " ]\n", + " helpers += [\n", + " ql.FraRateHelper(quote[10 + i], i + 1, i + 7, 2, calendar, ql.ModifiedFollowing, False, ql.Actual360())\n", + " for i in range(5)\n", + " ]\n", + " helpers += [\n", + " ql.SwapRateHelper(quote[15 + i], ql.Period(i + 1, ql.Years), calendar, ql.Annual, ql.ModifiedFollowing,\n", + " ql.Thirty360(ql.Thirty360.European), ql.Euribor6M())\n", + " for i in range(30)\n", + " ]\n", + " curve = ql.PiecewiseLinearZero(today, helpers, ql.Actual365Fixed(), ql.IterativeBootstrap(accuracy=1e-15))\n", + " curve.enableExtrapolation()\n", + " return ql.YieldTermStructureHandle(curve)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The swap book\n", + "\n", + "200 payer and receiver swaps with random nominals, fixed rates and maturities up to 30 years, from a seeded\n", + "random number generator. The book's value is the sum of their NPVs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "book value = -531,879.60\n" + ] + } + ], + "source": [ + "rng = ql.MersenneTwisterUniformRsg(1, 42)\n", + "\n", + "\n", + "def uniform():\n", + " return rng.nextSequence().value()[0].value\n", + "\n", + "\n", + "book = []\n", + "for _ in range(200):\n", + " swap_type = ql.Swap.Payer if uniform() < 0.5 else ql.Swap.Receiver\n", + " nominal = 1e6 * (1 + int(10 * uniform()))\n", + " fixed_rate = 0.002 + 0.008 * uniform()\n", + " years = 1 + int(29.999 * uniform())\n", + " book.append((swap_type, nominal, fixed_rate, years))\n", + "\n", + "\n", + "def price_swap(trade, curve):\n", + " swap_type, nominal, fixed_rate, years = trade\n", + " end = calendar.advance(start, years, ql.Years)\n", + "\n", + " def schedule(tenor):\n", + " return ql.Schedule(start, end, tenor, calendar, ql.ModifiedFollowing, ql.ModifiedFollowing,\n", + " ql.DateGeneration.Backward, False)\n", + "\n", + " swap = ql.VanillaSwap(swap_type, nominal, schedule(ql.Period(1, ql.Years)), fixed_rate,\n", + " ql.Thirty360(ql.Thirty360.European), schedule(ql.Period(6, ql.Months)),\n", + " ql.Euribor6M(curve), 0.0, ql.Actual360())\n", + " swap.setPricingEngine(ql.DiscountingSwapEngine(curve))\n", + " return swap.NPV()\n", + "\n", + "\n", + "def price_book(quotes):\n", + " curve = bootstrap(quotes)\n", + " return sum(price_swap(trade, curve) for trade in book)\n", + "\n", + "\n", + "print(f\"book value = {price_book(quotes):,.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Book risk in one adjoint sweep\n", + "\n", + "Register the 45 quotes as inputs, record the bootstrap and the pricing of all 200 swaps, and sweep back once from\n", + "the book value." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "book value = -531,879.60\n", + "\n", + "largest sensitivities, per 1bp move of the quote:\n", + " swap 14Y -45,152.25\n", + " swap 21Y -38,636.67\n", + " swap 28Y -29,911.59\n", + " swap 22Y -28,274.69\n", + " swap 25Y 27,279.16\n", + " swap 30Y 26,541.94\n", + " swap 17Y 25,610.10\n", + " swap 7Y 19,043.85\n", + " swap 29Y -15,346.74\n", + " swap 23Y -12,606.83\n" + ] + } + ], + "source": [ + "def book_value_and_risk(quotes):\n", + " inputs = [ql.Real(q) for q in quotes]\n", + " with Tape() as tape:\n", + " tape.registerInputs(inputs)\n", + " tape.newRecording()\n", + " value = price_book(inputs)\n", + " tape.registerOutput(value)\n", + " value.derivative = 1.0\n", + " tape.computeAdjoints()\n", + " return value.value, [q.derivative for q in inputs]\n", + "\n", + "\n", + "value, risk = book_value_and_risk(quotes)\n", + "\n", + "print(f\"book value = {value:,.2f}\\n\")\n", + "print(\"largest sensitivities, per 1bp move of the quote:\")\n", + "for i in sorted(range(len(risk)), key=lambda i: -abs(risk[i]))[:10]:\n", + " print(f\" {names[i]:12} {risk[i] * 1e-4:14,.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Check against bumping\n", + "\n", + "Central differences for the five largest sensitivities: two bootstraps and book revaluations per quote." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "quote XAD adjoint bumping rel diff\n", + "swap 14Y -451,522,471.99 -451,522,472.01 5.1e-11\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "swap 21Y -386,366,675.21 -386,366,675.25 9.6e-11\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "swap 28Y -299,115,874.32 -299,115,874.10 7.4e-10\n", + "swap 22Y -282,746,872.95 -282,746,872.90 1.8e-10\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "swap 25Y 272,791,566.37 272,791,565.78 2.1e-09\n" + ] + } + ], + "source": [ + "h = 1e-5\n", + "print(f\"{'quote':12} {'XAD adjoint':>16} {'bumping':>16} {'rel diff':>10}\")\n", + "for i in sorted(range(len(risk)), key=lambda i: -abs(risk[i]))[:5]:\n", + " up, down = list(quotes), list(quotes)\n", + " up[i] += h\n", + " down[i] -= h\n", + " bumped = (price_book(up) - price_book(down)) / (2 * h)\n", + " print(f\"{names[i]:12} {risk[i]:16,.2f} {bumped:16,.2f} {abs(risk[i] - bumped) / abs(bumped):10.1e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The adjoint sensitivities agree with bumping to its truncation and rounding error. Bumping all 45 quotes would\n", + "take 90 bootstraps and book revaluations; the adjoint sweep gives every sensitivity from one recording.\n", + "\n", + "One sweep gives the derivatives of one output. For the risk of each swap separately, register every NPV as an\n", + "output and sweep once per swap on the same recording; the C++ example shows a faster way that records the\n", + "curve once and each swap separately.\n", + "\n", + "## Next steps\n", + "\n", + "- [Quickstart](https://colab.research.google.com/github/auto-differentiation/QuantLibAAD-Py/blob/main/notebooks/01_quickstart.ipynb): option Greeks in one adjoint 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/)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From dd5d860da088bfd64613cce3d32354f0d1c5af5e Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:32:38 +0000 Subject: [PATCH 3/6] README: notebooks with Colab badges --- CHANGELOG.md | 1 + README.md | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9878c1cd..66a5f5f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ First release as QuantLibAAD. Clang), so AD values match a plain-double build with the same setting - One-step build with scikit-build-core; wheels for Python 3.10-3.14 on Linux, macOS and Windows - `tools/sync-swig.sh` syncs `SWIG/` with a QuantLib-SWIG release, re-applying the XAD changes in `SWIG/patches/` +- Colab notebooks in `notebooks/`: option Greeks and curve risk in one adjoint sweep ## [1.33.3] - 2024-04-04 diff --git a/README.md b/README.md index 38096a65..cc51aacc 100644 --- a/README.md +++ b/README.md @@ -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 | | +|---|---| +| [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/ . From 83dcbeb0c361ddb57440ecd61814c5fcbef2497c Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Thu, 12 Nov 2026 18:16:42 +0000 Subject: [PATCH 4/6] notebooks: outputs on QuantLib 1.44 --- notebooks/02_curve_risk.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/02_curve_risk.ipynb b/notebooks/02_curve_risk.ipynb index 1f74b091..eea1578c 100644 --- a/notebooks/02_curve_risk.ipynb +++ b/notebooks/02_curve_risk.ipynb @@ -232,14 +232,14 @@ "output_type": "stream", "text": [ "quote XAD adjoint bumping rel diff\n", - "swap 14Y -451,522,471.99 -451,522,472.01 5.1e-11\n" + "swap 14Y -451,522,471.99 -451,522,472.01 4.9e-11\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "swap 21Y -386,366,675.21 -386,366,675.25 9.6e-11\n" + "swap 21Y -386,366,675.21 -386,366,675.25 9.7e-11\n" ] }, { From 4e2ab4942cce14e3fce11f2920426cf9dec9c83a Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Fri, 13 Nov 2026 07:52:47 +0000 Subject: [PATCH 5/6] CHANGELOG: notebooks under Unreleased --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a5f5f3..c94fd5c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. @@ -42,7 +48,6 @@ First release as QuantLibAAD. Clang), so AD values match a plain-double build with the same setting - One-step build with scikit-build-core; wheels for Python 3.10-3.14 on Linux, macOS and Windows - `tools/sync-swig.sh` syncs `SWIG/` with a QuantLib-SWIG release, re-applying the XAD changes in `SWIG/patches/` -- Colab notebooks in `notebooks/`: option Greeks and curve risk in one adjoint sweep ## [1.33.3] - 2024-04-04 @@ -60,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 From 2841e5a33a88fdcd520962de7e91e64c31255ce0 Mon Sep 17 00:00:00 2001 From: Auto Differentiation Dev Team <107129969+auto-differentiation-dev@users.noreply.github.com> Date: Fri, 13 Nov 2026 08:01:55 +0000 Subject: [PATCH 6/6] notebooks: curve risk counts two bootstraps per quote; README table heading --- README.md | 2 +- notebooks/02_curve_risk.ipynb | 26 ++++---------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index cc51aacc..f3ca1370 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ with a `DeprecationWarning`. Please switch to `pip install QuantLibAAD` and Run these notebooks on Google Colab, with nothing to install: -| Notebook | | +| 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) | diff --git a/notebooks/02_curve_risk.ipynb b/notebooks/02_curve_risk.ipynb index eea1578c..031522d4 100644 --- a/notebooks/02_curve_risk.ipynb +++ b/notebooks/02_curve_risk.ipynb @@ -9,7 +9,7 @@ "[![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)\n", "\n", "A yield curve is bootstrapped from market quotes, and a book of swaps is priced off it. Its interest-rate risk is\n", - "the sensitivity of the book's value to each quote. Bumping needs one bootstrap and book revaluation per quote.\n", + "the sensitivity of the book's value to each quote. Bumping needs two bootstraps and book revaluations per quote (central differences).\n", "With QuantLibAAD, the bootstrap and the pricing are recorded once and **one adjoint sweep gives the sensitivity\n", "to every quote**.\n", "\n", @@ -232,28 +232,10 @@ "output_type": "stream", "text": [ "quote XAD adjoint bumping rel diff\n", - "swap 14Y -451,522,471.99 -451,522,472.01 4.9e-11\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "swap 21Y -386,366,675.21 -386,366,675.25 9.7e-11\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "swap 14Y -451,522,471.99 -451,522,472.01 4.9e-11\n", + "swap 21Y -386,366,675.21 -386,366,675.25 9.7e-11\n", "swap 28Y -299,115,874.32 -299,115,874.10 7.4e-10\n", - "swap 22Y -282,746,872.95 -282,746,872.90 1.8e-10\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "swap 22Y -282,746,872.95 -282,746,872.90 1.8e-10\n", "swap 25Y 272,791,566.37 272,791,565.78 2.1e-09\n" ] }