<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Implementing QuantLib]]></title><description><![CDATA[Tips, tricks and thoughts about QuantLib from Luigi Ballabio, its co-founder and current maintainer.]]></description><link>https://implementingquantlib.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png</url><title>Implementing QuantLib</title><link>https://implementingquantlib.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 06 Jun 2026 23:21:51 GMT</lastBuildDate><atom:link href="https://implementingquantlib.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Luigi Ballabio]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[implementingquantlib@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[implementingquantlib@substack.com]]></itunes:email><itunes:name><![CDATA[Luigi Ballabio]]></itunes:name></itunes:owner><itunes:author><![CDATA[Luigi Ballabio]]></itunes:author><googleplay:owner><![CDATA[implementingquantlib@substack.com]]></googleplay:owner><googleplay:email><![CDATA[implementingquantlib@substack.com]]></googleplay:email><googleplay:author><![CDATA[Luigi Ballabio]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The software side of replication]]></title><description><![CDATA[Today&#8217;s post was originally published in the November 2025 issue of Wilmott Magazine.  What if you could make it a lot easier for readers to replicate your paper?]]></description><link>https://implementingquantlib.substack.com/p/the-software-side-of-replication</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/the-software-side-of-replication</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 04 Jun 2026 05:30:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again!</p><p style="text-align: justify;">Today&#8217;s post was originally published in the November 2025 issue of <em>Wilmott Magazine</em>. What if you could make it a lot easier for readers to replicate your paper? That was the idea I followed when <em>Wilmott</em> called for articles to be published in a special issue on the replication crisis.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>The software side of replication</h2><p style="text-align: justify;">What if you could make it a lot easier for readers to replicate your paper?</p><p style="text-align: justify;">Some years ago, I replicated the results of a paper on bootstrapping interest-rate curves and published my code; first as a book chapter, later as a blog post, and recently&#8212;for this very article, actually&#8212;as live code that you can execute in your browser.</p><p style="text-align: justify;">You can do the same, if you want. In this article, I&#8217;ll describe some issues you might encounter and the tools you can throw at them until your paper is live on the Internet.</p><h2>The first step</h2><p style="text-align: justify;">But first, a problem that&#8217;s not technical. Can you provide the data and the code for replicating your results? And if so, are you willing to do it?</p><p style="text-align: justify;">The first is a legal problem, and of course it is what it is; you might not have the license to redistribute the data, or the permission to publish the code. I understand, I have a day job too. If that&#8217;s the case, the rest of this article won&#8217;t help you much (unless it&#8217;s only the data that you can&#8217;t share; in that case, you might share the code and tell people to input their own data into it).</p><p style="text-align: justify;">However, I&#8217;ve seen cases in which the data and the code might have been shared, and in the end they weren&#8217;t. There were two different reasons for this.</p><p style="text-align: justify;">Sometimes, I&#8217;ve seen people reason that if they published their code, someone else might take it and use it to beat them to the next paper. Of course, this can be a legitimate concern in a publish-or-perish environment. In this time and age, though, someone might feed your paper into an A.I. prompt and ask it to turn it into code anyway. Which, now that I think of it, raises an interesting question: can A.I. help with replication? It might be the subject of another article; feel free to beat me to it.</p><p style="text-align: justify;">Some other times, people had every intention to publish their code, but they said: &#8220;it&#8217;s not clean code; let me polish it first, and then I&#8217;ll publish it&#8221;. Friend, let me tell you: you won&#8217;t. Other things will get in the way. Publish it, and let other people make it better. It has served me well for these 25 years of open-source work on <a href="https://www.quantlib.org/">QuantLib</a>.</p><p style="text-align: justify;">Once last consideration: if you publish your code, take a bit of time to choose a license. It might be an open-source license or not, that&#8217;s up to you; but people need to know what they can do with the code.</p><h2>The paper</h2><p style="text-align: justify;">The paper I replicated is Ametrano and Bianchetti, <em>Everything You Always Wanted to Know About Multiple Interest Rate Curve Bootstrapping but Were Afraid to Ask</em>, published in 2013 and available from <a href="https://ssrn.com/abstract=2219548">SSRN</a>. It described how to bootstrap interest-rate curves in a multi-curve setting (not yet common knowledge at the time) using as a case study the EONIA and EURIBOR curves for different tenors. The authors used QuantLib for their calculations; to be precise, QuantLibXL, an Excel plugin wrapping it.</p><p style="text-align: justify;">This made it a good showcase for the capabilities of the library, which is why I set up to replicate their calculations using the QuantLib wrappers for Python. I published the resulting code in 2016 in <em><a href="https://leanpub.com/quantlibpythoncookbook">QuantLib Python Cookbook</a></em>, which I co-authored, and in 2023 on my blog, <em><a href="https://www.implementingquantlib.com/">Implementing QuantLib</a></em>, for the ten years of the paper.</p><p style="text-align: justify;">The paper provided the full set of market data used in the calculations, as well as some of the intermediate results.</p><h2>Making code and data available</h2><p style="text-align: justify;">And now, we can start talking about actually publishing your code.</p><p style="text-align: justify;">This will come as no surprise for most of you; nowadays, the preferred place to host code is <a href="https://github.com/">GitHub</a>, hands down. (Yes, I know: who would have said 20 years ago that the place where most open-source code lives would be operated by Microsoft?)</p><p style="text-align: justify;">I&#8217;m assuming, of course, that you&#8217;re using version control for your code: GitHub hosts Git repositories, not just any bunch of files (the same goes for other alternatives like GitLab or Bitbucket). If you&#8217;re not, please, <em>please</em> do. You&#8217;re bound to get hurt otherwise.</p><p style="text-align: justify;">And since I&#8217;m putting my code where my mouth is, GitHub is also where the code for my replication of the paper is hosted. The address is <a href="https://github.com/lballabio/ab-notebook">https://github.com/lballabio/ab-notebook</a> (where <code>ab</code> stands for Ametrano and Bianchetti, of course, but I also couldn&#8217;t resist a tip of the hat to the Abby Normal bit in <em>Young Frankenstein</em>). I&#8217;ll refer to a few things in there, so please go <a href="https://github.com/lballabio/ab-notebook">open it in a browser</a> now; I&#8217;ll wait.</p><p style="text-align: justify;">Done? Good.</p><h2>Languages and tooling</h2><p style="text-align: justify;">In my case, the choice of Python was straightforward, since it&#8217;s the language I&#8217;m more comfortable with among the ones in which QuantLib is available (the others being C# and Java, besides the native C++). You probably have your own preferred language, and that&#8217;s perfectly fine.</p><p style="text-align: justify;">In any case, and depending on the ecosystem offered by the language, I would suggest using some tooling that makes your code available in a notebook-like environment. For an example, you can go back to the browser tab where you opened my repository and click on the <code>index.ipynb</code> file; GitHub will open it in a static viewer.</p><p style="text-align: justify;">As you can see, it&#8217;s a notebook that mixes documentation, code, and the output of running the code; it also has specific formatting facilities for when the output is a data table or a plot. In this case, the tool I used to produce it was <a href="https://jupyter.org/">Jupyter</a>, a notebook-like environment that can host Julia, Python or R code natively (hence the name). Other extensions give it the possibility of adding SQL queries into the mix, or of using other languages such as C++, C#, Java and possibly others that I&#8217;m not aware of.</p><p style="text-align: justify;">Of course, Jupyter is not the only tool that does that. I think Mathematica was the first such environment (if we don&#8217;t count Donald Knuth&#8217;s work on literate programming), MATLAB has a Live Editor that provides a similar experience, and lately a bunch of tools have been created or extended to replicate the Jupyter interface, for instance <a href="https://colab.research.google.com/">Google Colab</a> or even Visual Studio Code. Another strong contender is <a href="https://observablehq.com/">Observable</a>, which uses JavaScript as its language, leverages excellent visualization libraries such as D3, and takes a different approach in which code cells are not executed sequentially but keeping track of their dependencies.</p><p style="text-align: justify;">Taken to the extreme, using notebooks might allow you to write your paper and embed your code into it. If you&#8217;re interested, you can look into other tools such as <a href="https://quarto.org/">Quarto</a>, a publishing system that takes Jupyter notebooks and turns them into books, both PDF and online. It&#8217;s what I&#8217;m using to write my latest book, <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a>;</em> you&#8217;re welcome to visit it and see what the result can look like.</p><h2>Replicating the environment</h2><p style="text-align: justify;">Once we start talking about tooling, we&#8217;re faced with a problem. Our code is going to depend on tools and external modules whose development we don&#8217;t control; for instance, if you look near the top of my notebook, you can see that I&#8217;m importing QuantLib, NumPy, pandas and Matplotlib, plus the datetime module from the Python standard library. And of course, I need Jupyter itself.</p><p style="text-align: justify;">To recreate the environment for running my code, you&#8217;ll need to install all of those. The problem is that, if you come back in five or ten years, the interface or the behavior of those modules might have changed and my notebook might not run anymore (or might run and give different results). We need to use the same versions of the modules.</p><p style="text-align: justify;">Moreover, those modules depend in turn on other modules, directly or indirectly; installing the five modules above (not counting datetime, that comes with Python) results in the installation of a whopping ninety-six additional dependencies, all of which release new versions regularly. How do we ensure that you and I run the code in the same environment?</p><p style="text-align: justify;">In the past few years, a number of tools have been built around the same idea. Well, two ideas: as I wrote this, I realized that I was taking the first one for granted.</p><p style="text-align: justify;">The first idea is that you need to have a separate environment for each project. If you need NumPy for a project, you don&#8217;t just run <code>pip install</code> and add it to your system installation; if you do that, eventually you&#8217;ll run into a wall when you try to install some module and find out that it requires <code>fonttools</code> version 4 or later, but you already have another module installed that requires <code>fonttools</code> to be at most version 3.8. Nowadays, tooling for virtual environments is available for most languages if not for all; for instance, Python distributes the <code>virtualenv</code> module in its standard library, but you&#8217;ll probably find something similar for your language, or at least a way to specify the directory where to search for modules.</p><p style="text-align: justify;">Once we have the basics, the second idea comes into play: instead of installing modules manually, do it through some tool that records the exact versions of all the dependencies that get installed. Again, it&#8217;s an idea that&#8217;s been implemented for most languages; for example, Paket for C#, Bundler for Ruby or Cargo for Rust are three such tools.</p><p style="text-align: justify;">The Python community has not standardized around a single tool; there are a few to choose from, such as <a href="https://pipenv.pypa.io/">Pipenv</a>, <a href="https://python-poetry.org/">Poetry</a>, <a href="https://docs.astral.sh/uv/">uv</a> and probably others I&#8217;m not aware of. In this case, I chose Pipenv because it plays well with another tool I&#8217;ll mention later.</p><p style="text-align: justify;">All these tools, for Python or otherwise, work by producing two files: a simpler file that lists the modules you specified directly, and a larger and more detailed file, usually called a lock file, in which all the dependencies and their versions are listed. If you look at my repository, you can see (and examine, if you want) the specification file <code>Pipfile</code> and the lock file <code>Pipfile.lock</code>. They are both supposed to be checked into version control, thus enabling reproducible builds; if you clone my repository on your machine and run <code>pipenv install</code>, the tool will notice the presence of the lock file, read it, check that you have the correct version of Python, and install the pinned versions of the dependencies.</p><p style="text-align: justify;">If you&#8217;re looking at making your build reproducible for the long term, you might notice a possible problem with the above. What if I do all correctly, use a dependency tool to specify that my code needs <code>traitlets</code> 5.14.3, and ten years from now you try to recreate my environment only to find that that version, or even that module, is no longer available online? A possibility is to freeze the entire environment; for instance with <a href="https://www.docker.com/">Docker</a>, a tool to create lightweight virtual machines. The resulting environment image can be published so that others can run calculations inside it.</p><h2>Pinning your code</h2><p style="text-align: justify;">After the environment is locked, the other variable that remains is your own code. It will change, too, as you work on your next paper and add features: you might decide that some function needs a new parameter, or that a calculation can be done in a different way that accommodates old and new features. And as you do that, you might not have the time to go back and update your old notebooks, or you might find that your time is better spent elsewhere.</p><p style="text-align: justify;">That&#8217;s ok. We have version control.</p><p style="text-align: justify;">When you finalize your paper, tag the version of the code you used. The tag doesn&#8217;t need to be in a particular format, so it can be a mnemonic for the paper; if you plan to make further changes to the code, though, it might be a good idea to use some kind of version number such as 1.0.</p><p style="text-align: justify;">Once the tag is created and pushed to your online repository, it makes it possible for people to find that specific version of the code; just mention the tag in your paper. If you want, you can stop there and start working on further changes to your code.</p><p style="text-align: justify;">However, GitHub added some features around it that you might want to use: for instance, you can turn your tag into a release. This creates a specific page for the tag and gives you the possibility to add release notes that will be displayed there. If you look at my repository on GitHub, you&#8217;ll see a &#8220;Releases&#8221; heading on the right side giving easy access to the latest released version of the code; you can click it to see what a release looks like.</p><p style="text-align: justify;">GitHub releases can also be integrated with other sites, such as CERN&#8217;s <a href="https://zenodo.org/">Zenodo</a>. If you get a free account there and set up the integration with your GitHub repository, creating a release will automatically cause Zenodo to download and store it; as it does so, it will also generate a unique Digital Object Identifier (DOI) through which your code can be cited.</p><p style="text-align: justify;">And while we&#8217;re on the subject of citations: adding a <code>CITATION.cff</code> file to your repository with the relevant information will make it easier for people to cite your work correctly. If you do so, GitHub will add a link under the &#8220;About&#8221; heading in your repository page from which people can copy the APA text or BibTeX code for the citation, ready to paste in their bibliography. Again, you can see an example in my repository.</p><h2>An online playground</h2><p style="text-align: justify;">I&#8217;ll finish by mentioning a possible way to make it even easier for people to experiment with your code.</p><p style="text-align: justify;">All of the above (pinning the dependencies and the code) make it possible to reproduce your exact build on one&#8217;s local machine. However, there are tools that allow you to set up an online environment so that people can run your code directly in their browser.</p><p style="text-align: justify;">Some of the tools I mentioned above (Colab or Observable, to name two) run natively on the Internet. Jupyter doesn&#8217;t, <em>per se</em>; but it can be run remotely by using <a href="https://mybinder.org/">Binder</a>. Strictly speaking, Binder is a server that anyone can host; however, thanks to sponsorships from a few scientific institutes, it also provides <a href="https://mybinder.org/">mybinder.org</a> as a service.</p><p style="text-align: justify;">Simply put, you can tell Binder the address of a GitHub repository that contains Jupyter notebooks and it will spawn a Jupyter server where you can run them, no installation required on your part. If you want to try my notebook, I provided a shortcut: look at the README file displayed on my Github repository page and you&#8217;ll find there a &#8220;launch binder&#8221; badge that you can click to start the process.</p><p style="text-align: justify;">It will take a couple of minutes to create the environment and start it. While you wait, and especially if you plan to use Binder with your code, consider clicking the &#8220;Donate&#8221; button on the top right. Unlike GitHub, which has a free tier but also a subscription model for enterprises, the Jupyter project lives on sponsorships and contributions alone.</p><h2>Conclusion</h2><p style="text-align: justify;">I don&#8217;t expect this article to be exhaustive&#8212;which would be pointless, anyway, as the list of available tools is always growing. What I hope is that it gives you a vista on some of the possibilities that the current landscape offers, that it will make you play with a few of these tools, and maybe that it will make you consider using them next time you publish a paper. Have fun experimenting!</p>]]></content:encoded></item><item><title><![CDATA[QuantLib and A.I.]]></title><description><![CDATA[I don&#8217;t use A.I. for my posts; I prefer the human touch. But this doesn&#8217;t mean I&#8217;m opposed to using A.I. for coding. What does this mean for QuantLib?]]></description><link>https://implementingquantlib.substack.com/p/quantlib-and-ai</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/quantlib-and-ai</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 07 May 2026 05:31:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello there. I don&#8217;t use A.I. for my posts; I prefer the human touch (and if you suspected I did use it because of em-dashes, I can point you to <a href="https://web.archive.org/web/20150501075500/http://www.implementingquantlib.com/">a snapshot of my blog from 2015</a> where you can see that I averaged a couple of them per post). But this doesn&#8217;t mean I&#8217;m opposed to using A.I. for coding.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p style="text-align: justify;">(And before I forget, let me remind you that the Internet Archive provides a great service and <a href="https://archive.org/donate/">you should donate if you can</a>. I just did.)</p><p style="text-align: justify;">Now, as I was saying: using A.I. for coding. In the past few months I started receiving pull requests for QuantLib that were developed using it, and I&#8217;m not saying that to disparage them: the authors made no mystery about it, and the quality was good. I might have been lucky. Anyway, this got me thinking: is there any way (ranging from a simple <code>AGENTS.md</code> to a more elaborate harness) in which we can make it easier for people to use A.I. when contributing to QuantLib?</p><p style="text-align: justify;">I don&#8217;t have definitive answers myself: I&#8217;ve been using Claude for just a few months now on proprietary code at work, I like what I&#8217;m seeing so far, but I&#8217;m not an expert. Instead, I&#8217;m hoping to start a discussion about this: I opened <a href="https://github.com/lballabio/QuantLib/issues/2497">an issue on GitHub</a> to host it, and you&#8217;re welcome to contribute your experience, suggestions, caveats, or whatever you think useful.</p><p style="text-align: justify;">That&#8217;s all for today; I&#8217;ll get out of the way now and let you think about it. Thanks, and see you next time&#8212;or, hopefully, on GitHub.</p>]]></content:encoded></item><item><title><![CDATA[Spread calculations]]></title><description><![CDATA[Hello there.  Here is another addition to A QuantLib Guide: a notebook on various spread calculations.]]></description><link>https://implementingquantlib.substack.com/p/spread-calculations</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/spread-calculations</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 19 Mar 2026 06:31:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello there. Here is another addition to <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a>:</em> a notebook on various spread calculations.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>This is actually not the notebook I wanted to write this time, but the one I did want to write needs some enhancements in the library first. They will be included in version 1.42, out in April with lots of other goodies from contributors old and new. Stay tuned.</p><h2>Spread calculations</h2><pre><code><code>import QuantLib as ql

today = ql.Date(16, ql.March, 2026)
ql.Settings.instance().evaluationDate = today

bps = 1e-4</code></code></pre><p style="text-align: justify;">We have already seen an example of z-spread calculation in <a href="https://www.quantlibguide.com/A%20taste%20of%20QuantLib.html">the first notebook of the guide</a>. In this notebook, we&#8217;ll show how to calculate a few other spreads, as well as a couple of functions that can be used in the particular case of a fixed-rate bond.</p><p style="text-align: justify;">I&#8217;ll start by building a simple bond.</p><pre><code><code>schedule = ql.MakeSchedule(
    effectiveDate=ql.Date(26, ql.May, 2020),
    terminationDate=ql.Date(26, ql.May, 2040),
    frequency=ql.Annual,
    calendar=ql.UnitedStates(ql.UnitedStates.GovernmentBond),
    convention=ql.Following,
    backwards=True,
)

settlement_days = 3
face_amount = 1_000_000.0
coupons = [0.02]
day_counter = ql.Thirty360(ql.Thirty360.BondBasis)

bond = ql.FixedRateBond(
    settlement_days,
    face_amount,
    schedule,
    coupons,
    day_counter,
)</code></code></pre><h2>The i-spread</h2><p style="text-align: justify;">The i-spread of the bond is the difference between its yield and an interpolated swap rate corresponding to its maturity date. There is no built-in function in QuantLib that returns it, but it can be calculated as follows.</p><p style="text-align: justify;">Given a quoted price, the yield can be calculated without having to define a discount curve:</p><pre><code><code>price = ql.BondPrice(98.08, ql.BondPrice.Clean)

y = bond.bondYield(
    price,
    day_counter,
    ql.SimpleThenCompounded,
    ql.Annual,
)
print(y)</code></code></pre><pre><code><code>0.021578895425796538</code></code></pre><p style="text-align: justify;">The interpolated swap rate can also be calculated from simple market data. Here we have a series of quoted OIS rates:</p><pre><code><code>ois_data = dict(
    [
        ("1Y", 0.137),
        ("2Y", 0.409),
        ("3Y", 0.674),
        ("5Y", 1.004),
        ("8Y", 1.258),
        ("10Y", 1.359),
        ("12Y", 1.420),
        ("15Y", 1.509),
        ("20Y", 1.574),
        ("25Y", 1.586),
        ("30Y", 1.579),
        ("35Y", 1.559),
        ("40Y", 1.514),
        ("45Y", 1.446),
        ("50Y", 1.425),
    ]
)</code></code></pre><p style="text-align: justify;">Given the maturity of the bond (a bit more than 14 years) we&#8217;ll have to interpolate linearly between the 12-years and the 15-years rate&#8212;assuming, of course, that no other quoted rates are available in this range.</p><pre><code><code>t1 = day_counter.yearFraction(today, today + ql.Period("12Y"))
t2 = day_counter.yearFraction(today, today + ql.Period("15Y"))</code></code></pre><pre><code><code>r1 = ois_data["12Y"] / 100
r2 = ois_data["15Y"] / 100</code></code></pre><pre><code><code>f = ql.LinearInterpolation([t1, t2], [r1, r2])</code></code></pre><pre><code><code>T = day_counter.yearFraction(today, bond.maturityDate())
print(T)</code></code></pre><pre><code><code>14.202777777777778</code></code></pre><pre><code><code>R = f(T)
print(R)</code></code></pre><pre><code><code>0.01485349074074074</code></code></pre><p style="text-align: justify;">And here is our spread, shown in basis points for convenience:</p><pre><code><code>i_spread = y - R
i_spread / bps</code></code></pre><pre><code><code>67.25404685055797</code></code></pre><h2>The z-spread</h2><p style="text-align: justify;">The calculation of the z-spread requires a base interest-rate curve. In this case, we&#8217;ll use a risk-free curve derived from the quoted OIS shown in the previous section&#8230;</p><pre><code><code>index = ql.Sofr()

settlement_days = 2

ois_helpers = []
for tenor in ois_data:
    q = ql.SimpleQuote(ois_data[tenor] / 100)
    ois_helpers.append(
        ql.OISRateHelper(
            settlement_days,
            ql.Period(tenor),
            ql.QuoteHandle(q),
            index,
            paymentFrequency=ql.Annual,
        )
    )

sofr_curve = ql.PiecewiseLogCubicDiscount(
    0,
    ql.UnitedStates(ql.UnitedStates.GovernmentBond),
    ois_helpers,
    ql.Actual360(),
)

sofr_handle = ql.YieldTermStructureHandle(sofr_curve)</code></code></pre><p style="text-align: justify;">&#8230;but the base curve might be any other one; for instance, a government curve <a href="https://www.quantlibguide.com/Fitted%20bond%20curves.html">fitted on quoted bond prices</a>.</p><p style="text-align: justify;">In the case of fixed-rate bonds, the library provides a function to calculate the z-spread directly:</p><pre><code><code>z = ql.BondFunctions.zSpread(
    bond,
    price,
    sofr_curve,
    day_counter,
    ql.SimpleThenCompounded,
    ql.Annual,
)
z / bps</code></code></pre><pre><code><code>64.75673883414345</code></code></pre><p style="text-align: justify;">However, there&#8217;s a snag. The function above takes the day-count convention we want to use for the spread, but due to limitations of the implementation the argument is ignored: passing a different convention returns the same result.</p><pre><code><code>ql.BondFunctions.zSpread(
    bond,
    price,
    sofr_curve,
    ql.Actual360(),
    ql.SimpleThenCompounded,
    ql.Annual,
) / bps</code></code></pre><pre><code><code>64.75673883414345</code></code></pre><p style="text-align: justify;">In fact, we&#8217;re in the process of deprecating the signature above and replacing it with one that doesn&#8217;t include the day-count convention.</p><p style="text-align: justify;">The returned rate uses the same day-count convention of the base curve. If we want to express it according to a different convention, we can use the methods of the <code>InterestRate</code> class to convert it into an equivalent rate, given a start and end date; for instance, today and one year from now.</p><pre><code><code>ql.InterestRate(
    z,
    sofr_curve.dayCounter(),
    ql.SimpleThenCompounded,
    ql.Annual,
).equivalentRate(
    day_counter,
    ql.SimpleThenCompounded,
    ql.Annual,
    today,
    today + ql.Period("1Y"),
).rate() / bps</code></code></pre><pre><code><code>65.65908427374367</code></code></pre><p style="text-align: justify;">In the case of a less simple bond (for instance, a floater), the function above won&#8217;t work and we&#8217;ll have to resort to a more generic calculation; that is, create a discount curve by adding a spread over the base curve&#8230;</p><pre><code><code>spread = ql.SimpleQuote(0.0)
discount_curve = ql.ZeroSpreadedTermStructure(
    sofr_handle,
    ql.QuoteHandle(spread),
    ql.SimpleThenCompounded,
    ql.Annual,
)

bond.setPricingEngine(
    ql.DiscountingBondEngine(
        ql.YieldTermStructureHandle(discount_curve)
    )
)</code></code></pre><p style="text-align: justify;">&#8230;and using a root solver to find the spread for which the bond price (as calculated off this curve) equals the quoted price.</p><pre><code><code>def discrepancy(s):
    spread.setValue(s)
    return bond.cleanPrice() - price.amount()


solver = ql.Brent()
accuracy = 1e-5
guess = 0.015
step = 1e-4

solver.solve(discrepancy, accuracy, guess, step) / bps</code></code></pre><pre><code><code>64.75637679007006</code></code></pre><p style="text-align: justify;">In this case, the bond was a fixed-rate one so we can compare the result with the one returned by the <code>zSpread</code> function shown above. As you can see, this calculation returns the same rate within numerical accuracy; this shows that this calculation, too, uses the same day-count convention as the base curve.</p><h2>The option-adjusted spread</h2><p style="text-align: justify;">Finally, we have a third kind of spread, which is defined for bonds that include optionality. In the case of callable bonds, the library provides an <code>OAS</code> method that returns the option-adjusted spread. In order to use it, you&#8217;ll have to set the bond up so that it&#8217;s priced on a trinomial tree based on a short-rate model: Hull-White, for instance.</p><pre><code><code>bond = ql.CallableFixedRateBond(
    settlement_days,
    face_amount,
    schedule,
    coupons,
    day_counter,
    ql.Following,
    100.0,
    schedule[0],
    [
        ql.Callability(
            ql.BondPrice(100.0, ql.BondPrice.Clean),
            ql.Callability.Call,
            schedule[15],
        )
    ],
)

bond.setPricingEngine(
    ql.TreeCallableFixedRateBondEngine(
        ql.HullWhite(sofr_handle, a=0.1, sigma=0.01), 100
    )
)</code></code></pre><p style="text-align: justify;">As expected, the same target price returns a tighter spread.</p><pre><code><code>bond.OAS(
    price.amount(),
    sofr_handle,
    day_counter,
    ql.SimpleThenCompounded,
    ql.Annual,
) / bps</code></code></pre><pre><code><code>48.39174863100259</code></code></pre><p style="text-align: justify;">See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Some more improvements to "A QuantLib Guide"]]></title><description><![CDATA[A quick post to belatedly wish you a happy 2026 and to list a number of improvements in "A QuantLib Guide".]]></description><link>https://implementingquantlib.substack.com/p/some-more-improvements-to-a-quantlib</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/some-more-improvements-to-a-quantlib</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 15 Jan 2026 06:30:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back, dear reader. This is just a quick post to belatedly wish you a happy 2026 and to list a number of improvements in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>Let me mention again that, if you find the guide useful and want to support the effort, you can do so by buying <a href="https://leanpub.com/quantlibguide">a PDF version</a>. It&#8217;s published on Leanpub, which means that you&#8217;ll have free access to any future updates.</p><p>So, here is the list:</p><ul><li><p>there&#8217;s a new notebook on <a href="https://www.quantlibguide.com/Interest-rate%20indexes.html">interest-rate indexes</a> that shows how they behave and how to create custom ones;</p></li><li><p>there&#8217;s a corresponding new section on creating custom indexes in the notebook on <a href="https://www.quantlibguide.com/Inflation%20indexes%20and%20curves.html">inflation indexes and curves</a>;</p></li><li><p>the notebook on <a href="https://www.quantlibguide.com/Different%20approaches%20to%20numerical%20Theta.html">numerical Theta</a> has a new note making a connection between the calculated Theta and fixed-income calculations such as roll-down and pull-to-par.</p></li></ul><p>And since we&#8217;re talking about new goodies, QuantLib 1.41 was released a couple of days ago with quite a few contributions, so <a href="https://github.com/lballabio/QuantLib/releases/tag/v1.41">check it out</a> in case you missed it. Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[Inflation bonds]]></title><description><![CDATA[Welcome, dear reader. Here is the latest addition to "A QuantLibGuide": a notebook on inflation&#160;bonds.]]></description><link>https://implementingquantlib.substack.com/p/inflation-bonds</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/inflation-bonds</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 27 Nov 2025 06:30:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vv2L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome, dear reader. Here is the latest addition to <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>: a notebook on inflation bonds.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Inflation bonds</h2><pre><code><code>import QuantLib as ql
import pandas as pd</code></code></pre><pre><code><code>today = ql.Date(19, ql.September, 2021)
ql.Settings.instance().evaluationDate = today</code></code></pre><p>For the purposes of this notebook, I&#8217;ll create an inflation curve by interpolating a few zero rates, which could also happen in actual practice if rates are provided by an external system. Alternatively, it can be bootstrapped <a href="https://implementingquantlib.substack.com/p/inflation-indexes-and-curves">as already shown</a>.</p><p>As usual, since inflation indexes are published with a lag, the curve starts in the past; in this case, we&#8217;ll assume we already have August&#8217;s fixing, but the base date would go as far back as July otherwise.</p><pre><code><code>base_date = ql.Date(1, ql.August, 2021)
dates = [
    base_date,
    base_date + ql.Period(5, ql.Years),
    base_date + ql.Period(10, ql.Years),
]
rates = [0.02, 0.023, 0.025]

inflation_curve = ql.ZeroInflationTermStructureHandle(
    ql.ZeroInflationCurve(
        today,
        dates,
        rates,
        ql.Monthly,
        ql.Actual360(),
    )
)</code></code></pre><p>I&#8217;ll also create an inflation index; its historical fixings will be stored, while the curve above will be used to forecast future fixings.</p><pre><code><code>index = ql.EUHICP(inflation_curve)

index.addFixing(ql.Date(1, 1, 2019), 102.2)
index.addFixing(ql.Date(1, 2, 2019), 102.3)
index.addFixing(ql.Date(1, 3, 2019), 102.5)
index.addFixing(ql.Date(1, 4, 2019), 102.6)
index.addFixing(ql.Date(1, 5, 2019), 102.7)
index.addFixing(ql.Date(1, 6, 2019), 102.7)
index.addFixing(ql.Date(1, 7, 2019), 102.7)
index.addFixing(ql.Date(1, 8, 2019), 103.2)
index.addFixing(ql.Date(1, 9, 2019), 102.5)
index.addFixing(ql.Date(1, 10, 2019), 102.4)
index.addFixing(ql.Date(1, 11, 2019), 102.3)
index.addFixing(ql.Date(1, 12, 2019), 102.5)
index.addFixing(ql.Date(1, 1, 2020), 102.7)
index.addFixing(ql.Date(1, 2, 2020), 102.5)
index.addFixing(ql.Date(1, 3, 2020), 102.6)
index.addFixing(ql.Date(1, 4, 2020), 102.5)
index.addFixing(ql.Date(1, 5, 2020), 102.3)
index.addFixing(ql.Date(1, 6, 2020), 102.4)
index.addFixing(ql.Date(1, 7, 2020), 102.3)
index.addFixing(ql.Date(1, 8, 2020), 102.5)
index.addFixing(ql.Date(1, 9, 2020), 101.9)
index.addFixing(ql.Date(1, 10, 2020), 102)
index.addFixing(ql.Date(1, 11, 2020), 102)
index.addFixing(ql.Date(1, 12, 2020), 102.3)
index.addFixing(ql.Date(1, 1, 2021), 102.9)
index.addFixing(ql.Date(1, 2, 2021), 103)
index.addFixing(ql.Date(1, 3, 2021), 103.3)
index.addFixing(ql.Date(1, 4, 2021), 103.7)
index.addFixing(ql.Date(1, 5, 2021), 103.6)
index.addFixing(ql.Date(1, 6, 2021), 103.8)
index.addFixing(ql.Date(1, 7, 2021), 104.2)
index.addFixing(ql.Date(1, 8, 2021), 104.7)</code></code></pre><h2>Simple inflation bonds</h2><p>Currently, the library only provides the <code>CPIBond</code> class. It models bonds paying inflation-based coupons and redemption. The i-th coupon pays an amount given by (apologies, Substack won&#8217;t let me inline a formula in a paragraph)</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;C = N \\times \\left(I_i/I_0\\right) \\times \\Delta T \\times r&quot;,&quot;id&quot;:&quot;HNIJGWNPPM&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <em>N</em> is the notional, <em>&#8710;T</em> is the accrual period of the coupon, <em>r</em> is a given fixed rate, <em>I(0)</em> (again, apologies) is a base CPI value, the same for all coupons, and <em>I(i)</em> is the value of the index at the end of the coupon minus an observation lag. The redemption is</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;R = N \\times \\left(I_N/I_0\\right)&quot;,&quot;id&quot;:&quot;GXJJTGOJJY&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <em>I(N)</em> is the value of the index at the maturity of the bond minus the observation lag.</p><p>The parameter of the constructor are those of most bonds (a schedule, a day-count convention, the notional, the settlement days) plus the base CPI value, the fixed rate, the observation lag, and the interpolation type (flat of linear.)</p><pre><code><code>schedule = ql.Schedule(
    ql.Date(8, ql.May, 2020),
    ql.Date(8, ql.May, 2026),
    ql.Period(6, ql.Months),
    ql.TARGET(),
    ql.Following,
    ql.Following,
    ql.DateGeneration.Backward,
    False,
)

settlement_days = 3
face_amount = 100.0
growth_only = False
base_cpi = 102.0
observation_lag = ql.Period(3, ql.Months)
fixed_rate = 0.02

bond = ql.CPIBond(
    settlement_days,
    face_amount,
    growth_only,
    base_cpi,
    observation_lag,
    index,
    ql.CPI.Flat,
    schedule,
    [fixed_rate],
    ql.Thirty360(ql.Thirty360.BondBasis),
)</code></code></pre><p>(Never mind the <code>growth_only</code> parameter; it&#8217;s already deprecated in the underlying C++ library, so you can omit it if you&#8217;re using it from that language, and will be removed in one of the next releases of the Python module. You can always set it to <code>False</code>).</p><p>As usual, we can use a helper function (<code>as_cpi_coupon</code>) to downcast the bond coupons and get additional information:</p><pre><code><code>coupon_data = []

for cf in bond.cashflows():
    c = ql.as_cpi_coupon(cf)
    if c is not None:
        coupon_data.append(
            (
                c.date(),
                c.rate(),
                c.indexFixing(),
                c.amount()
            )
        )
    else:
        coupon_data.append(
            (
                cf.date(),
                None,
                None,
                cf.amount()
            )
        )

df = pd.DataFrame(
    coupon_data,
    columns=(
        &#8221;date&#8221;,
        &#8220;rate&#8221;,
        &#8220;index fixing&#8221;,
        &#8220;amount&#8221;
    )
)
df.style.format(
    {
        &#8221;rate&#8221;: &#8220;{:.4%}&#8221;,
        &#8220;index fixing&#8221;: &#8220;{:.2f}&#8221;,
        &#8220;amount&#8221;: &#8220;{:.4f}&#8221;
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vv2L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vv2L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 424w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 848w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 1272w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vv2L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png" width="520" height="353.63984674329504" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:710,&quot;width&quot;:1044,&quot;resizeWidth&quot;:520,&quot;bytes&quot;:147331,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/179897765?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vv2L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 424w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 848w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 1272w, https://substackcdn.com/image/fetch/$s_!vv2L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe60e7dd5-4a61-47b2-bd04-50b6851fc9a1_1044x710.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And as usual, we can set a discounting engine to the bond and ask for its price:</p><pre><code><code>discount_curve = ql.YieldTermStructureHandle(
    ql.FlatForward(today, 0.01, ql.Actual365Fixed())
)
bond.setPricingEngine(
    ql.DiscountingBondEngine(discount_curve)
)</code></code></pre><pre><code><code>print(bond.cleanPrice())
print(bond.dirtyPrice())
print(bond.accruedAmount())</code></code></pre><pre><code><code>118.368287509748
119.11456201955193
0.7462745098039215</code></code></pre><h3>Pricing conventions</h3><p>Note that the price of the bond is returned as the discounted sum of the cash flows as defined above. In some markets, though, the convention is to quote as bond price the value above divided by the increase <em>I(S)/I(0)</em> of the inflation from the start of the bond to its current settlement date. The <code>CPIBond</code> class still doesn&#8217;t provide this feature; if that&#8217;s the required convention, we&#8217;ll have to adjust for the inflation factor manually, as in:</p><pre><code><code>current_cpi = ql.CPI.laggedFixing(
    index,
    bond.settlementDate(),
    observation_lag,
    ql.CPI.Flat
)
inflation_factor = current_cpi / base_cpi

print(bond.cleanPrice() / inflation_factor)</code></code></pre><pre><code><code>116.3156582465732</code></code></pre><h2>More exotic bonds</h2><p>Bonds other than simple CPI bonds (as defined above) can be built using the basic facilities provided by the library; but as we&#8217;ll see, this has drawbacks.</p><p>As an example, let&#8217;s take an Italian inflation bond I happened to come across a while ago. At each coupon maturity, it pays</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;C = N \\times \\left(I_i/I_{i-1}\\right) \\times \\Delta T \\times r&quot;,&quot;id&quot;:&quot;DRBOANSYCV&quot;}" data-component-name="LatexBlockToDOM"></div><p>that is, a fixed rate multiplied by the increase of the inflation over the life of the coupon (with an observation lag, as usual), and it also pays a principal payment</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;P = N \\times \\left(I_i/I_{i-1} - 1\\right).&quot;,&quot;id&quot;:&quot;FTLXSTRVSR&quot;}" data-component-name="LatexBlockToDOM"></div><p>However, if the inflation decreases over the life of the coupon, the payoff changes: there is no principal payment, and the coupon is simply a fixed-rate coupon paying <em>r</em>. In this case, the base value <em>I(i-1)</em> will be used for the next coupon instead.</p><p>There is no such bond in the library, but we can try building its cash flows. Here are the basic bond parameters:</p><pre><code><code>schedule = ql.Schedule(
    ql.Date(11, 4, 2019),
    ql.Date(11, 4, 2026),
    ql.Period(6, ql.Months),
    ql.TARGET(),
    ql.Unadjusted,
    ql.Unadjusted,
    ql.DateGeneration.Backward,
    False,
)

settlement_days = 2
face_amount = 100000
observation_lag = ql.Period(3, ql.Months)
fixed_rate = 0.004
day_counter = ql.Thirty360(ql.Thirty360.BondBasis)</code></code></pre><p>First, the coupons. The base CPI value for the first coupon is the interpolated fixing of the index at its start; for the ones after that, it&#8217;s the fixing at the end of the previous coupon. However, if the fixing at the end is lower than the fixing at the start, we replace the coupon with a fixed-rate one and keep the base CPI value so it can be used for the next coupon.</p><pre><code><code>coupons = []
base_cpi = ql.EUHICP(inflation_curve).fixing(
    schedule[0] - observation_lag
)
interpolation = ql.CPI.Linear
cpi_pricer = ql.CPICouponPricer()

for i in range(1, len(schedule)):
    start_date = schedule[i - 1]
    end_date = schedule[i]
    payment_date = ql.TARGET().adjust(end_date)

    c = ql.CPICoupon(
        base_cpi,
        payment_date,
        face_amount,
        start_date,
        end_date,
        index,
        observation_lag,
        interpolation,
        day_counter,
        fixed_rate,
    )
    c.setPricer(cpi_pricer)

    if c.baseCPI() &lt;= c.indexFixing():
        # normal case
        coupons.append(c)
        base_cpi = c.indexFixing()
    else:
        # use a fixed-rate coupon with the
        # same dates; also don&#8217;t update base CPI
        cf = ql.FixedRateCoupon(
            c.date(),
            face_amount,
            fixed_rate,
            day_counter,
            c.accrualStartDate(),
            c.accrualEndDate(),
            c.referencePeriodStart(),
            c.referencePeriodEnd(),
        )
        coupons.append(cf)</code></code></pre><p>Next, the principal payments. We can model them using the <code>ZeroInflationCashFlow</code> class;. Again, we have to keep track of whether the inflation increases or decreases over the period and adjust the cash flows accordingly.</p><pre><code><code>redemptions = []

growth_only = True
skipped_months = 0

for i in range(len(coupons) - 1):
    start_date = schedule[i - skipped_months]
    end_date = schedule[i + 1]
    payment_date = coupons[i].date()

    cf = ql.ZeroInflationCashFlow(
        face_amount,
        index,
        interpolation,
        start_date,
        end_date,
        observation_lag,
        payment_date,
        growth_only,
    )

    if cf.amount() &gt; 0:
        redemptions.append(cf)
        skipped_months = 0
    else:
        redemptions.append(
            ql.SimpleCashFlow(0.0, payment_date)
        )
        skipped_months = skipped_months + 1</code></code></pre><p>The final principal payment includes the redemption:</p><pre><code><code>growth_only = False
payment_date = coupons[-1].date()

cf = ql.ZeroInflationCashFlow(
    face_amount,
    index,
    interpolation,
    schedule[-2 - skipped_months],
    schedule[-1],
    observation_lag,
    payment_date,
    growth_only,
)

if cf.amount() &gt; face_amount:
    redemptions.append(cf)
else:
    redemptions.append(
        ql.SimpleCashFlow(face_amount, payment_date)
    )</code></code></pre><p>We can now build the bond by passing the cash flows we created:</p><pre><code><code>cashflows = sorted(
    coupons + redemptions, key=lambda c: c.date()
)

issue_date = ql.Date(11, 4, 2019)
maturity_date = cashflows[-1].date()

bond = ql.Bond(
    settlement_days,
    ql.TARGET(),
    face_amount,
    maturity_date,
    issue_date,
    cashflows,
)</code></code></pre><p>The following shows the cashflows of the bond. You can see a few cases (that is, in April and October 2020) where the inflation decreased with respect to the base CPI, and therefore the principal payments reverted to 0 and the interest payment reverted to a fixed-rate coupon.</p><pre><code><code>coupon_data = []
for cf in bond.cashflows():
    c = ql.as_cpi_coupon(cf)
    if c is not None:
        coupon_data.append(
            (
                c.date(),
                c.rate(),
                c.baseCPI(),
                c.indexFixing(),
                c.amount(),
                &#8220;interest&#8221;,
            )
        )
    else:
        c = ql.as_fixed_rate_coupon(cf)
        if c is not None:
            index_fixing = ql.CPI.laggedFixing(
                index,
                c.date(),
                observation_lag,
                interpolation
            )
            coupon_data.append(
                (
                    c.date(),
                    c.rate(),
                    None,
                    None,
                    c.amount(),
                    &#8220;interest&#8221;
                )
            )
        else:
            coupon_data.append(
                (
                    cf.date(),
                    None,
                    None,
                    None,
                    cf.amount(),
                    &#8220;principal&#8221;
                )
            )

df = pd.DataFrame(
    coupon_data,
    columns=(
        &#8221;date&#8221;,
        &#8220;rate&#8221;,
        &#8220;base CPI&#8221;,
        &#8220;fixing&#8221;,
        &#8220;amount&#8221;,
        &#8220;type&#8221;
    ),
)
df.style.format(
    {
        &#8220;rate&#8221;: &#8220;{:.4%}&#8221;,
        &#8220;base CPI&#8221;: &#8220;{:.2f}&#8221;,
        &#8220;fixing&#8221;: &#8220;{:.2f}&#8221;,
        &#8220;amount&#8221;: &#8220;{:.2f}&#8221;,
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xgXE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xgXE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 424w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 848w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 1272w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xgXE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png" width="636" height="723.861852433281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1450,&quot;width&quot;:1274,&quot;resizeWidth&quot;:636,&quot;bytes&quot;:323407,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/179897765?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xgXE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 424w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 848w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 1272w, https://substackcdn.com/image/fetch/$s_!xgXE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd0afb-3571-4ab7-a46c-b3984083af99_1274x1450.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>An important drawback</h3><p>Unfortunately, building a bond this way goes against the grain of the library. By creating coupons this way, we&#8217;re freezing their base CPI values &#8212; even for future coupons, whose base CPI are just a forecast that depend on the inflation curve &#8212; and we&#8217;re pre-determining whether a given principal payment will or won&#8217;t happen. Thus, the bond we&#8217;re building won&#8217;t work with the usual bump-and-reprice methods we&#8217;re used to; these half-predetermined coupons won&#8217;t react properly to the change in the inflation curve. If the latter changes, we&#8217;ll need to rebuild the coupons and the bond from scratch instead.</p><p>To work properly, these kind of coupon should be implemented in the library; they should determine their base CPI from the curve, and should somehow be linked to the previous coupon so they can know whether they should reuse its base CPI.</p><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Bonds and CDS curves]]></title><description><![CDATA[Hello again.&#160; Here is a short notebook in which default-probability curves are used for pricing bonds.]]></description><link>https://implementingquantlib.substack.com/p/bonds-and-cds-curves</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/bonds-and-cds-curves</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 30 Oct 2025 06:30:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again. Following <a href="https://implementingquantlib.substack.com/p/default-probability-curves">the notebook I posted a couple of months ago on default probability curves</a>, here is another short one in which they are used for pricing bonds. As usual, the notebook is also available in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Bonds and CDS curves</h2><pre><code><code>import QuantLib as ql</code></code></pre><pre><code><code>today = ql.Date(27, ql.April, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><p>What if you don&#8217;t have a sensible discount curve available for a corporate issue, but you have a CDS curve instead?</p><p>That&#8217;s not optimal, of course. There can be a significant basis between the CDS and bond markets (for instance, because of their different liquidity); make sure that you&#8217;re aware of any such issues before doing your calculations.</p><p>This said, let&#8217;s take a sample fixed-rate bond:</p><pre><code><code>schedule = ql.Schedule(
    ql.Date(8, ql.February, 2024),
    ql.Date(8, ql.February, 2034),
    ql.Period(6, ql.Months),
    ql.UnitedStates(ql.UnitedStates.GovernmentBond),
    ql.Following,
    ql.Following,
    ql.DateGeneration.Backward,
    False,
)

settlement_days = 3
face_amount = 10_000
coupons = [0.03]
payment_day_counter = ql.Thirty360(
    ql.Thirty360.BondBasis
)

bond = ql.FixedRateBond(
    settlement_days,
    face_amount,
    schedule,
    coupons,
    payment_day_counter
)</code></code></pre><h3>A risky bond engine</h3><p>The <code>RiskyBondEngine</code> class makes it possible to calculate a price by discounting its coupons at the risk-free rate, but also weighing them by the probability that they are actually paid; that is, the probability that the issuer doesn&#8217;t default. We&#8217;ll need a risk-free curve&#8230;</p><pre><code><code>dates, rates = zip(
    *[
        (ql.Date(27, 4, 2025), 0.02022),
        (ql.Date(27, 7, 2025), 0.02064),
        (ql.Date(27, 10, 2025), 0.02041),
        (ql.Date(27, 4, 2026), 0.02163),
        (ql.Date(27, 4, 2027), 0.02463),
        (ql.Date(27, 4, 2028), 0.02718),
        (ql.Date(27, 4, 2029), 0.02905),
        (ql.Date(27, 4, 2030), 0.03067),
        (ql.Date(27, 4, 2031), 0.03161),
        (ql.Date(27, 4, 2032), 0.03232),
        (ql.Date(27, 4, 2033), 0.03305),
        (ql.Date(27, 4, 2034), 0.03358),
        (ql.Date(27, 4, 2035), 0.03402),
        (ql.Date(27, 4, 2040), 0.03565),
        (ql.Date(27, 4, 2045), 0.03619),
        (ql.Date(27, 4, 2050), 0.03619),
        (ql.Date(27, 4, 2055), 0.03620),
    ]
)

risk_free_curve = ql.ZeroCurve(
    dates, rates, ql.Actual365Fixed()
)</code></code></pre><p>&#8230;and a default probability curve, that can be bootstrapped from CDS quotes.</p><pre><code><code>cds_data = [
    (ql.Period(1, ql.Years), 0.004),
    (ql.Period(2, ql.Years), 0.008),
    (ql.Period(3, ql.Years), 0.013),
    (ql.Period(5, ql.Years), 0.021),
    (ql.Period(7, ql.Years), 0.027),
    (ql.Period(10, ql.Years), 0.034),
]

fixed_rate = 0.01
recovery_rate = 0.4
cds_settlement_days = 1
upfront_settlement_days = 3
calendar = ql.UnitedStates(
    ql.UnitedStates.GovernmentBond
)

helpers = [
    ql.UpfrontCdsHelper(
        quote,
        fixed_rate,
        tenor,
        cds_settlement_days,
        calendar,
        ql.Quarterly,
        ql.ModifiedFollowing,
        ql.DateGeneration.CDS2015,
        ql.Actual360(),
        recovery_rate,
        ql.YieldTermStructureHandle(risk_free_curve),
        upfront_settlement_days,
    )
    for tenor, quote in cds_data
]

probability_curve = ql.PiecewiseFlatHazardRate(
    today, helpers, ql.Actual360()
)</code></code></pre><p>Once we have those, we can instantiate the engine, set it to the bond, and retrieve the results we want:</p><pre><code><code>bond.setPricingEngine(
    ql.RiskyBondEngine(
        ql.DefaultProbabilityTermStructureHandle(
            probability_curve
        ),
        recovery_rate,
        ql.YieldTermStructureHandle(risk_free_curve),
    )
)</code></code></pre><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>87.59096931191402</code></code></pre><h3>Back to rates</h3><p>If we want to translate this into a corresponding credit spread to be applied on top of the risk-free curve, we can create a discount curve with a spread yet to be determined:</p><pre><code><code>credit_spread = ql.SimpleQuote(0.0)

discount_curve = ql.ZeroSpreadedTermStructure(
    ql.YieldTermStructureHandle(risk_free_curve),
    ql.QuoteHandle(credit_spread),
)</code></code></pre><p>Then, we can use the CDS-based price as a target&#8230;</p><pre><code><code>target_price = bond.cleanPrice()</code></code></pre><p>&#8230;and set a new engine to the bond. Next we define a function that, given a value for the spread, calculates the corresponding bond price and returns its difference from the target price; we can then pass it to a solver that finds its zero, i.e., the spread for which the bond price is the same as the CDS-based price.</p><pre><code><code>bond.setPricingEngine(
    ql.DiscountingBondEngine(
        ql.YieldTermStructureHandle(discount_curve)
    )
)</code></code></pre><pre><code><code>def objective_function(s):
    credit_spread.setValue(s)
    return bond.cleanPrice() - target_price</code></code></pre><pre><code><code>solver = ql.Brent()
spread = solver.solve(
    objective_function, 1e-7, 0.005, 0.0001
)
spread</code></code></pre><pre><code><code>0.013749869755662938</code></code></pre><p>We can check that the price is indeed the same, within numerical tolerance:</p><pre><code><code>credit_spread.setValue(spread)
bond.cleanPrice()</code></code></pre><pre><code><code>87.59096931265552</code></code></pre><p>We can also express the spread as a difference between bond yields; namely, the yield that we can calculate based on the target price&#8230;</p><pre><code><code>y0 = bond.bondYield(
    ql.BondPrice(target_price, ql.BondPrice.Clean),
    payment_day_counter,
    ql.Compounded,
    ql.Semiannual,
)
y0</code></code></pre><pre><code><code>0.047452630996704104</code></code></pre><p>&#8230;and the one we can obtain from a risk-free bond price, that we can obtain by using the risk-free curve for discounting:</p><pre><code><code>bond.setPricingEngine(
    ql.DiscountingBondEngine(
        ql.YieldTermStructureHandle(risk_free_curve)
    )
)
risk_free_price = bond.cleanPrice()
risk_free_price</code></code></pre><pre><code><code>97.4066704451667</code></code></pre><pre><code><code>y1 = bond.bondYield(
    ql.BondPrice(
        risk_free_price, ql.BondPrice.Clean
    ),
    payment_day_counter,
    ql.Compounded,
    ql.Semiannual,
)
y1</code></code></pre><pre><code><code>0.03343150448799134</code></code></pre><p>The difference between the two yields is comparable to the z-spread we calculated earlier; given the different conventions (the z-spread is continuously compounded) we didn&#8217;t expect them to be the same.</p><pre><code><code>y0 - y1</code></code></pre><pre><code><code>0.014021126508712761</code></code></pre><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA["Implementing QuantLib" as a paperback: five years later]]></title><description><![CDATA[Recently I realized that it&#8217;s already five years since I published my Implementing QuantLib book as a paperback on Amazon, effectively freezing it. Is it still up to date? Let's find out.]]></description><link>https://implementingquantlib.substack.com/p/implementing-quantlib-as-a-paperback</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/implementing-quantlib-as-a-paperback</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 02 Oct 2025 05:30:58 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f1967278-a50c-4b6d-8188-c650c28f6422_363x522.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. Recently I realized that it&#8217;s already five years since I published my <em>Implementing QuantLib</em> book as <a href="https://getbook.at/implementingquantlib">a paperback on Amazon</a>, effectively freezing it. Is it still up to date?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>On the whole, yes; it is still a valid look inside the architecture of the library and the train of thought that went into it. The main way that the book shows its age is that it still refers to C++11 in multiple places as a language standard we wanted to adopt in the future; we made the switch in 2021 and we&#8217;re now well beyond that, using C++17 as the minimum required language version.</p><p>This said, there were some changes in the library, hopefully for the better. Given the five-years anniversary, I thought it would be a good idea for me to re-read the book and look at some of them to see how they affect its content.</p><p>Of course, these notes also apply to the digital version of <em>Implementing QuantLib</em> <a href="https://leanpub.com/implementingquantlib">available on Leanpub</a>. But I should warn you: you can&#8217;t leave the digital edition nonchalantly on your coffee table to show your guests that you&#8217;re a discerning quant.</p><h3>Chapter 1: introduction</h3><p>This is still a valid introduction to the book, and it is also proof that I&#8217;ve been using em-dashes for years&#8212;well before people started to associate them with AI-generated content. So don&#8217;t worry if you see me sprinkle some of them here. This content is still 100% organic.</p><h3>Chapter 2: financial instruments and pricing engines</h3><p>No relevant changes here: notifications and lazy calculations, with their pros and cons, continue to be the main design feature of the library, and I don&#8217;t have reasons to think that this will change in the future, even if I would have liked a better separation between the core math formulas and the rest of the framework. As they say in Italy, <em>chi nasce tondo non pu&#242; morire quadrato</em>.</p><h3>Chapter 3: term structures</h3><p>There are no architectural changes in the classes described here, but we were able to take advantage of later C++ versions and simplify some of the code. For instance, I describe here a <code>BootstrapError</code> class which is instantiated and passed to the solver during bootstrapping: you can still see it <a href="https://github.com/lballabio/QuantLib/blob/v1.39/ql/termstructures/bootstraperror.hpp#L35-L51">in our GitHub repository</a>. The whole class is now replaced by a much simpler lambda:</p><pre><code><code>auto error = [&amp;](Rate guess) {
    Traits::updateGuess(ts_-&gt;data_, guess, i);
    ts_-&gt;interpolation_.update();
    return helper-&gt;quoteError();
};</code></code></pre><p>On the other hand, in recent years there were a number of notable changes in classes that I only described in passing: inflation term structures. The book mentions that they contain a nominal term structure; this is no longer the case. Other changes (such as moving the interpolation into coupons where it belongs, or replacing a base lag with an explicit base date) touch features not covered in the book. You can see the updated inflation term structures used in <em><a href="https://www.quantlibguide.com/Inflation%20indexes%20and%20curves.html">A QuantLib Guide</a></em>.</p><h3>Chapter 4: cash flows and coupons</h3><p>A notable change here is that the <code>CashFlow</code> class now inherits from <code>LazyObject</code>. Therefore, most of the calculations that were previously performed directly in methods like <code>amount</code> were moved into <code>performCalculations</code> instead, with <code>amount</code> usually implemented as, for instance,</p><pre><code><code>Real FixedRateCoupon::amount() const {
    calculate();
    return amount_;
}</code></code></pre><p>User-defined classes don&#8217;t need to be changed to adapt to this; the old implementation will still work, but it won&#8217;t take advantage of lazy calculations.</p><p>Because of this change, the cash-flow implementations shown in the book no longer reflect those in the library; however, the hierarchy of the classes and the reasoning behind it remained the same.</p><p>Another change I might mention is the attempt to move towards fewer overloaded constructors for bonds. For instance, the book shows an example of fixed-rate bond where the constructor takes all the parameters needed to build the cash-flow schedule and builds the latter internally; the library also provided an overloaded constructor taking a pre-built schedule, not shown in the example. Other constructors where also provided for convenience. The library now provides just one constructor, namely, the one taking a schedule, mostly so that it&#8217;s possible to enable keyword arguments in Python (which would be prevented if the constructor were overloaded).</p><p>The upgrade to C++17 allowed us to use fewer overloaded constructors in other classes, as well; for instance, the constructors of some bootstrap helpers that used to take either a simple <code>Rate</code> or a <code>Handle&lt;Quote&gt;</code> can now declare a single constructor taking a <code>std::variant&lt;Rate, Handle&lt;Quote&gt;&gt;</code> and sort it out internally.</p><p>Finally, a smaller change: some methods like <code>Bond::yield</code> that used to take a price as a simple number now take it as a small <code>Bond::Price</code> structure containing both the price and an enum specifying the type (clean or dirty).</p><h3>Chapter 5: parameterized models and calibration</h3><p>No major changes here; instead, we managed to fix a number of smaller things I complained about in the book. The base class <code>BlackCalibrationHelper</code> no longer stores a yield term structure; its inner class <code>ImpliedVolatilityHelper</code> was replaced by a lambda; the <code>HestonModelHelper</code> class moved all its calculations in the <code>performCalculations</code> method instead of spreading them around the class; and using a later C++ standard allowed the inner class <code>CalibrationFunction</code> to avoid being declared as a friend of <code>CalibratedModel</code>.</p><h3>Chapter 6: the Monte Carlo framework</h3><p>The most relevant change here is that the <code>Disposable</code> class template disappeared, since its purpose was to implement move semantics and we have direct support for it in the language now. This makes the interface (and sometimes the implementation) of the various stochastic processes a lot more readable.</p><p>One change we might make in the future is to replace the default random-number generator: the library now also provides the xoshiro256** generator, which provides higher speed than the Mersenne Twister without loss of quality.</p><h3>Chapter 7: the tree framework</h3><p>No change here, almost literally: in the past five years, the only commits in this part of the code were automated changes by <code>clang-tidy</code> (like replacing type declarations with <code>auto</code> or replacing pass-by-reference and copy with pass-by-value and move).</p><h3>Chapter 8: the finite-difference framework</h3><p>Here the new framework is replacing the old one, as I hoped. The classes in the old framework are slowly being deprecated and removed; we&#8217;re probably more than halfway across the process, and the last traces of the old framework might disappear in two or three years (apart from a couple of class templates like <code>BoundaryCondition</code> and <code>StepCondition</code> that are also used in the new one). At that point, that part of the book might still be useful as a comparison between design choice; but it would no longer describe actual library code.</p><p>There were no relevant changes in the new framework.</p><h3>Chapter 9: conclusion</h3><p>Like the intro, it is still valid&#8212;except for the part where I describe writing a book as a daunting task. After <em>Implementing QuantLib</em>, <em>QuantLib Python Cookbook</em> and <em>A QuantLib Guide</em>, it seems clear that I&#8217;ll write one at the slightest provocation.</p><h3>Appendices</h3><p>No relevant changes here, apart from the disappearance of <code>Disposable</code> that I have already mentioned. You can find more about the Observer pattern in <em><a href="https://www.quantlibguide.com/The%20Observer%20pattern.html">A QuantLib Guide</a></em>, but it adds to the description in <em>Implementing QuantLib</em> rather than replacing it.</p><h3>The verdict</h3><p>In short, I was happy to find that <em>Implementing QuantLib</em> still holds its own surprisingly well. If you&#8217;re curious about the architecture of the library, it&#8217;s still the book to read.</p><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Default-probability curves]]></title><description><![CDATA[Hello, dear reader.&#160; Here is the latest addition to A QuantLib Guide: a notebook on bootstrapping and using default-probability curves.]]></description><link>https://implementingquantlib.substack.com/p/default-probability-curves</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/default-probability-curves</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 04 Sep 2025 05:30:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Rs1Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, dear reader. Here is the latest addition to <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>: a notebook on bootstrapping and using default-probability curves. It&#8217;s the first one in a new section of the guide. More to come.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Default-probability curves</h2><p>As usual, we&#8217;ll import the required modules and set the evaluation date. For convenience, we also define a constant that will let us write spreads more readably as <code>15 * bps</code> instead of <code>0.0015</code>.</p><pre><code>import QuantLib as ql
import pandas as pd</code></pre><pre><code><code>today = ql.Date(27, ql.August, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><pre><code><code>bps = 1e-4</code></code></pre><p>Like for other types of curves, QuantLib provides a few classes to bootstrap default-probability term structures based on market data. Nowadays, the most common way to quote a credit default swap (CDS) is to assume a standard spread (100 bps or 500 bps, based on the market) and to quote an additional upfront payment as a fraction of the notional. The sign of the upfront is seen as paid by the protection buyer; in the data below, a negative quote means that the buyer would receive 0.2% of the notional when entering into a 1-year CDS and a positive quote means the buyer would pay 1.1% of the notional when entering a 10-years one.</p><pre><code><code>cds_data = [
    (ql.Period(1, ql.Years), -0.002),
    (ql.Period(2, ql.Years), -0.005),
    (ql.Period(3, ql.Years), -0.008),
    (ql.Period(5, ql.Years), -0.004),
    (ql.Period(7, ql.Years), 0.007),
    (ql.Period(10, ql.Years), 0.011),
]

cds_spread = 100 * bps
recovery_rate = 0.4
cds_settlement_days = 1
upfront_settlement_days = 3

calendar = ql.UnitedStates(
    ql.UnitedStates.GovernmentBond
)</code></code></pre><p>The bootstrap process also needs an interest-rate curve as an input, in order to discount the CDS payments; a risk-free one, since the credit risk will be expressed by the default-probability curve itself. Here, for simplicity, I&#8217;m using a constant one.</p><pre><code><code>risk_free_curve = ql.YieldTermStructureHandle(
    ql.FlatForward(
        0, calendar, 0.03, ql.Actual365Fixed()
    )
)</code></code></pre><p>As we&#8217;ve already seen for other kinds of bootstrapped curves, each of the market quotes is wrapped into a helper; in this case, the class to use is <code>UpfrontCdsHelper</code>. The helpers are then passed to the curve constructor. In C++, templates give us a choice of the interpolation method and of the underlying quantity to model; for instance, we might declare types such as <code>PiecewiseDefaultCurve&lt;SurvivalProbability,Linear&gt;</code>, <code>PiecewiseDefaultCurve&lt;HazardRate,BackwardFlat&gt;</code>, <code>PiecewiseDefaultCurve&lt;DefaultDensity,LogLinear&gt;</code> or any other combination. In Python, at the time of this writing, only <code>PiecewiseFlatHazardRate</code> is available.</p><pre><code><code>helpers = [
    ql.UpfrontCdsHelper(
        quote,
        cds_spread,
        tenor,
        cds_settlement_days,
        calendar,
        ql.Quarterly,
        ql.ModifiedFollowing,
        ql.DateGeneration.CDS2015,
        ql.Actual360(),
        recovery_rate,
        risk_free_curve,
        upfront_settlement_days,
    )
    for tenor, quote in cds_data
]

probability_curve = ql.PiecewiseFlatHazardRate(
    0, calendar, helpers, ql.Actual365Fixed()
)</code></code></pre><p>Once we have the curve, we can show its nodes. I&#8217;ll extract the corresponding probabilities of default, too.</p><pre><code><code>pd.DataFrame(
    [
        (
             date,
             rate,
             probability_curve.defaultProbability(date)
        )
        for date, rate in probability_curve.nodes()
    ],
    columns=[
        "date",
        "hazard rate",
        "default probability"
    ],
).style.format(
    {
        "hazard rate": "{:.2%}",
        "default probability": "{:.2%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Rs1Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 424w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 848w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 1272w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png" width="516" height="226" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:452,&quot;width&quot;:1032,&quot;resizeWidth&quot;:516,&quot;bytes&quot;:196293,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/172412059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 424w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 848w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 1272w, https://substackcdn.com/image/fetch/$s_!Rs1Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6926f94d-216a-4e7d-a5b1-fa1590b0dc62_1032x452.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>One thing to note: the modern convention for CDS is to have standardized maturity dates, rolling twice a year; you can see that by looking at the node dates, which don&#8217;t correspond to a whole number of years after the reference date. This means that, as the evaluation date rolls forward, the nodes of the curve stay fixed&#8230;</p><pre><code><code>today = ql.Date(19, ql.September, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><pre><code><code>pd.DataFrame(
    [
        (
             date,
             rate,
             probability_curve.defaultProbability(date)
        )
        for date, rate in probability_curve.nodes()
    ],
    columns=[
        "date",
        "hazard rate",
        "default probability"
    ],
).style.format(
    {
        "hazard rate": "{:.2%}",
        "default probability": "{:.2%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!roGW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!roGW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!roGW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!roGW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!roGW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!roGW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png" width="548" height="231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:462,&quot;width&quot;:1096,&quot;resizeWidth&quot;:548,&quot;bytes&quot;:204651,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/172412059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!roGW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!roGW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!roGW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!roGW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bd5e1-6af8-48c1-b1e3-c174231c3549_1096x462.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>&#8230;until they suddenly jump ahead 6 months, when the CDS maturities roll.</p><pre><code><code>today = ql.Date(22, ql.September, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><pre><code><code>pd.DataFrame(
    [
        (
             date,
             rate,
             probability_curve.defaultProbability(date)
        )
        for date, rate in probability_curve.nodes()
    ],
    columns=[
        "date",
        "hazard rate",
        "default probability"
    ],
).style.format(
    {
        "hazard rate": "{:.2%}",
        "default probability": "{:.2%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pev2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pev2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!pev2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!pev2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!pev2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pev2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png" width="548" height="231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/daa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:462,&quot;width&quot;:1096,&quot;resizeWidth&quot;:548,&quot;bytes&quot;:219740,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/172412059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pev2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!pev2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!pev2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!pev2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaa3dcc8-d966-4ddd-a647-42a1ddfd8604_1096x462.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Older conventions</h3><p>Years ago, when CDS were less standardized, the convention was to quote the CDS spread instead of the upfront. If you need to deal with this kind of data, you can use the <code>SpreadCdsHelper</code> class:</p><pre><code><code>old_cds_data = [
    (ql.Period(1, ql.Years), 70 * bps),
    (ql.Period(2, ql.Years), 80 * bps),
    (ql.Period(3, ql.Years), 100 * bps),
    (ql.Period(5, ql.Years), 140 * bps),
    (ql.Period(7, ql.Years), 170 * bps),
    (ql.Period(10, ql.Years), 210 * bps),
]

helpers = [
    ql.SpreadCdsHelper(
        quote,
        tenor,
        cds_settlement_days,
        calendar,
        ql.Quarterly,
        ql.ModifiedFollowing,
        ql.DateGeneration.CDS,
        ql.Actual360(),
        recovery_rate,
        risk_free_curve,
    )
    for tenor, quote in old_cds_data
]

old_probability_curve = ql.PiecewiseFlatHazardRate(
    0, calendar, helpers, ql.Actual360()
)</code></code></pre><pre><code><code>pd.DataFrame(
    [
        (
             date,
             rate,
             old_probability_curve.defaultProbability(date)
        )
        for date, rate in old_probability_curve.nodes()
    ],
    columns=[
        "date",
        "hazard rate",
        "default probability"
    ],
).style.format(
    {
        "hazard rate": "{:.2%}",
        "default probability": "{:.2%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xKmg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xKmg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xKmg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png" width="548" height="231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:462,&quot;width&quot;:1096,&quot;resizeWidth&quot;:548,&quot;bytes&quot;:219918,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/172412059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xKmg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!xKmg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e60a3d3-575e-4502-b32d-5808be2b2ea4_1096x462.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Checking the correctness of the curve</h3><p>In order to verify that the bootstrap process results in a consistent curve, we can choose one of the quotes (the 3-years one, for example), create the corresponding CDS, and check that it is fair; i.e., that its NPV is close to 0 within numerical accuracy.</p><pre><code><code>test_tenor, test_upfront = cds_data[2]
test_tenor, test_upfront</code></code></pre><pre><code><code>(Period("3Y"), -0.008)</code></code></pre><p>There are a few conventions to consider if we want to get the correct result. Usually, and in the parameters we passed to the helpers, the CDS settle at T+1 and the upfront is paid at T+3. As I mentioned, the maturity date is standardized and can be retrieved using the <code>cdsMaturity</code> function.</p><pre><code><code>trade_date = today
start_date = calendar.advance(
    trade_date, cds_settlement_days, ql.Days
)
upfront_date = calendar.advance(
    trade_date, upfront_settlement_days, ql.Days
)

maturity_date = ql.cdsMaturity(
    start_date, test_tenor, ql.DateGeneration.CDS2015
)
print(maturity_date)</code></code></pre><pre><code><code>December 20th, 2028</code></code></pre><p>To get the correct schedule, we need to use this maturity date and to specify the <code>CDS2015</code> convention for date generation:</p><pre><code><code>schedule = ql.MakeSchedule(
    effectiveDate=today,
    terminationDate=maturity_date,
    frequency=ql.Quarterly,
    calendar=calendar,
    rule=ql.DateGeneration.CDS2015,
)</code></code></pre><p>And finally, we can create the CDS itself&#8212;which, as of now, is a bit awkward to do. If we don&#8217;t pass the trade date explicitly, it seems to calculate it in a way that doesn&#8217;t match the one in the helper: something that we&#8217;ll need to investigate. And unfortunately, the trade date comes after a whole bunch of other parameters; C++ doesn&#8217;t provide a syntax to skip them, and even in Python the wrappers for the constructor don&#8217;t support keyword arguments (another thing that we&#8217;ll need to look into). This leads to the call below, which in my opinion has way too many parameters:</p><pre><code><code>cds = ql.CreditDefaultSwap(
    ql.Protection.Buyer,
    10_000,
    test_upfront,
    100 * bps,
    schedule,
    ql.ModifiedFollowing,
    ql.Actual360(),
    True,
    True,
    start_date,
    upfront_date,
    ql.FaceValueClaim(),
    ql.Actual360(),
    True,
    trade_date,
)</code></code></pre><p>As usual, in order to price the CDS, we need to provide an engine. For consistency with the default calculation in the helpers, we&#8217;ll build and set an engine based on the mid-point approximation, in which the default event (if any) is assumed to happen only at exactly half of the coupon life. The engine needs the default-probability curve we just bootstrapped, plus a risk-free curve for discounting and the recovery rate.</p><pre><code><code>probability_handle = ql.RelinkableDefaultProbabilityTermStructureHandle(
    probability_curve
)</code></code></pre><pre><code><code>engine = ql.MidPointCdsEngine(
    probability_handle,
    recovery_rate,
    risk_free_curve,
)

cds.setPricingEngine(engine)</code></code></pre><p>As expected, the quoted CDS is fair (within numerical accuracy):</p><pre><code><code>cds.NPV()</code></code></pre><pre><code><code>3.699818229563334e-13</code></code></pre><p>As a further check, we can also ask for the fair upfront, which returns the quoted value:</p><pre><code><code>cds.fairUpfront()</code></code></pre><pre><code><code>-0.007999999999999964</code></code></pre><h3>Different models</h3><p>As I mentioned, the helpers use the mid-point calculation unless told otherwise. However, it is also possible to choose the ISDA model by passing them an additional optional parameter:</p><pre><code><code>helpers = [
    ql.UpfrontCdsHelper(
        quote,
        cds_spread,
        tenor,
        cds_settlement_days,
        calendar,
        ql.Quarterly,
        ql.ModifiedFollowing,
        ql.DateGeneration.CDS2015,
        ql.Actual360(),
        recovery_rate,
        risk_free_curve,
        upfront_settlement_days,
        model=ql.CreditDefaultSwap.ISDA,
    )
    for tenor, quote in cds_data
]

probability_curve = ql.PiecewiseFlatHazardRate(
    0, calendar, helpers, ql.Actual365Fixed()
)</code></code></pre><pre><code><code>pd.DataFrame(
    [
        (
             date,
             rate,
             probability_curve.defaultProbability(date)
        )
        for date, rate in probability_curve.nodes()
    ],
    columns=[
        "date",
        "hazard rate",
        "default probability"
    ],
).style.format(
    {
        "hazard rate": "{:.2%}",
        "default probability": "{:.2%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EXEx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EXEx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EXEx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png" width="548" height="231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:462,&quot;width&quot;:1096,&quot;resizeWidth&quot;:548,&quot;bytes&quot;:219850,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/172412059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EXEx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 424w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 848w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 1272w, https://substackcdn.com/image/fetch/$s_!EXEx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649a5cbd-808f-4fc3-88cc-0f0b2f33d9c0_1096x462.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The curve is now slightly different, and if we use it to price our sample CDS (which still uses the mid-point engine) its value is no longer zero:</p><pre><code><code>probability_handle.linkTo(probability_curve)</code></code></pre><pre><code><code>cds.NPV()</code></code></pre><pre><code><code>-0.20866157093615761</code></code></pre><p>However, using the <code>IsdaCdsEngine</code> provides a consistent calculation and, again, causes the CDS to be fair:</p><pre><code><code>engine = ql.IsdaCdsEngine(
    probability_handle,
    recovery_rate,
    risk_free_curve,
)</code></code></pre><pre><code><code>cds.setPricingEngine(engine)</code></code></pre><pre><code><code>cds.NPV()</code></code></pre><pre><code><code>-2.8371749394295875e-13</code></code></pre><pre><code><code>cds.fairUpfront()</code></code></pre><pre><code><code>-0.00800000000000003</code></code></pre><h3>Curve interface</h3><p>Usually, you&#8217;ll pass the curve to an engine and let it sort it out. However, if you want to inspect the curve or use it to write some different calculation, the curve provides a few different methods you can use. As we&#8217;ve already seen, it can return the default probability P up to a given date:</p><pre><code><code>probability_curve.defaultProbability(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.021722225513824633</code></code></pre><p>The survival probability is simply 1-P, but there&#8217;s also a convenience method to obtain it directly:</p><pre><code><code>probability_curve.survivalProbability(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.9782777744861754</code></code></pre><p>And if you&#8217;re so inclined, you can also ask for the default density,</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\rho = -\\frac{dS}{dt}&quot;,&quot;id&quot;:&quot;TZAKCJFRKX&quot;}" data-component-name="LatexBlockToDOM"></div><pre><code><code>probability_curve.defaultDensity(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.011182632606748252</code></code></pre><p>&#8230;and the hazard rate,</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;h = \\frac{\\rho}{S}&quot;,&quot;id&quot;:&quot;WTZDDBVCMH&quot;}" data-component-name="LatexBlockToDOM"></div><pre><code><code>probability_curve.hazardRate(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.011430938020258867</code></code></pre><h3>Different kinds of curves</h3><p>As I mentioned, bootstrapped curves are currently based only on piecewise-flat hazard rates. If you have some pre-calculated data, though, you have a few more possibilities. For instance, you can use linear interpolation between survival probabilities at some given nodes:</p><pre><code><code>survival_curve = ql.SurvivalProbabilityCurve(
    [
        today,
        today + ql.Period("1Y"),
        today + ql.Period("2Y"),
        today + ql.Period("5Y"),
        today + ql.Period("10Y"),
    ],
    [1.0, 0.995, 0.985, 0.97, 0.93],
    ql.Actual365Fixed(),
)</code></code></pre><pre><code><code>survival_curve.nodes()</code></code></pre><pre><code><code>((Date(22,9,2025), 1.0),
 (Date(22,9,2026), 0.995),
 (Date(22,9,2027), 0.985),
 (Date(22,9,2030), 0.97),
 (Date(22,9,2035), 0.93))</code></code></pre><p>You can also use <code>DefaultDensityCurve</code> to interpolate linearly between default densities. In C++, you can also choose different interpolations. All these curves present the same interface and therefore can be used in the same way:</p><pre><code><code>survival_curve.defaultProbability(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.011356164383561684</code></code></pre><pre><code><code>survival_curve.hazardRate(
    ql.Date(12, ql.May, 2027)
)</code></code></pre><pre><code><code>0.010114866081944281</code></code></pre><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Cash-flow analysis]]></title><description><![CDATA[Welcome back. This time, I have a proper look at a bit of infrastructure that I&#8217;ve used in multiple notebooks before. How do we extract specific information from the cash flows of an instrument?]]></description><link>https://implementingquantlib.substack.com/p/cash-flow-analysis</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/cash-flow-analysis</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 07 Aug 2025 05:30:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nVBl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. This time, I have a proper look at a bit of infrastructure that I&#8217;ve used in multiple notebooks before. How do we extract specific information from the cash flows of an instrument?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>As usual, this notebook is also included in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><h2>Cash-flow analysis</h2><p>I&#8217;ve already shown in some other notebook that we can extract and display information on the cash flows of, e.g., a bond; but it was somewhat in passing. Let&#8217;s have a better look at that.</p><pre><code><code>import QuantLib as ql
import pandas as pd</code></code></pre><pre><code><code>today = ql.Date(28, ql.July, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><h3>A complicated family</h3><p>The diagram below shows part of the hierarchy starting from the <code>CashFlow</code> class (which in turn inherits from <code>Event</code>, but let&#8217;s ignore it here). There are all kinds of different cash flows there, including some sub-hierarchies like the one with its base in the <code>Coupon</code> class.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nVBl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nVBl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 424w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 848w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 1272w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nVBl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png" width="1456" height="963" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:963,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:305735,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/169877814?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nVBl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 424w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 848w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 1272w, https://substackcdn.com/image/fetch/$s_!nVBl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a449c15-e140-445c-8fce-3a423e4e2301_2500x1653.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Depending on their actual class, they might provide multiple different pieces of information. The only things that they all have in common, though, are those declared in the interface of the base <code>CashFlow</code> class: a payment date and an amount. Other methods don&#8217;t belong there, since they&#8217;re not applicable to all cash flows. A redemption doesn&#8217;t have a rate; a fixed-rate coupon doesn&#8217;t have an observation lag.</p><h3>Lost in translation</h3><p>This poses a problem. Let&#8217;s take a sample fixed-rate bond as an example.</p><pre><code><code>schedule = ql.MakeSchedule(
    effectiveDate=ql.Date(8, ql.April, 2025),
    terminationDate=ql.Date(8, ql.April, 2030),
    frequency=ql.Semiannual,
    calendar=ql.TARGET(),
    convention=ql.Following,
    backwards=True,
)

settlement_days = 3
face_amount = 10_000
coupon_rates = [0.03]

bond = ql.FixedRateBond(
    settlementDays=3,
    faceAmount=10_000,
    schedule=schedule,
    coupons=[0.03],
    paymentDayCounter=ql.Thirty360(
        ql.Thirty360.BondBasis
    ),
)</code></code></pre><p>We can extract its cash flows and use the <code>CashFlow</code> interface to display their dates and amounts:</p><pre><code><code>cashflows = bond.cashflows()

data = []
for cf in cashflows:
    data.append((cf.date(), cf.amount()))

pd.DataFrame(
    data, columns=["date", "amount"]
).style.format(
    {"amount": "{:.2f}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kg2A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kg2A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 424w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 848w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 1272w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kg2A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png" width="304" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:684,&quot;width&quot;:608,&quot;resizeWidth&quot;:304,&quot;bytes&quot;:91853,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/169877814?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kg2A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 424w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 848w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 1272w, https://substackcdn.com/image/fetch/$s_!Kg2A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95d92c1f-3b57-4bc6-9b74-24b7c7e44395_608x684.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can see from the table above that the returned cash flows contain the interest-paying coupons as well as the redemption; the latter is returned a separate cash flow, even though it has the same date as the last coupon.</p><p>The problem surfaces when we try to extract additional information from, say, the first coupon. If we ask it for its rate, it&#8217;s going to complain loudly.</p><pre><code><code>try:
    print(cashflows[0].rate())
except Exception as e:
    print(f"{type(e).__name__}: {e}")</code></code></pre><pre><code><code>AttributeError: 'CashFlow' object has no attribute 'rate'</code></code></pre><p>That&#8217;s because, even though we&#8217;re working in Python here, we&#8217;re wrapping a C++ library and we&#8217;re subject to the constraints of the latter language. To return the set of its cash flows, the <code>Bond</code> class needs to collect them in a vector, which is homogeneous in C++: all elements must belong to a common type, and that would be the base <code>CashFlow</code> class. For polymorphism to work properly, the library also needs to work with pointers (smart pointers, usually). This results in the following return type:</p><pre><code><code>typedef std::vector&lt;ext::shared_ptr&lt;CashFlow&gt; &gt; Leg;</code></code></pre><p>We can confirm it by asking the Python interpreter to visualize the first coupon in the list; the SWIG-generated message contains its type.</p><pre><code><code>cashflows[0]</code></code></pre><pre><code><code>&lt;QuantLib.QuantLib.CashFlow; proxy of &lt;Swig Object of type 'ext::shared_ptr&lt; CashFlow &gt; *' at 0x10d8c1050&gt; &gt;</code></code></pre><p>How can we retrieve additional info, then? In C++, we could use a cast; something like</p><pre><code><code>auto coupon =
    ext::dynamic_pointer_cast&lt;Coupon&gt;(
        cashflows[0]
    );</code></code></pre><p>resulting in a pointer to a <code>Coupon</code> instance (possibly a null one, if the cast didn&#8217;t succeed because the type didn&#8217;t match). From that pointer, we can access the additional interface of the <code>Coupon</code> class. The same goes for any other specific class.</p><p>However, there is no such cast operation in Python, where objects retain the type they&#8217;re created with. What we had to do was add to the wrappers a set of small functions performing the cast, such as</p><pre><code><code>ext::shared_ptr&lt;Coupon&gt;
as_coupon(ext::shared_ptr&lt;CashFlow&gt; cf) {
    return ext::dynamic_pointer_cast&lt;Coupon&gt;(cf);
}</code></code></pre><p>Once exported to the Python module, they give us the possibility to downcast the cash flows and ask for more specific information:</p><pre><code><code>c = ql.as_coupon(cashflows[0])
print(f"{c.rate(): .2%}")</code></code></pre><pre><code><code> 3.00%</code></code></pre><p>Like the underlying cast operation, the function above returns a null pointer if the cast is not possible; for instance, if we try to convert the redemption (the last cash flow in the sequence) into a coupon. In Python, that translates into a <code>None</code>.</p><pre><code><code>print(ql.as_coupon(cashflows[-1]))</code></code></pre><pre><code><code>None</code></code></pre><p>As I mentioned, the QuantLib module provides a number of these functions for different classes:</p><pre><code><code>[
    getattr(ql, x)
    for x in dir(ql)
    if x.startswith("as_")
    and (x.endswith("coupon") or
         x.endswith("cash_flow"))
]</code></code></pre><pre><code><code>[&lt;function QuantLib.QuantLib.as_capped_floored_yoy_inflation_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_cpi_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_fixed_rate_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_floating_rate_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_inflation_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_multiple_resets_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_overnight_indexed_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_sub_periods_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_yoy_inflation_coupon(cf)&gt;,
 &lt;function QuantLib.QuantLib.as_zero_inflation_cash_flow(cf)&gt;]</code></code></pre><h3>Cash-flow analysis, at last</h3><p>Given the functions above, we can collect a lot more information when cycling over cash flows. For instance, here we detect the coupons by trying to cast them and use their specialized interface to extract nominal, rate and accrual period; when the cast fail (that is, for the redemption) we fall back to extracting date and amount. By selecting the correct casting function, we can analyze cash flows from other kinds of bonds and collect the relevant information in each case.</p><pre><code><code>data = []
for cf in cashflows:
    c = ql.as_coupon(cf)
    if c is not None:
        data.append(
            (
                c.date(),
                c.nominal(),
                c.rate(),
                c.accrualPeriod(),
                c.amount(),
            )
        )
    else:
        data.append(
            (
                cf.date(),
                None,
                None,
                None,
                cf.amount(),
            )
        )

pd.DataFrame(
    data, columns=[
        "date",
        "nominal",
        "rate",
        "accrual period",
        "amount",
    ]
).style.format(
    {
        "nominal": "{:.0f}",
        "rate": "{:.1%}",
        "accrual period": "{:.4f}",
        "amount": "{:.2f}",
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jFdu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jFdu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 424w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 848w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 1272w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jFdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png" width="670" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:684,&quot;width&quot;:1340,&quot;resizeWidth&quot;:670,&quot;bytes&quot;:153280,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/169877814?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jFdu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 424w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 848w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 1272w, https://substackcdn.com/image/fetch/$s_!jFdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1519d1fe-f86e-4e3b-ab85-2b94c119ac94_1340x684.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Other functions</h3><p>QuantLib provides a few other functions that act on a sequence of cash flows, rather than a single one. They are grouped as static methods of the <code>CashFlows</code> class; for instance, they include functions to calculate the present value and basis-point sensitivity, given a discount curve.</p><pre><code><code>discount_curve = ql.YieldTermStructureHandle(
    ql.FlatForward(
        0,
        ql.TARGET(),
        0.04,
        ql.Actual360(),
    )
)</code></code></pre><pre><code><code>ql.CashFlows.npv(cashflows, discount_curve, False)</code></code></pre><pre><code><code>9625.543550654686</code></code></pre><pre><code><code>ql.CashFlows.bps(cashflows, discount_curve, False)</code></code></pre><pre><code><code>4.535150508988854</code></code></pre><p>The last <code>False</code> parameter in the two calls above specifies that, if one of the cash flows were paid on the evaluation date, it should not be included.</p><p>Of course, these functions can also be used on a single cash flow by passing them a list with a single element; below, they are used to augment the analysis we performed earlier.</p><pre><code><code>def npv(c):
    return ql.CashFlows.npv(
        [c], discount_curve, False
    )


def bps(c):
    return ql.CashFlows.bps(
        [c], discount_curve, False
    )


data = []
for cf in cashflows:
    c = ql.as_coupon(cf)
    if c is not None:
        data.append(
            (
                c.date(),
                c.nominal(),
                c.rate(),
                c.amount(),
                npv(c),
                bps(c),
            )
        )
    else:
        data.append(
            (
                cf.date(),
                None,
                None,
                cf.amount(),
                npv(cf),
                None,
            )
        )

pd.DataFrame(
    data,
    columns=[
        "date",
        "nominal",
        "rate",
        "amount",
        "NPV",
        "BPS",
    ],
).style.format(
    {
        "nominal": "{:.0f}",
        "rate": "{:.1%}",
        "amount": "{:.2f}",
        "NPV": "{:.2f}",
        "BPS": "{:.2f}",
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VxDR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VxDR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 424w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 848w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 1272w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VxDR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png" width="700" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:684,&quot;width&quot;:1400,&quot;resizeWidth&quot;:700,&quot;bytes&quot;:166258,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/169877814?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VxDR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 424w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 848w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 1272w, https://substackcdn.com/image/fetch/$s_!VxDR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b178b39-8740-4418-981f-67babc1f2cd2_1400x684.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>One last note: the <code>BPS</code> function could have been called also on the redemption, since it has an internal mechanism to detect different kinds of coupons (based on the Acyclic Visitor pattern, if you&#8217;re curious; I explain it in <em>Implementing QuantLib</em>) and would have returned 0.0. Here, I chose to call it only on coupons, resulting in a <code>nan</code> being displayed for the redemption.</p><p>Both choices would have been correct, I guess: I&#8217;m preferring the latter because I&#8217;m seeing BPS as the question &#8220;How much does the present value of the redemption changes if we increase its rate by 1 bp?&#8221; to which my answer would be &#8220;The redemption doesn&#8217;t have a rate to increase&#8221;. It would also be ok to answer &#8220;It doesn&#8217;t change&#8221; instead.</p><p>Thanks for reading. See you next time!</p>]]></content:encoded></item><item><title><![CDATA[More improvements to "A QuantLib Guide"]]></title><description><![CDATA[Welcome back.&#160; This post follows in the footsteps of a similar one from a few months back. I&#8217;m hoping to establish a habit with this: periodic posts pointing out recent improvements in A QuantLib Guide.]]></description><link>https://implementingquantlib.substack.com/p/more-improvements-to-a-quantlib-guide</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/more-improvements-to-a-quantlib-guide</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 10 Jul 2025 05:30:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7b584d84-4c5a-4fff-aa23-92e062028e05_500x714.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. This post follows in the footsteps of <a href="https://implementingquantlib.substack.com/p/some-improvements-to-a-quantlib-guide">a similar one</a> from a few months back. I&#8217;m hoping to establish a habit with this: periodic posts pointing out recent improvements in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>. If you follow this blog, you&#8217;re already up to date with a few brand-new notebooks which I added in the past couple of months and I also published here as posts. Below, I&#8217;ll list some enhancements to existing content that might fly under the radar otherwise.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>And if you find <em>A QuantLib Guide</em> useful and want to support the effort, you can do so by buying <a href="https://leanpub.com/quantlibguide">a PDF version on Leanpub</a>, which also gives you free access to future updates.</p><h1>But first, a PSA</h1><p>Recent versions of the Visual C++ 2022 compiler (at least versions 17.14.2 through 17.14.7) have <a href="https://developercommunity.visualstudio.com/t/Code-optimization-bug-SIMD-std::transf/10912292">a known bug</a> that, unfortunately, affects QuantLib heavily and makes it basically unusable. A fix has been implemented and will be released at some point; I still have had no feedback on whether the fix is included in the latest version, 17.14.8, which was just released as I write this.</p><p>Hopefully, this will be sorted out before I release QuantLib 1.39&#8212;but in the meantime, if you&#8217;re compiling QuantLib on Windows, either use the Visual C++ 2019 toolset (you can do that from VC++ 2022, as well) or downgrade your compiler to an earlier version.</p><h1>And now, the actual changes</h1><ul><li><p>there are a couple of new sections in the notebook on <a href="https://www.quantlibguide.com/Inflation%20indexes%20and%20curves.html">inflation indexes and curves</a>; namely, a section on adding seasonality to inflation curves and another section on perturbing the curves to calculate inflation DV01;</p></li><li><p>the notebooks on <a href="https://www.quantlibguide.com/Cash%20flows%20and%20bonds.html">cash flows and bonds</a>, <a href="https://www.quantlibguide.com/Fitted%20bond%20curves.html">fitted bond curves</a> and <a href="https://www.quantlibguide.com/Vanilla%20bonds.html">vanilla bonds</a> were updated to avoid deprecated method calls that will be removed in the upcoming QuantLib 1.39; more precisely, the calls to the <code>bondYield</code> method no longer take a price as a simple number but as a <code>BondPrice</code> structure containing the price and its type (clean or dirty);</p></li><li><p>finally, this is not actually a change in the guide; but while we&#8217;re on the subject of version 1.39 and deprecated methods, you can also look at <a href="https://github.com/lballabio/QuantLib/pull/2204/files">this pull request</a> to see what other methods and classes were removed in C++ and <a href="https://github.com/lballabio/QuantLib-SWIG/pull/727/files">this other one</a> to see the corresponding deletions in the SWIG interfaces (and therefore in Python, Java and C#).</p></li></ul><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Different approaches to numerical Theta]]></title><description><![CDATA[Hello, dear reader.&#160; In a previous post on calculating numerical Greeks, I mentioned Theta rather quickly. This time, we dive in and look at the different ways to get it.]]></description><link>https://implementingquantlib.substack.com/p/different-approaches-to-numerical</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/different-approaches-to-numerical</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 19 Jun 2025 05:30:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, dear reader. In <a href="https://implementingquantlib.substack.com/p/numerical-greeks">a previous post</a> on calculating numerical Greeks, I mentioned Theta rather quickly. This time, we dive in and look at the different ways to get it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>As usual, this notebook is also published in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><h2>Different approaches to numerical Theta</h2><p>The idea of calculating the Theta numerically seems a simple one; change the time to maturity (which, in the case of QuantLib, means moving the evaluation date) while keeping everything else the same, and reprice the instrument. However, there are different ways of &#8220;keeping everything else the same&#8221;, and they lead to slightly different results. Let&#8217;s look at them.</p><pre><code><code>import QuantLib as ql
import pandas as pd

today = ql.Date(29, ql.October, 2021)
ql.Settings.instance().evaluationDate = today</code></code></pre><h2>Setting a baseline</h2><p>For the purposes of this notebook, we&#8217;ll look at a floating-rate bond on Euribor. For brevity, we&#8217;ll use a single curve for forecasting Euribor fixings and for discounting, and we&#8217;ll boostrap it over a set of interest-rate swaps. The argument I&#8217;ll make would remain the same if we were to use two different curves, as we should, or if we used a more diverse set of instruments for bootstrapping (including, for instance, a few interest-rate futures). Here is the construction of the curve: note that we&#8217;re not specifying its reference date explicitly, but as a number of business days and a calendar (in this case, 0 days, meaning that the reference date of the curve should equal the global evaluation date).</p><pre><code><code>swap_data = [
    ("1Y", 0.137),
    ("2Y", 0.409),
    ("3Y", 0.674),
    ("5Y", 1.004),
    ("8Y", 1.258),
    ("10Y", 1.359),
    ("12Y", 1.420),
    ("15Y", 1.509),
    ("20Y", 1.574),
    ("25Y", 1.586),
    ("30Y", 1.579),
    ("35Y", 1.559),
    ("40Y", 1.514),
    ("45Y", 1.446),
    ("50Y", 1.425),
]

swap_helpers = [
    ql.SwapRateHelper(
        quote / 100.0,
        ql.Period(tenor),
        ql.TARGET(),
        ql.Annual,
        ql.Following,
        ql.Thirty360(ql.Thirty360.BondBasis),
        ql.Euribor6M(),
    )
    for tenor, quote in swap_data
]

swap_curve = ql.PiecewiseKrugerZero(
    0,
    ql.TARGET(),
    swap_helpers,
    ql.Actual360(),
)</code></code></pre><p>Here are the resulting zero rates at the curve nodes, corresponding to the maturities of the input swaps.</p><pre><code><code>todays_rates = pd.DataFrame(
    swap_curve.nodes(), columns=["node", "rate"]
)
todays_rates.style.format({"rate": "{:.4%}"})</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iPPp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iPPp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 424w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 848w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 1272w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iPPp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png" width="316" height="477" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce617157-3a3f-4685-be52-520d878fa138_632x954.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:632,&quot;resizeWidth&quot;:316,&quot;bytes&quot;:142122,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iPPp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 424w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 848w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 1272w, https://substackcdn.com/image/fetch/$s_!iPPp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce617157-3a3f-4685-be52-520d878fa138_632x954.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Next, we build the bond. As I mentioned, we&#8217;re passing the same handle to the index for forecasting and to the engine for discounting; this is just to avoid repeating code in the rest of the notebook. We should use two different curves instead.</p><pre><code><code>curve_handle = \
    ql.RelinkableYieldTermStructureHandle(swap_curve)</code></code></pre><pre><code><code>index = ql.Euribor6M(curve_handle)
index.addFixing(ql.Date(7, ql.May, 2021), 0.01)

schedule = ql.MakeSchedule(
    effectiveDate=ql.Date(11, ql.May, 2021),
    terminationDate=ql.Date(11, ql.May, 2025),
    frequency=ql.Semiannual,
)

bond = ql.FloatingRateBond(
    settlementDays=3,
    faceAmount=10_000,
    schedule=schedule,
    index=index,
    paymentDayCounter=ql.Thirty360(
        ql.Thirty360.BondBasis
    ),
    paymentConvention=ql.Following,
)

bond.setPricingEngine(
    ql.DiscountingBondEngine(curve_handle)
)</code></code></pre><p>The price of the bond will work as the reference against which variations (and thus the Theta) will be measured.</p><pre><code><code>P0 = bond.cleanPrice()
P0</code></code></pre><pre><code><code>99.98927601247395</code></code></pre><h2>Same market quotes</h2><p>One way of &#8220;keeping everything else the same&#8221; is to simply move the evaluation date to the next business day; since the current date is a Friday, we&#8217;ll jump three days to the next Monday.</p><pre><code><code>next_day = ql.TARGET().advance(today, 1, ql.Days)
next_day</code></code></pre><pre><code><code>Date(1,11,2021)</code></code></pre><pre><code><code>ql.Settings.instance().evaluationDate = next_day</code></code></pre><p>Now, if we ask the bond for its price, the bond will reach for the curve and this will cause the latter to re-bootstrap. The reference date will move to the new evaluation date, the maturities of the input swaps will be recalculated accordingly, and the corresponding zero rates will be recalculated so that the curve once again reprices exactly the quoted swap rates&#8212;that have not changed. The bond price will change accordingly, and we can estimate the Theta (the Theta per day, to be exact) as the difference between the new price and the reference one.</p><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>99.98208800779048</code></code></pre><pre><code><code>theta_per_day = bond.cleanPrice() - P0
theta_per_day</code></code></pre><pre><code><code>-0.0071880046834706945</code></code></pre><p>We can also check the nodes of the new curve and compare them with the old ones. The dates have moved as expected, and some of the zero rates have changed.</p><pre><code><code>new_rates = pd.DataFrame(
    swap_curve.nodes(), columns=["node", "rate"]
)
pd.concat(
    [todays_rates, new_rates], axis=1
).style.format(
    {"rate": "{:.4%}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nkYF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nkYF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 424w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 848w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 1272w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nkYF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png" width="634" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:960,&quot;width&quot;:1268,&quot;resizeWidth&quot;:634,&quot;bytes&quot;:243064,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nkYF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 424w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 848w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 1272w, https://substackcdn.com/image/fetch/$s_!nkYF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd05bb34-5ef2-4ea2-b043-e7a1b7af3c4f_1268x960.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The changes are due to the adjustments of the nodes around weekends and holidays; for instance, you can see that the distance between the 2023 and 2024 nodes changed from 368 days to 367, or that the distance between the 2029 and 2031 nodes changed from 731 to 728. This is perfectly fine: is a consequence of having chosed to keep the market quotes constant. However, we might decide to interpret the requirements in a different way.</p><h2>Same zero rates</h2><p>In particular, we might choose to keep the curve the same in the sense of translating it rigidly from the old evaluation date to the new one. This means taking the nodes of the old curve, shifting all the dates by the same three days, and keeping the rates the same; finally, we&#8217;ll pass them to a curve that uses the same interpolation between rates as the bootstrapped one.</p><pre><code><code>shift = next_day - today</code></code></pre><pre><code><code>new_curve = ql.KrugerZeroCurve(
    [d + shift for d in todays_rates["node"]],
    todays_rates["rate"],
    swap_curve.dayCounter(),
)</code></code></pre><p>If we check the new nodes, we can see that the zero rates are the same and so are the distances between nodes. Note that this curve won&#8217;t reprice the input swaps exactly; but again, this is ok: it&#8217;s a consequence of the legitimate choice of interpreting &#8220;keeping everything else the same&#8221; as a rigid translation of the curve.</p><pre><code><code>new_rates = pd.DataFrame(
    new_curve.nodes(), columns=["node", "rate"]
)
pd.concat(
    [todays_rates, new_rates], axis=1
).style.format(
    {"rate": "{:.4%}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vTKn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vTKn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 424w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 848w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 1272w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vTKn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png" width="634" height="479" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:958,&quot;width&quot;:1268,&quot;resizeWidth&quot;:634,&quot;bytes&quot;:267688,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vTKn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 424w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 848w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 1272w, https://substackcdn.com/image/fetch/$s_!vTKn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfcd27f-8f70-4d07-a6a6-7b3b2f06e858_1268x958.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If we link this new curve to the handle we&#8217;re using, we can get a new bond price and thus a new Theta:</p><pre><code><code>curve_handle.linkTo(new_curve)</code></code></pre><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>99.98218981727182</code></code></pre><pre><code><code>theta_per_day = bond.cleanPrice() - P0
theta_per_day</code></code></pre><pre><code><code>-0.007086195202134604</code></code></pre><h2>Same coupon rates</h2><p>There is a third possibility. Both approaches so far will cause the expected coupon rates to change, because the curve will shift while the coupon period remains the same. We can check this by going back to the old evaluation date and curve, extracting the rates by using a small helper function, and then going back to the new ones and doing the same.</p><pre><code><code>ql.Settings.instance().evaluationDate = today
curve_handle.linkTo(swap_curve)</code></code></pre><pre><code><code>def cashflows():
    data = []
    for cf in bond.cashflows():
        c = ql.as_coupon(cf)
        if c is None:
            data.append(
                (cf.date(), None, cf.amount())
            )
        else:
            data.append(
                (c.date(), c.rate(), c.amount())
            )
    return pd.DataFrame(
        data, columns=["date", "rate", "amount"]
    )</code></code></pre><pre><code><code>base_cf = cashflows()
base_cf.style.format(
    {"rate": "{:.4%}", "amount": "{:.4f}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cFlf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cFlf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 424w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 848w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 1272w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cFlf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png" width="460" height="283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:566,&quot;width&quot;:920,&quot;resizeWidth&quot;:460,&quot;bytes&quot;:102733,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cFlf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 424w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 848w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 1272w, https://substackcdn.com/image/fetch/$s_!cFlf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9dd8f48c-866b-43ea-a244-9f6a30c80f42_920x566.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code><code>ql.Settings.instance().evaluationDate = next_day
curve_handle.linkTo(new_curve)</code></code></pre><pre><code><code>new_cf = cashflows()
pd.concat(
    [base_cf, new_cf[["rate", "amount"]]], axis=1
).style.format(
    {"rate": "{:.4%}", "amount": "{:.4f}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1-1H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1-1H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 424w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 848w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 1272w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1-1H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png" width="680" height="378" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:756,&quot;width&quot;:1360,&quot;resizeWidth&quot;:680,&quot;bytes&quot;:161666,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1-1H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 424w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 848w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 1272w, https://substackcdn.com/image/fetch/$s_!1-1H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6caf3fc1-f194-4804-a22a-5e10fee0fee3_1360x756.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As expected, some of the coupon rates change. What if we built a curve with the new reference date but that returns the same forward rates as the old one over a given period? This would be a third interpretation of &#8220;keeping everything else the same&#8221;, and an equally sensible one.</p><p>To do that, we first create a curve which is the same as the old one but with a reference date that doesn&#8217;t move&#8230;</p><pre><code><code>base_curve = new_curve = ql.KrugerZeroCurve(
    todays_rates["node"],
    todays_rates["rate"],
    swap_curve.dayCounter()
)</code></code></pre><p>&#8230;and then we use the <code>ImpliedTermStructure</code> class, which returns a curve with a new reference date but with the same expected rates between two dates.</p><pre><code><code>new_curve = ql.ImpliedTermStructure(
    ql.YieldTermStructureHandle(base_curve),
    next_day
)</code></code></pre><p>We can link this new curve to our handle and check that the coupons remain unchanged:</p><pre><code><code>curve_handle.linkTo(new_curve)</code></code></pre><pre><code><code>new_cf = cashflows()
pd.concat(
    [base_cf, new_cf[["rate", "amount"]]], axis=1
).style.format(
    {"rate": "{:.4%}", "amount": "{:.4f}"}
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N37d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N37d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 424w, https://substackcdn.com/image/fetch/$s_!N37d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 848w, https://substackcdn.com/image/fetch/$s_!N37d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 1272w, https://substackcdn.com/image/fetch/$s_!N37d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N37d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png" width="680" height="378" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:756,&quot;width&quot;:1360,&quot;resizeWidth&quot;:680,&quot;bytes&quot;:161969,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N37d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 424w, https://substackcdn.com/image/fetch/$s_!N37d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 848w, https://substackcdn.com/image/fetch/$s_!N37d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 1272w, https://substackcdn.com/image/fetch/$s_!N37d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ff3fab-10e6-44e2-9472-e8cf035e3b8a_1360x756.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And finally, we can reprice the bond and recalculate a third version of the Theta:</p><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>99.98207313531329</code></code></pre><pre><code><code>theta_per_day = bond.cleanPrice() - P0
theta_per_day</code></code></pre><pre><code><code>-0.007202877160665366</code></code></pre><p>Note that, while the forward rates stay the same, the zero rates corresponding to the coupon dates&#8212;that is, the numbers going into the exp&#8289;(&#8722;rT) formula to give you discount factors&#8212;must change. As the saying goes in Italy, the blanket is short: you can cover your shoulders or your feet, but not both.</p><pre><code><code>data = []
for cf in bond.cashflows():
    if cf.date() &gt; bond.settlementDate():
        data.append(
            (
                cf.date(),
                base_curve.zeroRate(
                    cf.date(),
                    base_curve.dayCounter(),
                    ql.Continuous
                ).rate(),
                new_curve.zeroRate(
                    cf.date(),
                    new_curve.dayCounter(),
                    ql.Continuous
                ).rate(),
            )
        )
pd.DataFrame(
    data,
    columns=["date", "zero rate", "new zero rate"]
).style.format(
    {
        "zero rate": "{:.5%}",
        "new zero rate": "{:.5%}"
    }
)</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jsqQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jsqQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 424w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 848w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 1272w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jsqQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png" width="488" height="282" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:564,&quot;width&quot;:976,&quot;resizeWidth&quot;:488,&quot;bytes&quot;:116213,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/165925036?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jsqQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 424w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 848w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 1272w, https://substackcdn.com/image/fetch/$s_!jsqQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccfb8750-e074-40b0-bd4f-2fce6f641c59_976x564.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Choices, choices</h2><p>So, which one is the right Theta? Well, I&#8217;m not sure there is one; the choice will be yours, probably based on how the Theta will be used in your application or on your desks, or on whether it should be compared to figures coming from elsewhere. As usual, any choice will likely be a trade-off.</p><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Cross-currency curve bootstrapping]]></title><description><![CDATA[Hello again. In a previous post, I sketched how cross-currency swaps can be modeled in QuantLib but assumed that we already had the relevant curves. This time, we build them.]]></description><link>https://implementingquantlib.substack.com/p/cross-currency-curve-bootstrapping</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/cross-currency-curve-bootstrapping</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 22 May 2025 05:30:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sZP0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again. In <a href="https://www.implementingquantlib.com/2023/09/cross-currency-swaps.html">a previous post on my blog</a>, I sketched how cross-currency swaps can be modeled in QuantLib but assumed that we already had the relevant curves. This time, we build them. As usual, this notebook was added to <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>Oh, and forgive me for mentioning from time to time that I&#8217;m available for training, both online and (when possible) on-site: visit my <a href="https://implementingquantlib.substack.com/p/training">Training</a> page for more information.</p><h2>Cross-currency curve bootstrapping</h2><p>Cross-currency curves can be bootstrapped over a set of quotes for the relevant instruments; mostly, cross-currency swaps.</p><pre><code><code>import QuantLib as ql
import pandas as pd

today = ql.Date(27, ql.April, 2025)
ql.Settings.instance().evaluationDate = today</code></code></pre><p>In this case, the bootstrap process also requires a few other interest-rate curves to be available. For the purposes of this notebook, we&#8217;ll use pre-built curves; the data frame below holds made-up zero rates from a few different curves at a number of nodes&#8230;</p><pre><code><code>sample_rates = pd.DataFrame(
    [
        (ql.Date(27, 10, 2021), 2.0229, 1.4510, 1.5131, 2.6893),
        (ql.Date(27, 1, 2022), 2.0645, 1.4416, 1.4943, 2.6919),
        (ql.Date(27, 4, 2022), 2.0414, 1.4520, 1.4764, 2.7306),
        (ql.Date(27, 10, 2022), 2.1630, 1.4344, 1.4969, 2.8107),
        (ql.Date(27, 10, 2023), 2.4638, 1.5635, 1.6532, 3.1354),
        (ql.Date(27, 10, 2024), 2.7187, 1.6500, 1.7510, 3.3850),
        (ql.Date(27, 10, 2025), 2.9055, 1.6959, 1.8410, 3.5358),
        (ql.Date(27, 10, 2026), 3.0673, 1.7660, 1.9268, 3.6438),
        (ql.Date(27, 10, 2027), 3.1615, 1.8310, 1.9669, 3.7127),
        (ql.Date(27, 10, 2028), 3.2325, 1.8959, 2.0346, 3.7374),
        (ql.Date(27, 10, 2029), 3.3049, 1.9930, 2.1263, 3.7842),
        (ql.Date(27, 10, 2030), 3.3584, 2.0272, 2.1832, 3.7844),
        (ql.Date(27, 10, 2031), 3.4023, 2.0744, 2.2599, 3.7927),
        (ql.Date(27, 10, 2036), 3.5657, 2.3011, 2.4406, 3.8902),
        (ql.Date(27, 10, 2041), 3.6191, 2.3882, 2.5331, 3.9043),
        (ql.Date(27, 10, 2046), 3.6199, 2.3762, 2.5225, 3.8677),
        (ql.Date(27, 10, 2051), 3.6208, 2.3401, 2.4926, 3.8204),
    ],
    columns=[
        "date",
        "SOFR",
        "ESTR",
        "Euribor3M",
        "CORRA",
    ],
)</code></code></pre><p>&#8230;and this helper function uses them to create corresponding interest-rate curves. Depending on the context, these curves will be used for forecasting, discounting or both.</p><pre><code><code>def sample_curve(tag):
    curve = ql.ZeroCurve(
        sample_rates["date"],
        sample_rates[tag] / 100,
        ql.Actual365Fixed()
    )
    return ql.YieldTermStructureHandle(curve)</code></code></pre><pre><code><code>sofr_curve = sample_curve("SOFR")
estr_curve = sample_curve("ESTR")
euribor_curve = sample_curve("Euribor3M")
corra_curve = sample_curve("CORRA")</code></code></pre><h2>A common case</h2><p>As for other curves we have already seen, the process consists of creating a set of bootstrap helpers, each modeling one of the quoted instruments, and passing them to the constructor of one of the available piecewise curves.</p><p>We&#8217;ll bootstrap over quoted swaps paying compounded SOFR and receiving 3-months Euribor. Thus, we need to create instances of the corresponding indexes and associate them with their respective forecast curves.</p><pre><code><code>sofr = ql.Sofr(sofr_curve)
euribor = ql.Euribor3M(euribor_curve)</code></code></pre><p>The swaps are quoted as a basis over Euribor: here is a made-up set of such quotes for different maturities.</p><pre><code><code>basis_quotes = [
    (ql.Period(1, ql.Years), -14.5),
    (ql.Period(18, ql.Months), -18.5),
    (ql.Period(2, ql.Years), -20.5),
    (ql.Period(3, ql.Years), -23.75),
    (ql.Period(4, ql.Years), -25.5),
    (ql.Period(5, ql.Years), -26.5),
    (ql.Period(7, ql.Years), -26.75),
    (ql.Period(10, ql.Years), -26.25),
    (ql.Period(15, ql.Years), -24.75),
    (ql.Period(20, ql.Years), -23.25),
    (ql.Period(25, ql.Years), -20.50),
]</code></code></pre><p>Building the curve requires us to specify some familiar conventions&#8230;</p><pre><code><code>fixing_days = 2
calendar = ql.TARGET()
convention = ql.Following
end_of_month = True</code></code></pre><p>&#8230;as well as some less common ones which are specific to cross-currency swap helpers.</p><p>To begin with, we define one of the indexes (in this example, SOFR) as the &#8220;base&#8221; index; the other conventions are specified depending on this choice. In this case, we want to bootstrap a curve for discounting EUR cash flows when the collateral is in USD. This means we already know the collateral curve used for discounting USD cash flows, namely, the SOFR curve. The other parameters we&#8217;re passing to the helpers specify that the currency of the collateral is the one that corresponds to the base index, that the quoted basis is not applied to the base index but to the other, and that the base-index leg is not the one that resets its notionals.</p><pre><code><code>base_index = sofr
other_index = euribor
collateral_curve = sofr_curve
base_is_collateral = True
basis_on_base = False
base_resets = False
frequency = ql.Quarterly

helpers = [
    ql.MtMCrossCurrencyBasisSwapRateHelper(
        ql.makeQuoteHandle(basis / 10000),
        tenor,
        fixing_days,
        calendar,
        convention,
        end_of_month,
        base_index,
        other_index,
        collateral_curve,
        base_is_collateral,
        basis_on_base,
        base_resets,
        frequency,
    )
    for tenor, basis in basis_quotes
]</code></code></pre><p>Now we can finally create the curve and inspect its nodes:</p><pre><code><code>discount_curve = ql.PiecewiseLinearZero(
    today, helpers, ql.Actual365Fixed()
)</code></code></pre><pre><code><code>pd.DataFrame(
    discount_curve.nodes(), columns=["date", "rate"]
).style.format({"rate": "{:.4%}"})</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sZP0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sZP0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sZP0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png" width="372" height="366" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:732,&quot;width&quot;:744,&quot;resizeWidth&quot;:372,&quot;bytes&quot;:112941,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/163943229?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sZP0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!sZP0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19b7b834-a6b9-4788-8c41-f41bbd39937c_744x732.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>What? No FX rate?</h3><p>You might have noticed that the current EUR/USD rate was not among the inputs of the calculation. Shouldn&#8217;t it matter?</p><p>Well, yes, if you&#8217;re calculating the value of the swaps. But during the bootstrap, what we&#8217;re calculating and matching for each swap is the fair basis between the floating rates. If you work out the math, you&#8217;ll see that the current FX rate cancels out.</p><h3>An alternate view</h3><p>The same swap can be seen from the other side if we swap the role of the indexes and choose Euribor as the base index. Note that the collateral remains in USD, and therefore the collateral curve remains the same and the boolean parameters (referring now to Euribor) are inverted; the base index is not in the collateral currency, the basis is on the base index, and the base-index leg resets.</p><pre><code><code>base_index = euribor
other_index = sofr
collateral_curve = sofr_curve
base_is_collateral = False
basis_on_base = True
base_resets = True
frequency = ql.Quarterly

helpers = [
    ql.MtMCrossCurrencyBasisSwapRateHelper(
        ql.makeQuoteHandle(basis / 10000),
        tenor,
        fixing_days,
        calendar,
        convention,
        end_of_month,
        base_index,
        other_index,
        collateral_curve,
        base_is_collateral,
        basis_on_base,
        base_resets,
        frequency,
    )
    for tenor, basis in basis_quotes
]</code></code></pre><p>The resulting curve, as expected, is the same.</p><pre><code><code>discount_curve = ql.PiecewiseLinearZero(
    today, helpers, ql.Actual365Fixed()
)</code></code></pre><pre><code><code>pd.DataFrame(
    discount_curve.nodes(), columns=["date", "rate"]
).style.format({"rate": "{:.4%}"})</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EADQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EADQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EADQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png" width="372" height="366" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:732,&quot;width&quot;:744,&quot;resizeWidth&quot;:372,&quot;bytes&quot;:112919,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/163943229?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EADQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!EADQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33926ac-2e01-48ad-984b-e0ec266d53c7_744x732.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>A slightly more complex case</h2><p>The previous case involved two known curves (namely, the forecast curves for SOFR and 3-months Euribor) as well as the curve being bootstrapped. The SOFR curve was also used for discounting USD cash flows.</p><p>Let&#8217;s now say that we want to bootstrap over EUR/CAD cross-currency swaps paying 3-months Euribor vs CORRA, with the collateral being in EUR. Besides the curve being bootstrapped, this is going to require three different known curves: the forecast curves for Euribor and CORRA, and the ESTR curve used for discounting EUR cash flows.</p><pre><code><code>euribor = ql.Euribor3M(euribor_curve)
corra = ql.Corra(corra_curve)</code></code></pre><p>We&#8217;ll choose Euribor as the base index and set the other parameters accordingly; as I mentioned, the collateral curve will be the ESTR curve. For brevity I&#8217;ll use the same set of basis quotes as before, and I&#8217;ll assume they&#8217;re added on Tibor.</p><pre><code><code>base_index = euribor
other_index = corra
collateral_curve = estr_curve
base_is_collateral = True
basis_on_base = False
base_resets = False
frequency = ql.Quarterly

helpers = [
    ql.MtMCrossCurrencyBasisSwapRateHelper(
        ql.makeQuoteHandle(basis / 10000),
        tenor,
        fixing_days,
        calendar,
        convention,
        end_of_month,
        base_index,
        other_index,
        collateral_curve,
        base_is_collateral,
        basis_on_base,
        base_resets,
        frequency,
    )
    for tenor, basis in basis_quotes
]</code></code></pre><p>Here is the resulting curve:</p><pre><code><code>discount_curve = ql.PiecewiseLinearZero(
    today, helpers, ql.Actual365Fixed()
)</code></code></pre><pre><code><code>pd.DataFrame(
    discount_curve.nodes(), columns=["date", "rate"]
).style.format({"rate": "{:.4%}"})</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V7Hk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V7Hk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V7Hk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png" width="372" height="366" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:732,&quot;width&quot;:744,&quot;resizeWidth&quot;:372,&quot;bytes&quot;:114669,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://implementingquantlib.substack.com/i/163943229?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!V7Hk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 424w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 848w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 1272w, https://substackcdn.com/image/fetch/$s_!V7Hk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f961d6-0f3e-4525-bed1-f49d9deb8dd9_744x732.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[The global evaluation date]]></title><description><![CDATA[Welcome back.&#160; In this post, an initial look at a feature I always used in my examples but never really explained: the global evaluation date.]]></description><link>https://implementingquantlib.substack.com/p/the-global-evaluation-date</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/the-global-evaluation-date</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Wed, 30 Apr 2025 05:30:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. In this post, an initial look at a feature I always used in my examples but never really explained: the global evaluation date. It&#8217;s short as notebooks go, but it has some discussion at the end that tries to give some more context. (Pun not intended. What pun? You&#8217;ll see.) And of course, it&#8217;s also been added to <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>The global evaluation date</h2><p>If you already used QuantLib, chances are that you know about the global evaluation date. By default, it is set to the current date, which is why you might have ignored it if you only performed calculations as of today; but as soon as you try to calculate figures at some past date, you&#8217;ll have to set it properly.</p><h2>A simple example</h2><pre><code><code>import QuantLib as ql</code></code></pre><p>Let&#8217;s say you have a bond which has already matured; for instance, this one.</p><pre><code><code>bond = ql.ZeroCouponBond(
    settlementDays=3,
    calendar=ql.TARGET(),
    faceAmount=10_000,
    maturityDate=ql.Date(8, ql.February, 2025),
)</code></code></pre><p>As I write this, it&#8217;s April 2025; but let&#8217;s say you&#8217;re checking some end-of-year figures, and want to reprice the bond as of that date:</p><pre><code><code>as_of_date = ql.Date(31, ql.December, 2024)</code></code></pre><p>Next, you set up a discount curve with the correct reference date and use it to price the bond by passing it to the usual engine.</p><pre><code><code>discount_curve = ql.YieldTermStructureHandle(
    ql.FlatForward(as_of_date, 0.02, ql.Actual360())
)
engine = ql.DiscountingBondEngine(discount_curve)
bond.setPricingEngine(engine)</code></code></pre><p>You already know where this is going: you ask for the price, and you get a null one.</p><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>0.0</code></code></pre><p>This is because the bond methods don&#8217;t rely on the reference date of the curve (which doesn&#8217;t always equal the evaluation date) but on the global evaluation date. If we ask the bond, it&#8217;s going to tell us that it has matured.</p><pre><code><code>bond.isExpired()</code></code></pre><pre><code><code>True</code></code></pre><p>The correct way to get its end-of-year price, of course, is to set the evaluation date:</p><pre><code><code>ql.Settings.instance().evaluationDate = as_of_date</code></code></pre><p>At which point the calculations work as expected.</p><pre><code><code>bond.cleanPrice()</code></code></pre><pre><code><code>99.80574447629698</code></code></pre><p>In C++, which uses <code>::</code> for calling class methods and doesn&#8217;t have properties, the syntax to use is</p><pre><code><code>Settings::instance().evaluationDate() = as_of_date;</code></code></pre><h3>Other possible points of failure</h3><p>In a real-world case, you might have found that something was amiss even before asking the bond for its price. If you had tried to create the discount curve by bootstrapping it over the end-of-year quoted rates, you&#8217;d have probably failed to do so; the bootstrap algorithm would have considered the 5-years swap rate as starting from today, not from the reference date passed to the curve. Why this inconsistence inside the curve itself? Because, as I mentioned, the reference date of the curve might not equal the evaluation date (I&#8217;ve seen desks where the reference date of the swap curve would be the spot date, two business days from the evaluation date) and thus the algorithm can&#8217;t rely on it.</p><h2>Term structures</h2><p>Term structures can be set up so that their reference date moves with the global evaluation date; I talk about this in more detail in <a href="https://www.quantlibguide.com/Term%20structures.html">another notebook</a>.</p><h2>Fixings</h2><p>Another case where the evaluation date plays a role is determining whether the fixing of an interest-rate index should be calculated. Let&#8217;s create an index and give it a flat 1% forecast curve:</p><pre><code><code>forecast_curve = ql.YieldTermStructureHandle(
    ql.FlatForward(
        0, ql.NullCalendar(), 0.01, ql.Actual360()
    )
)</code></code></pre><pre><code><code>index = ql.Euribor3M(forecast_curve)</code></code></pre><p>The evaluation date is still at the end of 2024. If we try to ask the index for a fixing at a past date, it&#8217;s going to gently but firmly rebuke us:</p><pre><code><code>try:
    index.fixing(ql.Date(15, ql.February, 2021))
except Exception as e:
    print(f"{type(e).__name__}: {e}")</code></code></pre><pre><code><code>RuntimeError: Missing Euribor3M Actual/360 fixing for February 15th, 2021</code></code></pre><p>That&#8217;s because the fixing is in the past with respect to the evaluation date, and therefore it can&#8217;t be forecast off the curve: it must have been stored instead (here I&#8217;ll use a bogus 2% value). Once we do this, the index returns the stored value.</p><pre><code><code>index.addFixing(ql.Date(15, ql.February, 2021), 0.02)</code></code></pre><pre><code><code>index.fixing(ql.Date(15, ql.February, 2021))</code></code></pre><pre><code><code>0.02</code></code></pre><p>If I move the evaluation date before that fixing date, though, the behavior of the index changes. Now the fixing date is in the future with respect to the evaluation date, and therefore the fixing is forecast from the 1% curve we set previously, even though the stored value is still there:</p><pre><code><code>ql.Settings.instance().evaluationDate = ql.Date(
    1, ql.February, 2021
)</code></code></pre><pre><code><code>index.fixing(ql.Date(15, ql.February, 2021))</code></code></pre><pre><code><code>0.010012371303880592</code></code></pre><h2>Why a global evaluation date anyway?</h2><p>Yes, sensible question. In this age of multithreading, it feels weird to have a feature that prevents us from perform two parallel calculations as of two different evaluation dates.</p><p>The historical reason is that QuantLib was started in 2000, and multi-core computers were not yet the norm at the time. This wouldn&#8217;t prevent us to redesign it, <em>per se</em>; but as I wrote in <em>Implementing QuantLib</em>, we don&#8217;t really have a workable alternative.</p><p>The obvious idea (that is, the idea that quite a few smart people had when we talked about it) was to use some kind of context object that should replace the global settings. But how would one select a context for any given calculation?</p><p>It would be appealing to add a <code>setContext</code> method to the <code>Instrument</code> class, and to arrange things so that during calculation the instrument propagates the context to its engine and in turn to any term structures that need it. However, this can&#8217;t be implemented easily.</p><p>To begin with, the instrument and its engine are not always directly aware of all the term structures that are involved in the calculation. For instance, a swap contains a number of coupons, any of which might or might not reference a forecast curve. We&#8217;re not going to reach them unless we add the relevant machinery to all the classes involved. I&#8217;m not sure that we want to set a context to a coupon.</p><p>Another problem is that there are some kinds of data sharing that just feel natural in the library. For instance, all instruments based on the Euribor index can share the same index instance and depend on a single forecast curve. Trying to price two such instruments in two different contexts in parallel wouldn&#8217;t work. Using contexts would force us to duplicate the index and curve objects, at which point it would be simpler to use a thread-local evaluation date (see below).</p><p>Also, I&#8217;m skipping over the scenario in which the context is threaded through the various method calls during calculations instead of being set. It would lead to method calls like</p><pre><code><code>termStructure-&gt;discount(t, context);</code></code></pre><p>which would completely break caching, would cause discomfort to all parties involved, and if we wanted stuff like this we&#8217;d write in Haskell.</p><h3>Not just globals, unfortunately</h3><p>Truth be told, sharing the same curve between instruments is ok in a single-threaded calculation, but would be dangerous when multi-threading, even if the evaluation date or the context were the same. A number of calculations in the library are lazy; that is, they&#8217;re performed when required but not earlier. For instance, an interest-rate curve would not be bootstrapped when created, but only when actually asked for rates or discount factors. This means that passing the same curve to two instruments and asking them for their values in parallel might trigger the bootstrap of the curve on two different threads. It wouldn&#8217;t end well.</p><h2>Thread-local evaluation date</h2><p>The library has a setting that can cause the evaluation date (and other globals) to be thread-local. Unfortunately, it requires recompiling, which rules out a simple <code>pip install QuantLib</code> for all you Pythonistas.</p><p>One still needs to be careful, though; you better not share of objects between threads, since the bootstrap example above still applies. And it&#8217;s somewhat inconvenient, as well; for instance, index fixings would also be saved in a thread-local object and would have to be reloaded in each thread.</p><h2>Living in the present</h2><p>What if you&#8217;re doing calculations as of today&#8212;I mean, the actual current date? Should you still set the evaluation date?</p><p>All in all, yes, I&#8217;d advise doing that. There&#8217;s no harm in it, whereas leaving the evaluation date unset has two (possibly quite minor) disadvantages.</p><p>The first: every time the calculations will ask for the evaluation date, it will be retrieved by calling some system or library facility; that&#8217;s slower than returning the stored date that you set. Granted, this might be negligible compared to the time taken by the rest of the calculation, but it&#8217;s still a waste that can be avoided with no effort.</p><p>The second, not very probable but possible: if your calculations ever run beyond midnight, the evaluation date will change and your objects won&#8217;t receive any notifications. This would leave them in an inconsistent state.</p><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Some improvements to "A QuantLib Guide"]]></title><description><![CDATA[Hello. Nice to see you again.Not a lot of content in the post proper this time&#8212;but that&#8217;s because this post points to a number of improvements in A QuantLib Guide, where the actual content is.]]></description><link>https://implementingquantlib.substack.com/p/some-improvements-to-a-quantlib-guide</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/some-improvements-to-a-quantlib-guide</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 03 Apr 2025 05:31:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello. Nice to see you again. Not a lot of content in the post proper this time&#8212;but that&#8217;s because this post points to a number of improvements in <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>, where the actual content is.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>Of course, if you want to receive this kind of updates, subscribing to this Substack is a good idea. And if you find <em>A QuantLib Guide</em> useful and want to support the effort, you can do so by buying <a href="https://leanpub.com/quantlibguide">a PDF version on Leanpub</a>. Don&#8217;t worry about its being a work in progress; you&#8217;ll have free access to new versions any time the book is updated.</p><p>And now, as Leporello said, <em>il catalogo &#232; questo</em>:</p><ul><li><p>there&#8217;s a new short notebook on <a href="https://www.quantlibguide.com/Kinds%20of%20interest-rate%20term%20structures.html">the common interface of interest-rate term structures</a>, showing a few ways you can use them regardless of the way they&#8217;re modeled;</p></li><li><p>the notebook on <a href="https://www.quantlibguide.com/Curve%20bootstrapping.html">curve bootstrapping</a> has a new bit on checking the correctness of the bootstrap, as well as a new section on adding jumps;</p></li><li><p>the notebook on <a href="https://www.quantlibguide.com/Vanilla%20bonds.html">vanilla bonds</a> has more details on the difference between the <code>dirtyPrice</code> and <code>NPV</code> methods;</p></li><li><p>the notebook on <a href="https://www.quantlibguide.com/Different%20kinds%20of%20swaps.html">different kinds of swaps</a> now also shows the <code>MakeOIS</code> utility, as well as the new optional parameters recently added to OIS such as lookback days and lockout days;</p></li><li><p>and finally, the notebook on <a href="https://www.quantlibguide.com/Numerical%20Greeks.html">numerical Greeks</a> now has a comparison with analytic Greeks, in a case where they&#8217;re available.</p></li></ul><p>I&#8217;ll leave you to it now. Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[The QuantLib ecosystem]]></title><description><![CDATA[Hello again!&#160; Today&#8217;s post was originally published in the November 2024 issue of Wilmott Magazine.&#160; In this article, I&#8217;ll widen my net and list a number of satellite projects and ports in other languages which spawned from QuantLib.]]></description><link>https://implementingquantlib.substack.com/p/the-quantlib-ecosystem</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/the-quantlib-ecosystem</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 13 Mar 2025 06:30:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again! Today&#8217;s post was originally published in the November 2024 issue of <em>Wilmott Magazine</em>. So far, it&#8217;s the last of my columns there. You can find the full list of articles <a href="https://www.implementingquantlib.com/p/tutorials.html">on the Tutorial page of my site</a>, together with the corresponding source code when available.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>The QuantLib ecosystem</h2><p>Welcome back! In my previous articles, I tried to look at QuantLib from a number of different perspectives such as features, usability, design, or extensibility.</p><p>In this article, I&#8217;ll widen my net and list a number of satellite projects and ports in other languages which spawned from QuantLib (mostly through no effort of ours, I must say.) Let&#8217;s have a look.</p><h2>Different languages, same library.</h2><p>The same C++ library we know and love can also be called from other languages through bindings. We officially provide some of those: others are independent efforts.</p><h3>Official bindings</h3><p>If you happened to read my article from <a href="https://implementingquantlib.substack.com/p/using-quantlib-interactively">the September 2023 issue</a> of Wilmott, or if you bought the <em><a href="http://localhost:4000/p/the-book.html">QuantLib Python Cookbook</a></em>, you already know that QuantLib can be used from Python and interact with Jupyter and the rest of the language&#8217;s impressive ecosystem. If you&#8217;re a Python user and you don&#8217;t need to modify the library, you don&#8217;t even need to compile it; it&#8217;s already available on PyPI.</p><p>What if you prefer another language? And by &#8220;prefer&#8221;, of course, I mean that the rest of your system is implemented in another language, or that the expertise of your development team leans that way. Well, <a href="https://swig.org/">SWIG</a> (the same program we use to semi-automatically generate the Python bindings) can also be used for other languages; currently, we support two enterprise languages, C# and Java, and a more niche one, R. As for Python, a precompiled C# binary is available on NuGet; you&#8217;re welcome to try it and report any problems on the QuantLib mailing list.</p><p>If you want to use QuantLib from Java, I&#8217;m afraid you&#8217;ll have to compile it; and you&#8217;ll have to pass a compiler flag that enables a thread-safe implementation of the Observer pattern, because (as I mentioned in passing in <a href="https://implementingquantlib.substack.com/p/the-observer-pattern-in-quantlib">the November 2023 issue</a>; see how everything comes together?) the garbage collector might otherwise interfere.</p><p>Bindings for an older version of QuantLib are also available for the most used functional language of all&#8212;<a href="https://www.quantlib.org/quantlibxl/">Microsoft Excel</a>. Their maintainer (hi, Eric) did an excellent job making it possible to use an object-oriented library from a spreadsheet. Unfortunately, the project is currently dormant and could use some help because, well, life doesn&#8217;t always give one a chance to maintain an open-source project. For the record, I&#8217;m forever grateful to my employers for giving me some company time to do it.</p><h3>Unofficial alternatives</h3><p>The advantage of using SWIG to generate bindings is that the same set of input files can be used for different languages. The disadvantage is that using the same set of input files gives you little leeway to tailor the interfaces for idiomatic language use.</p><p>A few projects address this issue by providing alternative idiomatic bindings for a single language each.</p><p>First and eldest, <a href="https://dirk.eddelbuettel.com/code/rquantlib.html">RQuantLib</a> provides idiomatic bindings for R and is available via CRAN. It also provides a nice example of serendipity, because RQuantLib spawned a much more relevant package for the R ecosystem: <a href="https://dirk.eddelbuettel.com/code/rcpp.html">Rcpp</a>, which can be used to integrate C++ code into R and is currently used by over 2500 packages on CRAN.</p><p>Also for R and from the same maintainer (hi, Dirk), the lighter-weight <a href="https://dirk.eddelbuettel.com/code/qlcal-r.html">qlcal</a> project provides just the calendaring functionality of QuantLib. You might remember that I already mentioned it in <a href="https://implementingquantlib.substack.com/p/holidays-in-quantlib">the January 2024 issue</a>. Do you see a pattern here? Pick up your back issues of <em>Wilmott</em> or hit <a href="https://www.implementingquantlib.com/p/tutorials.html">the Tutorials page on my site</a>.</p><p>In the Python camp, <a href="https://github.com/enthought/pyql">PyQL</a> provides more idiomatic bindings by using Cython, a tool specialized for the language. This makes it easier to support niceties like keyword arguments and <code>snake_case</code> method names (as opposed to the <code>camelCase</code> names we have in the C++ library.) Like RQuantLib, PyQL provides fewer classes and methods than the official bindings are able to support. As a general rule, all the open-source projects I&#8217;m mentioning in this article will be glad to have your help if you&#8217;re so inclined.</p><h2>Different languages, different library</h2><p>Other projects tried to avoid the impedance mismatch between C++ and their target language by porting the library rather than wrapping it; that is, by rewriting it in the target language. A few of them are listed on <a href="https://www.quantlib.org/extensions.shtml">the QuantLib site</a>. By using the target language directly, they are able to integrate better with it and to use its existing patterns and facilities.</p><p>Not surprisingly, though, it&#8217;s difficult for them to keep up with the sheer quantity of code in the original QuantLib. One port that seems to have reached critical mass and to be still developed is the C# port, called <a href="https://github.com/amaggiulli/qlnet">QLNet</a>; the Java port <a href="https://github.com/frgomes/jquantlib">JQuantLib</a> also has decent coverage but seems to have been dormant for a while.</p><h2>Same library, different types</h2><p>A long time ago, in a rare display of foresight, we hid native C++ types behind a few typedefs; for instance, we added <code>Real</code> as an alias to <code>double</code>, or <code>Integer</code> as an alias to <code>int</code>. These typedefs give us the possibility to redefine the types we&#8217;re using in the whole code with a single localized change.</p><p>Our thinking at the time was that one might have wanted to replace the default <code>double</code> with, say, <code>long double</code> for added precision, or <code>float</code> for higher speed. As often happens, we were wrong. But it all turned out fine in the end, because in more recent years this provided the required hook for adding Adjoint Algorithmic Differentiation (AAD) to the library.</p><p>The idea (which I&#8217;m oversimplifying here) is that by replacing the built-in double type with an instrumented class that overloads arithmetic operators, it&#8217;s possible to keep track of a given calculation and provide simultaneously the result and its derivatives with respect to all the input variables, without changing the existing code and with a cost in performance which is a lot less than the cost of getting the same derivatives by bumping the inputs and redoing the calculation.</p><p>The first implementation of the idea was provided a few years ago by <a href="https://github.com/compatibl/QuantLibAdjoint">CompatibL</a>, and now seems dormant. Nowadays, there are two very active implementations by <a href="https://matlogica.com/practical-AAD-QuantLib-XVA-demo.php">Matlogica</a> and <a href="https://auto-differentiation.github.io/quantlib/">XAD</a> (in strictly alphabetical order), both of which provide other goodies besides AAD.</p><h2>A lot more of the same</h2><p>Finally, another project builds on QuantLib and adds more complex functionality. The <a href="https://www.opensourcerisk.org/">Open-Source Risk Engine</a>, or ORE for short, is a number of things. It contains a recent version of QuantLib, extends it with further instruments and models, and wraps it in an application through which you can provide your input market data and portfolio specifications and perform tasks such as exposure calculation and XVA.</p><p>A note: the people behind ORE would be completely cool about taking some of their additional instruments and adding them to QuantLib, but like all of us they&#8217;re short on time to do it themselves. Again, help would be appreciated.</p><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Coupons with multiple resets, revisited]]></title><description><![CDATA[Welcome back. My first post on this argument didn&#8217;t go as planned. Let&#8217;s revisit it, now that release 1.37 fixed the problem.]]></description><link>https://implementingquantlib.substack.com/p/coupons-with-multiple-resets-revisited</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/coupons-with-multiple-resets-revisited</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 20 Feb 2025 06:30:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sez3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. <a href="https://implementingquantlib.substack.com/p/coupons-with-multiple-resets">My first post on this argument</a> didn&#8217;t go as planned and showed that we needed a change in the library. Let&#8217;s revisit it, now that release 1.37 fixed the problem. This notebook, like other notebooks I published in the past on this blog, is now part of <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>&#8212;give it a try.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Coupons with multiple resets</h2><p>(Originally based on a question on the QuantLib mailing list; thanks, Jack.)</p><p>Some bonds and swaps have coupons whose reset frequency is different than the payment frequency; for instance, a coupon might pay quarterly but its rate would be the combination of three 1-month fixings occurring during its life.</p><p>There is no specific instrument in QuantLib to instantiate such a bond; but as we&#8217;ve seen in other cases, it&#8217;s possible to use the constructor of the base <code>Bond</code> or <code>Swap</code> class once we have built the coupons. We can do this by means of the <code>MultipleResetsCoupon</code> class and the <code>MultipleResetsLeg</code> utility (a class in C++ or a function in Python.)</p><p>Let&#8217;s take some initialization out of the way.</p><pre><code><code>import QuantLib as ql
import pandas as pd
import numpy as np

today = ql.Date(9, ql.September, 2024)
ql.Settings.instance().evaluationDate = today</code></code></pre><h2>The underlying index</h2><p>As an example, I&#8217;ll use a an Euribor1M index. To avoid constant fixings, I&#8217;ll use a mock curve with zero rates increasing linearly over the next 20 years.</p><pre><code><code>forecast_curve = ql.ZeroCurve(
    [today, today + ql.Period(20, ql.Years)],
    [0.035, 0.06],
    ql.Actual365Fixed(),
)
forecast_handle = ql.YieldTermStructureHandle(
    forecast_curve
)</code></code></pre><pre><code><code>index = ql.Euribor(
    ql.Period(1, ql.Months), forecast_handle
)</code></code></pre><p>And as usual, depending on the coupon schedule we might have to add some past fixings to the index.</p><pre><code><code>index.addFixing(
    ql.Date(30, ql.July, 2024), 0.03611
)
index.addFixing(
    ql.Date(29, ql.August, 2024), 0.03602
)</code></code></pre><h2>Multiple-resets coupons</h2><p>As I mentioned, we can use the <code>MultipleResetsLeg</code> function to build a sequence of coupons for a bond; and as usual, we need to build the corresponding schedule first. Other parameters may include a payment lag (2 days in this example), the method used to combine the underlying index fixings (compounding or averaging, defaulting to the former), and other not shown here such as ex-coupon information, a multiplier or an additional spread.</p><p>However, <code>MultipleResetsLeg</code> differs in one important respect from other similar functions such as <code>FixedRateLeg</code> or <code>IborLeg</code>. The schedule we need to pass is not the schedule of the coupons, but the full schedule of the underlying multiple fixings over the whole leg. In order to derive the coupon schedule, we&#8217;ll also pass the number of underlying fixings periods per coupon. In this example, I&#8217;ll specify 3 monthly fixings per coupon, resulting in quarterly payments.</p><pre><code><code>reset_schedule = ql.MakeSchedule(
    effectiveDate=ql.Date(1, ql.August, 2024),
    terminationDate=ql.Date(1, ql.August, 2025),
    frequency=ql.Monthly,
    calendar=ql.TARGET(),
)

nominal = 100.0
resets_per_coupon = 3
coupons = ql.MultipleResetsLeg(
    reset_schedule,
    index,
    resets_per_coupon,
    [nominal],
    paymentLag=2,
    averagingMethod=ql.RateAveraging.Compound,
)

settlement_days = 3
calendar = ql.TARGET()
bond = ql.Bond(
    settlement_days,
    calendar,
    reset_schedule[0],
    coupons,
)</code></code></pre><p>First of all, I&#8217;ll use a constant-rate discount curve to check that everything works:</p><pre><code><code>discount_curve = ql.FlatForward(
    today, 0.03, ql.Actual365Fixed()
)
discount_handle = ql.YieldTermStructureHandle(
    discount_curve
)

bond.setPricingEngine(
    ql.DiscountingBondEngine(discount_handle)
)
bond.cleanPrice()</code></code></pre><pre><code><code>100.51555336316565</code></code></pre><p>And now, let&#8217;s look under the hood.</p><h2>Cash-flow analysis</h2><p>As in previous notebooks, we can examine the coupons we created to retrieve various bits of information. Calling <code>bond.cashflows()</code> returns the coupons we created, plus the redemption inferred by the <code>Bond</code> constructor. Since we&#8217;re calling from the underlying C++ library, the coupons are returned as instances of the base <code>CashFlow</code> class and need to go through a <code>dynamic_pointer_cast</code> to give them their original type and access the corresponding methods. In Python, the C++ cast is exported as the <code>as_sub_periods_coupon</code> function; if the cast fails (as for the redemption) it returns <code>None</code>.</p><p>Once we have the coupon, we can ask (for instance) for its multiple fixings dates or its final rate besides its payment date and amount. For the redemption, payment date and amount are all we can get.</p><p>Pardon all the <code>None</code>s I&#8217;m adding to the info and my formatting code; it&#8217;s to get a decent-looking table in the end.</p><pre><code><code>def coupon_info(c):
    info = []
    for d in c.fixingDates():
        info.append(
            (d, index.fixing(d), None, None, None)
        )
    info.append(
        (None, None, c.rate(), c.date(), c.amount())
    )
    return info</code></code></pre><pre><code><code>def redemption_info(cf):
    return [
        (None, None, None, cf.date(), cf.amount())
    ]</code></code></pre><pre><code><code>data = []
for cf in bond.cashflows():
    c = ql.as_multiple_resets_coupon(cf)
    if c is not None:
        data += coupon_info(c)
    else:
        data += redemption_info(cf)</code></code></pre><pre><code><code>df = pd.DataFrame(
    data,
    columns=[
        "fixing date",
        "fixing",
        "coupon rate",
        "payment date",
        "amount",
    ],
)
df.style.format(
    {
        "fixing date":
          lambda d:
            "" if d is None else d.ISO(),
        "fixing":
          lambda f:
            "" if np.isnan(f) else f"{f:.2%}",
        "coupon rate":
          lambda r:
            "" if np.isnan(r) else f"{r:.2%}",
        "payment date":
          lambda d:
            d.ISO() if d is not None else "",
        "amount":
          lambda x:
            "" if np.isnan(x) else f"{x:.4}",
    }
).hide(axis="index")</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sez3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sez3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 424w, https://substackcdn.com/image/fetch/$s_!sez3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 848w, https://substackcdn.com/image/fetch/$s_!sez3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 1272w, https://substackcdn.com/image/fetch/$s_!sez3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sez3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png" width="644" height="509" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1018,&quot;width&quot;:1288,&quot;resizeWidth&quot;:644,&quot;bytes&quot;:169449,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sez3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 424w, https://substackcdn.com/image/fetch/$s_!sez3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 848w, https://substackcdn.com/image/fetch/$s_!sez3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 1272w, https://substackcdn.com/image/fetch/$s_!sez3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F249c31bb-d1e7-4f05-80b1-7743b458c440_1288x1018.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And by the way, if you see that a <code>SubPeriodsLeg</code> function is also available (it&#8217;s deprecated, and it might be removed by the time you read this), don&#8217;t use it. It&#8217;s an older version of the above, and sometimes it calculated the wrong schedule.</p><h3>What did we get?</h3><p>The table above shows the coupon dates (including the payment lag), the underlying fixings, the resulting compounded coupon rates, and the coupon amounts. What you might still be asking yourselves is, didn&#8217;t we specify a reset schedule rolling on the first of the month? Why are all the fixings a few days before that?</p><p>The thing is, what I called the reset schedule doesn&#8217;t give us the sequence of the fixing dates; instead, it gives us the schedule of the periods underlying each of the fixings. To see if I can make more sense, let&#8217;s take the first coupon and ask it for its start and end dates.</p><pre><code><code>first = ql.as_multiple_resets_coupon(
    bond.cashflows()[0]
)
print(first.accrualStartDate())
print(first.accrualEndDate())</code></code></pre><pre><code><code>August 1st, 2024
November 1st, 2024</code></code></pre><p>If this were a floating-rate coupon, it would have a single fixing date. Since the Euribor index has two fixing days&#8230;</p><pre><code><code>print(index.fixingDays())</code></code></pre><pre><code><code>2</code></code></pre><p>&#8230;the fixing date would be as follows:</p><pre><code><code>print(
    calendar.advance(
        first.accrualStartDate(),
        -index.fixingDays(),
        ql.Days,
    )
)</code></code></pre><pre><code><code>July 30th, 2024</code></code></pre><p>that is, the first fixing date in the table above. Since the coupon has multiple resets, its period is divided into three sub-periods specified by the first dates of the reset schedule:</p><pre><code><code>print(f"{reset_schedule[0]} to {reset_schedule[1]}")
print(f"{reset_schedule[1]} to {reset_schedule[2]}")
print(f"{reset_schedule[2]} to {reset_schedule[3]}")</code></code></pre><pre><code><code>August 1st, 2024 to September 2nd, 2024
September 2nd, 2024 to October 1st, 2024
October 1st, 2024 to November 1st, 2024</code></code></pre><p>These roll on the first of the month, as specified, and when the fixing days are taken into account we find the dates in the table:</p><pre><code><code>for i in [0,1,2]:
    print(
        calendar.advance(
            reset_schedule[i],
            -index.fixingDays(),
            ql.Days
        )
    )</code></code></pre><pre><code><code>July 30th, 2024
August 29th, 2024
September 27th, 2024</code></code></pre><p>See you next time!</p>]]></content:encoded></item><item><title><![CDATA[Adding a new cash flow to QuantLib, part II]]></title><description><![CDATA[Hello, dear reader.&#160; Today&#8217;s post was originally published in the September 2024 issue of Wilmott Magazine.&#160; So, where were we?]]></description><link>https://implementingquantlib.substack.com/p/adding-a-new-cash-flow-to-quantlib-110</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/adding-a-new-cash-flow-to-quantlib-110</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 30 Jan 2025 06:31:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hkwl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, dear reader. Today&#8217;s post was originally published in the September 2024 issue of <em>Wilmott Magazine</em>. The full source code is available <a href="https://www.implementingquantlib.com/p/tutorials.html">on my Tutorial page</a>, together with code from other articles and, when available, either the articles themselves or corresponding posts like this one.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Adding a new cash flow to QuantLib, part II</h2><p>Welcome back. So, where were we?</p><h2>A quick recap</h2><p>In the article in <a href="https://implementingquantlib.substack.com/p/adding-a-new-cash-flow-to-quantlib">the previous issue</a>, I described part of the class hierarchy that has <code>CashFlow</code> at its root and models different types of cash flows and coupons; in particular, and more to our purpose, floating-rate coupons.</p><p>And in case I need to refresh said purpose in your memory: we want to implement a coupon whose rate must be interpolated between the fixings of two quoted indexes; such a stub can be sometimes found at the beginning of a floating-rate bond or an interest-rate swap. For instance, it might be a four-months coupon whose rate should be interpolated between the quoted three-months and six-months Euribor fixings, followed by semiannual coupons paying the six-months Euribor rate.</p><p>Now, given the coupon we want to interpolate and given the existing hierarchy (sketched in the figure below), the question becomes: where in this hierarchy can we find a peg on which to hang the new class?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hkwl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hkwl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 424w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 848w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hkwl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png" width="1456" height="1420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1420,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:422095,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Hkwl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 424w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 848w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!Hkwl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F551da8a0-ca63-40e2-9589-0deb02fddaca_2500x2438.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>A sample case</h2><p>Instead of just pondering the possibilities, we can write some sample code and try them out. Since, as I said, you can download the complete code, here I&#8217;ll skip a couple of utility functions in the interest of brevity (the first displays data on a series of cash flows and the second calculates the interpolated rate between two fixings) and show instead the contents of the <code>main</code> function, which sets up the example I mentioned above: it creates a semiannual schedule with an initial short stub spanning four months, from April to August 2023, as well as instances of the three-months and six-months Euribor indexes, each with its forecast curve and a few past fixings stored in memory.</p><pre><code><code>   auto today = Date(18, March, 2024);
   Settings::instance().evaluationDate() = today;
   IborCoupon::Settings::instance()
      .createIndexedCoupons();

   Schedule schedule =
      MakeSchedule()
         .from(Date(8, April, 2023))
         .withFirstDate(Date(8, August, 2023))
         .to(Date(8, February, 2028))
         .withFrequency(Semiannual)
         .withCalendar(TARGET())
         .backwards();

   auto forecastCurve3m =
      ext::make_shared&lt;FlatForward&gt;(
         today, 0.030, Actual360());

   auto index3m = ext::make_shared&lt;Euribor3M&gt;(
      Handle&lt;YieldTermStructure&gt;(forecastCurve3m));
   index3m-&gt;addFixing(Date(5, April, 2023), 0.03055);

   auto forecastCurve6m =
      ext::make_shared&lt;FlatForward&gt;(
         today, 0.035, Actual360());

   auto index6m = ext::make_shared&lt;Euribor6M&gt;(
      Handle&lt;YieldTermStructure&gt;(forecastCurve6m));
   index6m-&gt;addFixing(Date(5, April, 2023), 0.03339);
   index6m-&gt;addFixing(Date(4, August, 2023), 0.0394);
   index6m-&gt;addFixing(Date(6, February, 2024), 0.03922);</code></code></pre><h3>How not to do it</h3><p>The first bond we create based on those objects, <code>bond0</code>, is not correct. The code builds it by creating a sequence of coupons paying six-months Euribor and passing them unchanged to the bond constructor.</p><pre><code><code>   int settlementDays = 3;
   double faceAmount = 1000000.0;
   auto dayCounter = index6m-&gt;dayCounter();

   Leg coupons =
      IborLeg(schedule, index6m)
         .withNotionals(faceAmount)
         .withPaymentDayCounter(dayCounter);

   Date issueDate = schedule[0];

   Bond bond0(settlementDays, schedule.calendar(),
              issueDate, coupons);</code></code></pre><p>Inspecting the bond coupons we get the results below: the coupons from the second onwards are correct, but the first coupon needs to be changed so that it interpolates between the three-months and six-months index fixings.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jeNz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jeNz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 424w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 848w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 1272w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jeNz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png" width="498" height="343" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:686,&quot;width&quot;:996,&quot;resizeWidth&quot;:498,&quot;bytes&quot;:338728,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jeNz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 424w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 848w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 1272w, https://substackcdn.com/image/fetch/$s_!jeNz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F835703c1-42ec-487d-a0a8-d4cfecd5e26c_996x686.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>First try: a special IBOR coupon</h3><p>Your first thought, and a perfectly reasonable one, might be that this coupon is irregular but still a Euribor coupon after all. I won&#8217;t argue with that; in fact, I&#8217;ll go ahead and implement a new class inheriting from <code>IborCoupon</code>. Let&#8217;s call it <code>InterpolatedIborCoupon1</code>.</p><pre><code><code>   class InterpolatedIborCoupon1 : public IborCoupon {
      shared_ptr&lt;IborIndex&gt; otherIndex_;
    public:
      InterpolatedIborCoupon1(
         const Date&amp; paymentDate,
         Real nominal,
         const Date&amp; startDate,
         const Date&amp; endDate,
         Natural fixingDays,
         const shared_ptr&lt;IborIndex&gt;&amp; shorterIndex,
         const shared_ptr&lt;IborIndex&gt;&amp; longerIndex)
      : IborCoupon(paymentDate,
                   nominal,
                   startDate,
                   endDate,
                   fixingDays,
                   longerIndex),
        otherIndex_(shorterIndex) {
         registerWith(otherIndex_);
      }
      Rate indexFixing() const override {
         return interpolatedRate(
            fixingDate(),
            accrualStartDate(),
            accrualEndDate(),
            otherIndex_,
            iborIndex());
      }
      shared_ptr&lt;IborIndex&gt; shorterIndex() const {
         return otherIndex_;
      }
      shared_ptr&lt;IborIndex&gt; longerIndex() const {
         return iborIndex();
      }
   };</code></code></pre><p>The implementation turns out to be simple: we need to declare a constructor and to override the <code>indexFixing</code> method from the base class so that it returns the interpolated fixing (the other methods delegate to this one).</p><p>However, there are a couple of issues with this. Mind you, the class works: when we create another bond (<code>bond1</code> in the code) after replacing the first coupon and print out its cash flows, we get the results in the next table, where the amount paid by the first coupon is the one we wanted. But there are hints that our mental model might not be solid.</p><pre><code><code>   auto stub =
      ext::dynamic_pointer_cast&lt;IborCoupon&gt;(
         coupons[0]);

   auto newStub1 =
      ext::make_shared&lt;InterpolatedIborCoupon1&gt;(
         stub-&gt;date(), stub-&gt;nominal(),
         stub-&gt;accrualStartDate(),
         stub-&gt;accrualEndDate(),
         stub-&gt;fixingDays(), index3m, index6m);
   newStub1-&gt;setPricer(
      ext::make_shared&lt;BlackIborCouponPricer&gt;());

   coupons[0] = newStub1;

   Bond bond1(settlementDays, schedule.calendar(),
              issueDate, coupons);</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eDz8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eDz8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 424w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 848w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 1272w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eDz8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png" width="498" height="343" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:686,&quot;width&quot;:996,&quot;resizeWidth&quot;:498,&quot;bytes&quot;:339390,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eDz8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 424w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 848w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 1272w, https://substackcdn.com/image/fetch/$s_!eDz8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c319cb4-4d58-4121-90d1-bb158c57e7a6_996x686.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>A shaky implementation?</h4><p>First: the constructor takes the date information for the coupon, as well as the two indexes we need to interpolate. The base-class constructor? It asks for the same date information, and for the index it&#8217;s supposed to pay. We have two, and which one we should pass is anybody&#8217;s guess; I&#8217;m passing the longer one here, but it&#8217;s entirely arbitrary.</p><p>This causes some of the methods in the class interface to return an answer which doesn&#8217;t make a lot of sense, or is just wrong. In our example, calling the <code>index()</code> method of the coupon will return the 6-months Euribor and fail to mention the second index and the interpolation being performed; other methods will fail in a similar way. And unfortunately, overriding <code>index</code> is not possible; it is virtual, but its signature declares that it returns a single index instance, so we can&#8217;t return two.</p><p>The problem, as I mentioned in the previous article, is that the <code>IborCoupon</code> class and its base <code>FloatingRateCoupon</code> class assume a single underlying index. Our coupon looks a lot like an IBOR coupon, but it doesn&#8217;t match its interface.</p><p>For a deeper discussion on this, search the Internet for &#8220;circle-ellipse problem&#8221;. Is a circle also an ellipse? The answer is not obvious in object-oriented programming. It&#8217;s all about the interfaces, both declared and inherited, that an object is supposed to provide.</p><p>The issue can be mitigated somehow by declaring other methods, like <code>shorterIndex</code> and <code>longerIndex</code> in the example, that match the coupon better. But the other methods remain.</p><p>I mentioned a couple of issues. The second is that this coupon works because the pricer we&#8217;re using doesn&#8217;t happen to call any of its methods besides <code>indexFixing</code>. If someone wrote another pricer that did, the problem above would suddenly become a lot less abstract.</p><h3>Second try: an entire new kind of coupon</h3><p>Ok, what if we choose not to consider this as an IBOR coupon? In this case, we have to go up the inheritance chain (shown in the figure at the beginning of the post) to find another base class. The next class up is <code>FloatingRateCoupon</code>, but it relies on a single index and would have the same interface mismatch we found with <code>IborCoupon</code>. This leaves us with <code>Coupon</code>.</p><p>As I&#8217;m fond of saying, designing software is about tradeoffs. The <code>Coupon</code> class is high enough in the hierarchy&#8212;that is, it&#8217;s generic enough&#8212;that it doesn&#8217;t force on us any interface choice, as <code>IborCoupon</code> did. However, this also means that we&#8217;ll have to implement quite a bit of functionality that <code>IborCoupon</code> already provided.</p><p>Let&#8217;s call our second attempt <code>InterpolatedIborCoupon2</code>.</p><pre><code><code>   class InterpolatedIborCoupon2 : public Coupon {
      shared_ptr&lt;IborIndex&gt; shorterIndex_;
      shared_ptr&lt;IborIndex&gt; longerIndex_;
      mutable Rate rate_;
      Date fixingDate_;
    public:
      InterpolatedIborCoupon2(
         const Date&amp; paymentDate,
         Real nominal,
         const Date&amp; startDate,
         const Date&amp; endDate,
         Natural fixingDays,
         const shared_ptr&lt;IborIndex&gt;&amp; shorterIndex,
         const shared_ptr&lt;IborIndex&gt;&amp; longerIndex)
      : Coupon(paymentDate, nominal,
               startDate, endDate),
        shorterIndex_(shorterIndex),
        longerIndex_(longerIndex) {
         registerWith(shorterIndex_);
         registerWith(longerIndex_);
         fixingDate_ =
            shorterIndex_-&gt;fixingCalendar().advance(
               startDate, -fixingDays, Days);
      }
      void performCalculations() const override {
         rate_ = interpolatedRate(
            fixingDate_,
            accrualStartDate(),
            accrualEndDate(),
            shorterIndex_,
            longerIndex_);
      }
      Rate rate() const override {
         calculate();
         return rate_;
      }
      Real amount() const override {
         return nominal() * rate() * accrualPeriod();
      }
      Real accruedAmount(const Date&amp; d)
      const override {
         return nominal() * rate() *
                dayCounter().yearFraction(
                   accrualStartDate(), d);
      }
      DayCounter dayCounter() const override {
         return shorterIndex_-&gt;dayCounter();
      }
      shared_ptr&lt;IborIndex&gt; shorterIndex() const {
         return shorterIndex_;
      }
      shared_ptr&lt;IborIndex&gt; longerIndex() const {
         return longerIndex_;
      }
   };</code></code></pre><p>Not surprisingly, its constructor takes the same arguments as our previous class; but this time, we&#8217;re only passing some of the information (the nominal and the coupon dates) to the base class constructor. The two indexes are stored in data members of this class, which allows it to provide two reasonable inspectors for them.</p><p>We also need to implement a number of calculations. First of all, the rate, which (as before) is given by a call to the <code>interpolateRate</code> function. It is calculated in the <code>performCalculation</code> method, because cash flows participate in the Observer pattern (remember <a href="https://implementingquantlib.substack.com/p/the-observer-pattern-in-quantlib">the November 2023 issue</a>?) and this is how we ensure that they are cached between notifications. If you want details, chapter 2 of <em><a href="https://www.implementingquantlib.com/p/the-book.html">Implementing QuantLib</a></em> has plenty of them. The <code>rate</code> method ensures that calculations are up to date and then returns the rate.</p><p>Besides the rate, though, there are other calculations required by the interface of <code>Coupon</code>; namely, the total amount and the accrued amount at a given date, returned in the obvious way. We also need to return the day counter, but we delegate that to one of the indexes.</p><p>And once we have the class, we can use it to replace the first coupon and create another bond (<code>bond2</code> in the code); printing out its cash flows gives the same results already shown for the previous bond. The class works as we wanted.</p><pre><code><code>   auto newStub2 =
      ext::make_shared&lt;InterpolatedIborCoupon2&gt;(
         stub-&gt;date(), stub-&gt;nominal(),
         stub-&gt;accrualStartDate(),
         stub-&gt;accrualEndDate(),
         stub-&gt;fixingDays(),
         index3m, index6m);

   coupons[0] = newStub2;

   Bond bond2(settlementDays, schedule.calendar(),
              issueDate, coupons);</code></code></pre><p>So, is it better or worse than the first? It&#8217;s not easy to say, and as a matter of fact we might be going into matters of mere taste. The advantage of this class is an interface that won&#8217;t risk confusing the users and returning wrong results. The disadvantage is that it needs to reimplement a bunch of virtual methods; and also, do we really want this coupon <em>not</em> to be an IBOR coupon? Your mileage might vary.</p><h3>Third try: a special IBOR index</h3><p>But wait, what if we think laterally? What if we circle back to <code>IborCoupon</code> and provide a single index which is the interpolation of the two given indexes? This results in the <code>InterpolatedIborIndex</code> class; but (spoiler alert) it doesn&#8217;t turn out to be a great idea.</p><pre><code><code>   class InterpolatedIborIndex : public IborIndex {
      Date couponStartDate_, couponEndDate_;
      shared_ptr&lt;IborIndex&gt; shorterIndex_;
      shared_ptr&lt;IborIndex&gt; longerIndex_;
    public:
      InterpolatedIborIndex(
         const Date&amp; couponStartDate,
         const Date&amp; couponEndDate,
         const shared_ptr&lt;IborIndex&gt;&amp; shorterIndex,
         const shared_ptr&lt;IborIndex&gt;&amp; longerIndex)
      : IborIndex(*longerIndex),
        couponStartDate_(couponStartDate),
        couponEndDate_(couponEndDate),
        shorterIndex_(shorterIndex),
        longerIndex_(longerIndex) {
         registerWith(shorterIndex_);
         registerWith(longerIndex_);
      }
      Rate fixing(const Date&amp; fixingDate,
                  bool = false) const override {
         return interpolatedRate(
            fixingDate,
            couponStartDate_, couponEndDate_,
            shorterIndex_, longerIndex_);
      }
      Rate forecastFixing(
         const Date&amp; fixingDate) const override {
            return fixing(fixingDate);
      }
      Rate pastFixing(
         const Date&amp; fixingDate) const override {
            return fixing(fixingDate);
      }
   };</code></code></pre><p>It is inherited from <code>IborIndex</code>, which makes it possible to pass it with <code>IborCoupon</code>, and overrides its relevant methods so that they return the interpolated rate. Its constructor takes the two indexes, stores them, and sets things up so that any notifications from them are forwarded. However, that&#8217;s not all the information that the constructor must take. The start and end date of the coupon are also needed for the calculation, and since they can&#8217;t be passed to the <code>fixing</code> method (whose interface is defined in the base class) they must be passed to the constructor and stored.</p><p>This makes the index not quite as generic as I would like; that is, we can&#8217;t create an instance of this index based on, say, 3-months and 6-months Euribor and use it for different bonds or coupons. Any instance is tied to a specific coupon and can only be used with it.</p><p>The real deal breaker, though, is that this class doesn&#8217;t always work. It does in this case: after creating an instance of the index and the corresponding Euribor coupon, the code builds yet another bond (<code>bond3</code>). If we print its cash flows, we get again the results in the last table above.</p><pre><code><code>   auto newIndex =
      ext::make_shared&lt;InterpolatedIborIndex&gt;(
         stub-&gt;accrualStartDate(),
         stub-&gt;accrualEndDate(),
         index3m, index6m);

   auto newStub3 = ext::make_shared&lt;IborCoupon&gt;(
      stub-&gt;date(), stub-&gt;nominal(),
      stub-&gt;accrualStartDate(),
      stub-&gt;accrualEndDate(),
      stub-&gt;fixingDays(), newIndex);
   newStub3-&gt;setPricer(
      ext::make_shared&lt;BlackIborCouponPricer&gt;());

   coupons[0] = newStub3;

   Bond bond3(settlementDays, schedule.calendar(),
              issueDate, coupons);</code></code></pre><p>However, this only happens because the coupon was fixed in the past. If it was to be fixed in the future and therefore forecast, it would give the wrong result, because <code>IborCoupon</code> cheats. For the sake of performance, it&#8217;s declared as a friend of <code>IborIndex</code> and calls a private, non-virtual method <code>forecastFixing</code> instead of the one we could override in this code. (Years ago, this was necessary to be able to bootstrap a curve in real time from Excel. It might not be necessary now, but for the time being it&#8217;s in the code.)</p><h2>Summary</h2><p>Among the three alternatives I described, there seems to be a clear loser (the interpolated index) but not a clear winner. The one objective consideration might be that the first class is less robust against changes in the pricer implementation; other differences might be more subjective. So, as usual, no definitive design answers from yours truly except &#8220;it depends.&#8221;</p><p>Thanks for reading, and see you next time!</p>]]></content:encoded></item><item><title><![CDATA[Adding a new cash flow to QuantLib, part I]]></title><description><![CDATA[Welcome back.&#160; Today&#8217;s post was originally published in the July 2024 issue of Wilmott Magazine.&#160; Is QuantLib&#8217;s reputation for over-engineering well deserved? You&#8217;ll be the judge, dear reader.]]></description><link>https://implementingquantlib.substack.com/p/adding-a-new-cash-flow-to-quantlib</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/adding-a-new-cash-flow-to-quantlib</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 09 Jan 2025 06:30:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_Ra-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back. Today&#8217;s post was originally published in the July 2024 issue of <em>Wilmott Magazine</em>. The full source code is available <a href="https://www.implementingquantlib.com/p/tutorials.html">on the Tutorial page of my blog</a>, together with code from other articles and, when available, either the articles themselves or corresponding blog posts like this one.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Adding a new cash flow to QuantLib, part I</h2><p>Welcome back. In <a href="https://implementingquantlib.substack.com/p/cash-flows-and-bonds-in-quantlib">the May 2024 issue</a>, we looked at generating bond cash flows with a given schedule. It was from the point of view of using the existing features of the library.</p><p>This post changes focus and discusses the internal design of the library with the purpose to extend it. Is QuantLib&#8217;s reputation for over-engineering well deserved? You&#8217;ll be the judge, dear reader.</p><h2>A new kind of coupon</h2><p>As I write this, in late March 2024, some QuantLib contributors are discussing one particular kind of coupon which is not currently implemented: a short stub at the beginning of a floating-rate bond (or an interest-rate swap) whose rate must be interpolated between the fixings of two quoted indexes. Just to make an example, it might be a four-months coupon whose rate should be interpolated between the quoted three-months and six-months Euribor fixings.</p><p>By the time you read this, the library might have an implementation available. On this side of the publication lag, though, that didn&#8217;t happen yet; which made me think of writing this article as a way to clear my own ideas. First, I&#8217;ll describe the existing structure in which it should be inserted. In the next article, I&#8217;ll list a few possible ways to do it and comment on the pros and cons of each.</p><p>Will we find a sensible implementation? Will the current design bite us back? Since <em>Wilmott Magazine</em> doesn&#8217;t provide audio features, I&#8217;ll leave it to you to insert an appropriately suspenseful chord here.</p><h2>The status quo</h2><p>The implementation of cash flows is the subject of a full chapter in my <em><a href="https://implementingquantlib.substack.com/p/my-books">Implementing QuantLib</a></em> book, and you can find drafts of it in my blog if you don&#8217;t feel like heading to Amazon. Here are the Cliff&#8217;s notes.</p><p>There is a whole hierarchy of cash flows in the library, and you can see a sketch of part of it in the following figure.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_Ra-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_Ra-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 424w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 848w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_Ra-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png" width="1456" height="1420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1420,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:422095,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_Ra-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 424w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 848w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!_Ra-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e45f6d-9aa9-43e5-9bc3-788df237d9db_2500x2438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The parent class, <code>CashFlow</code>, has only a minimal interface; it can return the date and the amount of the payment&#8212;I&#8217;m glossing over a few other methods, coming from the fact that <code>CashFlow</code> in turn inherits from other classes (for instance, the <code>LazyObject</code> class I described in <a href="https://implementingquantlib.substack.com/p/the-observer-pattern-in-quantlib">the November 2023 issue</a> in the context of the Observer pattern) and that it participates in the Acyclic Visitor pattern, too.</p><h3>Simple cash flows</h3><p>For some cash flows, date and amount are all there is; that&#8217;s the case for the <code>Redemption</code> class, modeling the final payment of a bond. The same goes for any amortizing payments during the life of a bond.</p><h3>More interesting cash flows</h3><p>A number of other cash flows can be implemented under the umbrella of the <code>Coupon</code> base class, modeling any payment that comes from accruing an interest rate over a period. Yes, that was a dad joke in the heading above. Force of habit.</p><p>The <code>Coupon</code> class adds several other methods to the interface of <code>CashFlow</code>. Some of them return information on the rate, the nominal upon which the interest is paid, the start and end date of the coupon, the day-count convention used to calculate the corresponding accrual time, and any ex-coupon period if that&#8217;s the case; and others return calculated results such as the accrued amount at any given date. They are a mixture of virtual and non-virtual methods; there is some method in the madness, but I&#8217;m not going into it in this article. Cliff&#8217;s notes, as I said.</p><p>The simplest class that derives from <code>Coupon</code> is probably <code>FixedRateCoupon</code>. The interest rate is a given, so there&#8217;s no particular calculation to do in order to return the total coupon amount or the accrued amount at a given date. However, the implementation is not as simple as I would like (a common theme in the library, unfortunately) in order to allow some more outlandish coupons in which the rate is specified as a compounded or continuous rate.</p><h3>The sorrows of floating-rate coupons</h3><p>In <em>Implementing QuantLib</em>, I wrote: &#8220;The <code>FloatingRateCoupon</code> class is emblematic of the life of most software. It started simple, became more complex as time went by, and might now need some refactoring; its current implementation has a number of issues that I&#8217;ll point out as I describe it.&#8221;</p><p>As you can see, I would have made a very poor salesman.</p><p>This class models coupons that pay an interest rate based on the fixing of some index. The most common case used to be some kind of LIBOR-like index, but it could also be, for instance, a quoted swap rate in the case of the floating coupons of a constant-maturity swap. You&#8217;re surely thinking of another example right now, aren&#8217;t you?</p><p>To generalize over all of these cases, its constructor takes an instance (ok, a pointer to an instance) of the abstract <code>InterestRateIndex</code> base class, from which actual indexes are inherited; the index is responsible for providing its fixings, either by forecasting future ones or by storing past ones.</p><p>The <code>FloatingRateCoupon</code> class adds a number of methods to the interface of <code>Coupon</code>; you can see some of them in the figure above, but they&#8217;re by no means the only ones. The interested reader&#8212;that mythical figure&#8212;can find them in the library code or in <em>Implementing QuantLib</em>. Derived classes such as <code>IborCoupon</code> (a coupon that pays the fixing of an index such as Euribor, or LIBOR until the recent past) implement them using the relevant conventions, and their constructors restrict the kind of interest-rate index they can accept.</p><p>Now, <code>FloatingRateCoupon</code> looks harmless at first, but it already has some assumptions baked in; some of those might bite back, and a few have already done so. For instance, it has a <code>fixingDate</code> method and an <code>indexFixing</code> method; their signature assumes that the coupon rate is fixed at one single date, based on a single index fixing. As you can imagine, this hasn&#8217;t aged well now that coupons based on average SOFR fixings are the new normal. In fact, as shown in the diagram, the <code>OvernightIndexedCoupon</code> class (which models such coupons) had to ignore the inherited interface and declare a new, sensible one for its use case.</p><p>And of course, the other big assumption is that the coupon is based on the fixing of one single index. It doesn&#8217;t look good when you remember that we need to interpolate between two of them, does it?</p><h3>An example: a floating-rate bond</h3><p>I don&#8217;t want to be a downer, though. These coupon work, after all: in the following code, you can see an example where most of the complexity is hidden behind the scenes: we can instantiate a simple floating-rate bond by building its coupon schedule, creating an instance of the index to be paid, and passing everything to the constructor of the <code>FloatingRateBond</code> class.</p><pre><code><code>   auto today = Date(18, March, 2024);
   Settings::instance().evaluationDate() = today;
   IborCoupon::Settings::instance()
       .createIndexedCoupons();

   auto forecastCurve =
       ext::make_shared&lt;FlatForward&gt;(
           today, 0.04, Actual360());

   auto index6m = ext::make_shared&lt;Euribor6M&gt;(
      Handle&lt;YieldTermStructure&gt;(forecastCurve));
   index6m-&gt;addFixing(
       Date(6, February, 2023), 0.03008);
   index6m-&gt;addFixing(
       Date(4, August, 2023), 0.0394);
   index6m-&gt;addFixing(
       Date(6, February, 2024), 0.03922);

   Schedule schedule =
      MakeSchedule()
         .from(Date(8, February, 2023))
         .to(Date(8, February, 2028))
         .withFrequency(Semiannual)
         .withCalendar(TARGET())
         .backwards();

   int settlementDays = 3;
   double faceAmount = 1000000.0;
   auto dayCounter = index6m-&gt;dayCounter();

   FloatingRateBond bond(settlementDays, faceAmount,
                         schedule, index6m,
                         dayCounter);</code></code></pre><p>The index needs a forecast curve to return estimates of future fixings, and needs to store the past fixings we need (or, if you&#8217;re not writing a self-contained example, their whole history loaded from a DB.) Once everything is set up, the resulting bond can give you the cash flows shown in the following table, and can be priced given a discount curve or a yield (not shown here).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wng5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wng5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 424w, https://substackcdn.com/image/fetch/$s_!wng5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 848w, https://substackcdn.com/image/fetch/$s_!wng5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 1272w, https://substackcdn.com/image/fetch/$s_!wng5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wng5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png" width="590" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:682,&quot;width&quot;:1180,&quot;resizeWidth&quot;:590,&quot;bytes&quot;:374707,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wng5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 424w, https://substackcdn.com/image/fetch/$s_!wng5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 848w, https://substackcdn.com/image/fetch/$s_!wng5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 1272w, https://substackcdn.com/image/fetch/$s_!wng5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c680f64-cf05-45a3-b54f-d160c3bc5486_1180x682.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Adding a floor</h3><p>Unfortunately, sometimes we have to get a bit more under the hood. For instance, if we want our coupons to have a floor, we need (as shown in the next bit of code) first to create them and then to give them a pricer. The latter knows how to deal with the optionality and contains the required additional market data, such as the volatility of the rate in this case.</p><pre><code><code>   Leg coupons =
       IborLeg(schedule, index6m)
       .withNotionals(faceAmount)
       .withPaymentDayCounter(dayCounter)
       .withFloors(0.01);

   auto volatility =
       ext::make_shared&lt;ConstantOptionletVolatility&gt;(
           today, TARGET(), Following, 0.25,
           Actual360(), ShiftedLognormal, 0.01);

   auto pricer =
       ext::make_shared&lt;BlackIborCouponPricer&gt;(
           Handle&lt;OptionletVolatilityStructure&gt;(
               volatility));

   setCouponPricer(coupons, pricer);

   Date issueDate = schedule[0];

   Bond floored(settlementDays,
                schedule.calendar(),
                issueDate,
                coupons);</code></code></pre><p>The cash flows of the resulting bond are shown in the next table; you can see that the expected rate increases due to the floor and no longer equals the index fixing.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fM39!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fM39!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 424w, https://substackcdn.com/image/fetch/$s_!fM39!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 848w, https://substackcdn.com/image/fetch/$s_!fM39!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 1272w, https://substackcdn.com/image/fetch/$s_!fM39!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fM39!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png" width="590" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:682,&quot;width&quot;:1180,&quot;resizeWidth&quot;:590,&quot;bytes&quot;:374545,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fM39!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 424w, https://substackcdn.com/image/fetch/$s_!fM39!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 848w, https://substackcdn.com/image/fetch/$s_!fM39!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 1272w, https://substackcdn.com/image/fetch/$s_!fM39!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3f5620d-e33e-493f-8e34-be7811c6aa0d_1180x682.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This doesn&#8217;t add a lot of code to the example, compared to the simpler case; but supporting this case adds complexity to the cash flow hierarchy. As you can see from the figure above, a separate class deals with capped and/or floored coupons. This makes me itch every time I see it, because it means that a floored LIBOR coupon is not (in the sense of inheritance) a LIBOR coupon.</p><p>And of course, we need to introduce a hierarchy of pricers. But I won&#8217;t go in any detail about them; again, you know where to look if you&#8217;re interested.</p><h2>Next time: interpolated stub coupons</h2><p>Now, to go back to our desired new coupon: where should we place it in the cash flow hierarchy? The answer will have to wait for the next installment; and as I mentioned, it&#8217;s not entirely clear to me as of now. The seemingly obvious choices might have it inherit the wrong interface, or the wrong constraints. It might turn out to be a study in picking the lesser evil. Such is the interesting life of the library writer.</p><p>See you next time!</p><p></p>]]></content:encoded></item><item><title><![CDATA[A new QuantLib book]]></title><description><![CDATA[Hello, dear reader.&#160; I didn&#8217;t initially plan for this to happen right before Christmas, but, well, here we are. I just published A QuantLib Guide. You&#8217;re welcome.]]></description><link>https://implementingquantlib.substack.com/p/a-new-quantlib-book</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/a-new-quantlib-book</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 19 Dec 2024 06:31:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MA6B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223c8997-c723-410f-b32c-0d9892293101_1200x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, dear reader. I didn&#8217;t initially plan for this to happen right before Christmas, but, well, here we are. I just published <em><a href="https://www.quantlibguide.com/">A QuantLib Guide</a></em>. You&#8217;re welcome.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t2Xn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t2Xn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 424w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 848w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t2Xn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg" width="240" height="311" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:311,&quot;width&quot;:240,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31165,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t2Xn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 424w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 848w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!t2Xn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d7729ed-e536-4313-9681-0d8ae661e499_240x311.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Why a new book, you ask? It tries to fill a gap in the documentation available for QuantLib. It&#8217;s a gap that, unfortunately, I contributed to create; <em><a href="https://getbook.at/implementingquantlib">Implementing QuantLib</a></em> is aimed more at developers wanting to extend the library and less at library users, and the material I contributed to <em><a href="https://leanpub.com/quantlibpythoncookbook">QuantLib Python Cookbook</a></em> is a decent catalog of use cases but lacks a bit of direction.</p><p>This new guide includes some updated material from those two books, as well as some later material I published on this blog or on Wilmott Magazine; the idea is to collect, update and present it all in a sequence that makes it suitable as a tutorial for someone approaching QuantLib or trying to get more fluent in its usage.</p><p>It&#8217;s a work in progress; please let me know if it goes in the right direction. I&#8217;ll be grateful for any corrections, suggestions or comments. And if you find this guide useful and want to support the effort, you can do so by buying <a href="https://leanpub.com/quantlibguide">a PDF version of this book on Leanpub</a>. As usual on Leanpub, you&#8217;ll have free access to new versions when the book is updated.</p><p>And of course, if you haven&#8217;t already, you can subscribe to this Substack to get my content in your email inbox as soon as I publish it. It&#8217;s free. Bye for now!</p>]]></content:encoded></item><item><title><![CDATA[Pricing over a range of days]]></title><description><![CDATA[Hello again. In this post, another old notebook originally included in the QuantLib Python Cookbook and updated for publication here]]></description><link>https://implementingquantlib.substack.com/p/pricing-over-a-range-of-days</link><guid isPermaLink="false">https://implementingquantlib.substack.com/p/pricing-over-a-range-of-days</guid><dc:creator><![CDATA[Luigi Ballabio]]></dc:creator><pubDate>Thu, 28 Nov 2024 06:31:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again. In this post, another old notebook originally included in the <em><a href="https://leanpub.com/quantlibpythoncookbook">QuantLib Python Cookbook</a></em> and updated for publication here. If you are starting to see a pattern, be patient and wait for next month (he said, smiling obscurely).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://implementingquantlib.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://implementingquantlib.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>Pricing over a range of days</h2><p>Based on questions on <em>Stack Exchange</em> from <a href="https://stackoverflow.com/questions/32869325/">Charles</a>, <a href="https://quant.stackexchange.com/questions/35961/">bob.jonst</a>, <a href="https://quant.stackexchange.com/questions/38509">MCM</a> and <a href="https://quant.stackexchange.com/questions/36830/">lcheng</a>.</p><pre><code><code>import QuantLib as ql
import pandas as pd
from matplotlib import pyplot as plt</code></code></pre><p>Let&#8217;s say we have an instrument (a fixed-rate bond, for instance) that we want to price on a number of dates. I assume we also have the market quotes, or the curves, corresponding to each of the dates; in this case we only need interest rate information, but the library works the same way for any quotes.</p><p>We&#8217;ll store the resulting prices in a dictionary, with the date as the key.</p><pre><code><code>prices = {}</code></code></pre><h2>Producing a single price</h2><p>To price the bond on a single date, we set the evaluation date&#8230;</p><pre><code><code>today = ql.Date(3, ql.May, 2021)
ql.Settings.instance().evaluationDate = today</code></code></pre><p>&#8230;and we create the instrument itself&#8230;</p><pre><code><code>start_date = ql.Date(8, ql.February, 2019)
maturity_date = start_date + ql.Period(5, ql.Years)
schedule = ql.Schedule(
    start_date,
    maturity_date,
    ql.Period(ql.Semiannual),
    ql.TARGET(),
    ql.Following,
    ql.Following,
    ql.DateGeneration.Backward,
    False,
)
coupons = [0.01]
bond = ql.FixedRateBond(
    3, 100, schedule, coupons,
    ql.Thirty360(ql.Thirty360.BondBasis)
)</code></code></pre><p>&#8230;and the required discount curve. Here, I&#8217;m interpolating it over a set of tabulated rates, such as those I might have available from an external curve service.</p><pre><code><code>data = [
    (ql.Period(0, ql.Months), 0.04),
    (ql.Period(6, ql.Months), 0.04),
    (ql.Period(1, ql.Years), 0.06),
    (ql.Period(2, ql.Years), 0.16),
    (ql.Period(3, ql.Years), 0.33),
    (ql.Period(5, ql.Years), 0.84),
    (ql.Period(7, ql.Years), 1.29),
    (ql.Period(10, ql.Years), 1.63),
    (ql.Period(20, ql.Years), 2.18),
    (ql.Period(30, ql.Years), 2.30),
]</code></code></pre><pre><code><code>dates = [today + p for p, _ in data]
rates = [r / 100 for _, r in data]

discount_curve = ql.ZeroCurve(
    dates, rates, ql.Actual365Fixed()
)</code></code></pre><p>Given the bond and the curve, we link them together through an engine and get the result.</p><pre><code><code>discount_handle = (
    ql.RelinkableYieldTermStructureHandle(
        discount_curve
    )
)
bond.setPricingEngine(
    ql.DiscountingBondEngine(discount_handle)
)</code></code></pre><pre><code><code>prices[today] = bond.cleanPrice()
print(prices[today])</code></code></pre><pre><code><code>101.94609091386468</code></code></pre><h2>Pricing on multiple days</h2><p>Now, let&#8217;s say for instance that we have rates for each day from January to April and we want to get the corresponding prices. For this notebook, they&#8217;re stored in a file so I don&#8217;t need to show you pages of numbers in order to define them. In the real world, they will probably be stored in a DB or some other resource.</p><pre><code><code>df = pd.read_csv(
    "./data/multiple-yields.txt",
    delimiter="\t", parse_dates=["Date"]
)
df</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iMNj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iMNj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 424w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 848w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 1272w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iMNj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png" width="686" height="343" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:686,&quot;width&quot;:1372,&quot;resizeWidth&quot;:686,&quot;bytes&quot;:134966,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iMNj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 424w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 848w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 1272w, https://substackcdn.com/image/fetch/$s_!iMNj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd145b5e-849f-4cbb-b06b-d5786980a977_1372x686.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We could repeat the whole calculation for all dates, but it goes against the grain of the library. The architecture (see chapter 2 of <a href="https://getbook.at/implementingquantlib">Implementing QuantLib</a> for details) was designed so that the instrument can react to changing market conditions; therefore, we can at least avoid recreating the instrument. We&#8217;ll still have change the discount curve and the evaluation date.</p><p>For instance, here I&#8217;ll calculate the price for the first row of data. As I mentioned, I need to set the new evaluation date:</p><pre><code><code>d = ql.Date.from_date(df["Date"][0])
ql.Settings.instance().evaluationDate = d</code></code></pre><p>Then I&#8217;ll pick the corresponding rates and rebuild a discount curve:</p><pre><code><code>tenors = list(df.columns[1:])</code></code></pre><pre><code><code>dates = []
rates = []
for tenor in tenors:
    dates.append(d + ql.Period(tenor))
    rates.append(df[tenor][0] / 100)

discount_curve = ql.ZeroCurve(
    dates, rates, ql.Actual365Fixed()
)</code></code></pre><p>Now I can link the handle in the engine to the new discount curve&#8230;</p><pre><code><code>discount_handle.linkTo(discount_curve)</code></code></pre><p>&#8230;after which the bond returns the updated price.</p><pre><code><code>print(bond.cleanPrice())</code></code></pre><pre><code><code>102.55592512367953</code></code></pre><p>By repeating the process, I can generate prices for the whole data set.</p><pre><code><code>for _, row in df.iterrows():
    d = ql.Date.from_date(row["Date"])
    ql.Settings.instance().evaluationDate = d

    dates = []
    rates = []
    for tenor in tenors:
        dates.append(d + ql.Period(tenor))
        rates.append(row[tenor] / 100)
    discount_curve = ql.ZeroCurve(
        dates, rates, ql.Actual365Fixed()
    )

    discount_handle.linkTo(discount_curve)

    prices[d] = bond.cleanPrice()</code></code></pre><p>Here are the results.</p><pre><code><code>dates, values = zip(*sorted(prices.items()))</code></code></pre><pre><code><code>ax = plt.figure(figsize=(9, 4)).add_subplot(1, 1, 1)
ax.plot([d.to_date() for d in dates], values, "-");</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7sj8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7sj8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 424w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 848w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 1272w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7sj8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png" width="736" height="337" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:337,&quot;width&quot;:736,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31685,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7sj8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 424w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 848w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 1272w, https://substackcdn.com/image/fetch/$s_!7sj8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6e3271-ce3e-4f3d-8983-b7a0a572061d_736x337.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Using quotes</h2><p>If we work with quotes, we can also avoid rebuilding the curve. Let&#8217;s say our discount curve is defined as a risk-free curve with an additional credit spread. The risk-free curve is bootstrapped from a number of market rates; for simplicity, here I&#8217;ll use a set of overnight interest-rate swaps, but you&#8217;ll use whatever makes sense in your case.</p><pre><code><code>index = ql.Estr()
tenors = [
    ql.Period(i, ql.Years) for i in range(1, 11)
]
rates = [
    0.010,
    0.012,
    0.013,
    0.014,
    0.016,
    0.017,
    0.018,
    0.020,
    0.021,
    0.022,
]

quotes = []
helpers = []
for tenor, rate in zip(tenors, rates):
    q = ql.SimpleQuote(rate)
    h = ql.OISRateHelper(
        2, tenor, ql.QuoteHandle(q), index
    )
    quotes.append(q)
    helpers.append(h)</code></code></pre><p>One thing to note: I&#8217;ll setup the curve so that it moves with the evaluation date. This means that I won&#8217;t pass an explicit reference date, but a number of business days and a calendar. Passing 0, as in this case, will cause the reference date of the curve to always equal the evaluation date, whatever it happens to be at any given moment; while passing 2, for instance, would cause it to equal the corresponding spot date.</p><pre><code><code>risk_free_curve = ql.PiecewiseFlatForward(
    0, ql.TARGET(), helpers, ql.Actual360()
)</code></code></pre><p>Finally, I&#8217;ll manage credit as an additional spread over the curve:</p><pre><code><code>spread = ql.SimpleQuote(0.01)
discount_curve = ql.ZeroSpreadedTermStructure(
    ql.YieldTermStructureHandle(risk_free_curve),
    ql.QuoteHandle(spread)
)</code></code></pre><p>Now we can recalculate today&#8217;s price:</p><pre><code><code>ql.Settings.instance().evaluationDate = today
discount_handle.linkTo(discount_curve)

prices[today] = bond.cleanPrice()
print(prices[today])</code></code></pre><pre><code><code>96.48182194290018</code></code></pre><h2>Multiple days again</h2><p>As before, I&#8217;ll now assume to have quotes stored for the past four months; and again, I&#8217;ll read the quotes from a file.</p><pre><code><code>df = pd.read_csv(
    "./data/multiple-quotes.txt",
    delimiter="\t", parse_dates=["date"]
)
df</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l3vj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l3vj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 424w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 848w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 1272w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l3vj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png" width="706" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:682,&quot;width&quot;:1412,&quot;resizeWidth&quot;:706,&quot;bytes&quot;:133021,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!l3vj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 424w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 848w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 1272w, https://substackcdn.com/image/fetch/$s_!l3vj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fb5e474-2f8a-476f-8b9e-1182bfa210a2_1412x682.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now that we created the curve based on quotes, we don&#8217;t need to build a new one at each step. Instead, we can set new values to the quotes and they will trigger the necessary recalculations in the curve and the instrument.</p><pre><code><code>prices = {}

for _, row in df.iterrows():
    date = ql.Date.from_date(row["date"])

    for q, tenor in zip(
        quotes,
        ["1Y", "2Y", "3Y", "4Y", "5Y",
         "6Y", "7Y", "8Y", "9Y", "10Y"],
    ):
        q.setValue(row[tenor] / 100)

    spread.setValue(row["spread"] / 100)

    ql.Settings.instance().evaluationDate = date

    prices[date] = bond.cleanPrice()</code></code></pre><p>Note that we didn&#8217;t create any new object in the loop; we&#8217;re only settings new values to the quotes.</p><p>Again, here are the results:</p><pre><code><code>dates, values = zip(*sorted(prices.items()))

ax = plt.figure(figsize=(9, 4)).add_subplot(1, 1, 1)
ax.plot([d.to_date() for d in dates], values, "-");</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yb2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yb2b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 424w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 848w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 1272w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yb2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png" width="728" height="337" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:337,&quot;width&quot;:728,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32309,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yb2b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 424w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 848w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 1272w, https://substackcdn.com/image/fetch/$s_!yb2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff844143-d321-43e2-a29f-df88e20e6ae3_728x337.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>A complication: past fixings</h2><p>For instruments that depend on the floating rate, we might need some past fixings. This is not necessarily related to pricing on a range of dates: even on today&#8217;s date, we need the fixing for the current coupon. Let&#8217;s set the instrument up&#8230;</p><pre><code><code>forecast_handle = ql.YieldTermStructureHandle(
    risk_free_curve
)
index = ql.Euribor6M(forecast_handle)

bond = ql.FloatingRateBond(
    3, 100, schedule, index,
    ql.Thirty360(ql.Thirty360.BondBasis)
)
bond.setPricingEngine(
    ql.DiscountingBondEngine(discount_handle)
)</code></code></pre><pre><code><code>ql.Settings.instance().evaluationDate = today
for q, r in zip(quotes, rates):
    q.setValue(r)
spread.setValue(0.01)</code></code></pre><p>&#8230;and try to price it. No joy.</p><pre><code><code>try:
    print(bond.cleanPrice())
except Exception as e:
    print(f"{type(e).__name__}: {e}")</code></code></pre><pre><code><code>RuntimeError: Missing Euribor6M Actual/360 fixing for February 4th, 2021</code></code></pre><p>Being in the past, the fixing can&#8217;t be retrieved from the curve. We have to store it into the index, after which the calculation works:</p><pre><code><code>index.addFixing(ql.Date(4, ql.February, 2021), 0.005)

print(bond.cleanPrice())</code></code></pre><pre><code><code>97.09258058529258</code></code></pre><p>When pricing on a range of dates, though, we need to take into account the fact that the current coupon changes as we go back in time. These two dates will work&#8230;</p><pre><code><code>ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.March, 2021)
)
print(bond.cleanPrice())

ql.Settings.instance().evaluationDate = (
    ql.Date(15, ql.February, 2021)
)
print(bond.cleanPrice())</code></code></pre><pre><code><code>96.84054989393012
96.78778930005275</code></code></pre><p>&#8230;but this one causes the previous coupon to be evaluated, and that requires a new fixing:</p><pre><code><code>ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.February, 2021)
)
try:
    print(bond.cleanPrice())
except Exception as e:
    print(f"{type(e).__name__}: {e}")</code></code></pre><pre><code><code>RuntimeError: Missing Euribor6M Actual/360 fixing for August 6th, 2020</code></code></pre><p>Once we add it, the calculation works again.</p><pre><code><code>index.addFixing(ql.Date(6, ql.August, 2020), 0.004)
print(bond.cleanPrice())</code></code></pre><pre><code><code>96.97859587521839</code></code></pre><p>(If you&#8217;re wondering how the calculation worked before, since this coupon belonged to the bond: on the other evaluation dates, this coupon was expired and the engine could skip it without needing to calculate its amount. Thus, its fixing didn&#8217;t need to be retrieved.)</p><h2>More complications: future past fixings</h2><p>What if we go forward in time, instead of pricing on past dates?</p><p>For one thing, we&#8217;ll need to forecast curves in some way. One way is to imply them from today&#8217;s curves: I talk about implied curves in another notebook, so I won&#8217;t repeat myself here. Let&#8217;s assume we have implied rates and we can set them. Once we do, we can price in the future just as easily as we do in the past.</p><pre><code><code>ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.June, 2021)
)

print(bond.cleanPrice())</code></code></pre><pre><code><code>97.2097212826188</code></code></pre><p>However, there&#8217;s another problem, as pointed out by <a href="https://sourceforge.net/p/quantlib/mailman/message/35270917/">Mariano Zeron</a> in a post to the QuantLib mailing list. If we go further in the future, the bond will require&#8212;so to speak&#8212;future past fixings.</p><pre><code><code>ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.September, 2021)
)
try:
    print(bond.cleanPrice())
except Exception as e:
    print(f"{type(e).__name__}: {e}")</code></code></pre><pre><code><code>RuntimeError: Missing Euribor6M Actual/360 fixing for August 5th, 2021</code></code></pre><p>Here the curve starts on September 1st 2021, and cannot retrieve the fixing at the start of the corresponding coupon.</p><p>One way out of this might be to forecast fixings off the current curve and store them:</p><pre><code><code>ql.Settings.instance().evaluationDate = today

fixing_date = ql.Date(5, ql.August, 2021)
future_fixing = index.fixing(fixing_date)
print(future_fixing)
index.addFixing(fixing_date, future_fixing)</code></code></pre><pre><code><code>0.009974987403789588</code></code></pre><p>This way, they will be retrieved in the same way as real past fixings.</p><pre><code><code>ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.September, 2021)
)

print(bond.cleanPrice())</code></code></pre><pre><code><code>97.55258650460641</code></code></pre><p>Of course, you might forecast them in a better way: that&#8217;s up to you. And if you&#8217;re worried that this might interfere with pricing on today&#8217;s date, don&#8217;t: stored fixings are only used if they&#8217;re in the past with respect to the evaluation date. The fixing I&#8217;m storing below for February 3rd 2022 will be retrieved if the evaluation date is later&#8230;</p><pre><code><code>index.addFixing(ql.Date(3, ql.February, 2022), 0.02)

ql.Settings.instance().evaluationDate = (
    ql.Date(1, ql.June, 2022)
)
print(index.fixing(ql.Date(3, ql.February, 2022)))</code></code></pre><pre><code><code>0.02</code></code></pre><p>&#8230;but it will be forecast from the curve when it&#8217;s after the evaluation date:</p><pre><code><code>ql.Settings.instance().evaluationDate = today
print(index.fixing(ql.Date(3, ql.February, 2022)))</code></code></pre><pre><code><code>0.012063742194402307</code></code></pre>]]></content:encoded></item></channel></rss>