Welcome back.

Today’s post on handles was originally published as an article in the March 2023 issue of Wilmott Magazine. A word of advice: if you ever submit articles for publication, keep the copyright of your content and grant a non-exclusive license to the publisher. You’re welcome.

In this post, the C++ code is mixed with the text. The full source file is available on my Tutorial page, together with code from other articles and, when available, either the articles themselves or corresponding blog posts like this one.

Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.

Handling dependencies in QuantLib

In this column, I’ll describe a class which is used everywhere in the code, is a critical part of the design of the library, and might just be a confusing complication if I don’t stop now and explain what problem it actually solves for you. It’s the Handle class. (Yes, the title above is a pun. Sue me.) This will also give me the opportunity to write about a few techniques and principles that you can use regardless of QuantLib.

First, let’s get the includes out of the way.

#include <ql/instruments/bonds/zerocouponbond.hpp>
#include <ql/pricingengines/bond/discountingbondengine.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/settings.hpp>
#include <ql/termstructures/yield/zerocurve.hpp>
#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/calendars/unitedkingdom.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <iostream>

A use case for handles: setting up

Let’s say we hold a number of different bonds. In the first part of the code, we build four of them and collect them in a vector. In the interest of brevity, they’re all zero-coupon bonds with different maturity dates; note, though, that the vector is declared to contain pointers to the base Bond class, so we could have easily mixed different kinds of bonds there.

int main() {

   using namespace QuantLib;

   auto valueDate = Date(7, March, 2023);
   Settings::instance().evaluationDate() = valueDate;

   // Create a few bonds

   std::vector<ext::shared_ptr<Bond>> bonds;

   auto settlementDays = 3;
   auto faceAmount = 100.0;
   auto calendar = TARGET();

   bonds.push_back(ext::make_shared<ZeroCouponBond>(
       settlementDays, calendar, faceAmount,
       Date(10, February, 2025)));

   bonds.push_back(ext::make_shared<ZeroCouponBond>(
       settlementDays, calendar, faceAmount,
       Date(26, August, 2030)));

   bonds.push_back(ext::make_shared<ZeroCouponBond>(
       settlementDays, calendar, faceAmount,
       Date(13, May, 2024)));

   bonds.push_back(ext::make_shared<ZeroCouponBond>(
       settlementDays, calendar, faceAmount,
       Date(15, August, 2040)));

Then, let’s assume that the bonds were issued by two different entities, and that we have discount curves available for both. They might come from an external system, or you might have bootstrapped or fitted them using some other facility provided by QuantLib. In this example, we build them using the ZeroCurve class, which interpolates a set of given zero rates at different dates; a real system would provide us with a lot more nodes but, again, I tried to keep the code short. As for bonds, the specific class doesn’t matter; any class inherited from the base YieldTermStructure class would do.

   std::vector<Date> curveNodes = {valueDate,
                                   valueDate + 10 * Years,
                                   valueDate + 20 * Years};
   std::vector<Rate> curveRates1 = {0.015, 0.025, 0.028};
   std::vector<Rate> curveRates2 = {0.005, 0.009, 0.012};

   auto issuerCurve1 = ext::make_shared<ZeroCurve>(
       curveNodes, curveRates1, Actual360());
   auto issuerCurve2 = ext::make_shared<ZeroCurve>(
       curveNodes, curveRates2, Actual360());

And here comes the step which might seem unnecessary: instead of using the curves directly, we wrap them in handles, namely, we pass each curve to an instance of RelinkableHandle<YieldTermStructure>. Yes, it’s a mouthful.

   auto issuerHandle1 =
       RelinkableHandle<YieldTermStructure>(issuerCurve1);
   auto issuerHandle2 =
       RelinkableHandle<YieldTermStructure>(issuerCurve2);

To complete our setup, we pass each of the handles to an instance of DiscountingBondEngine, which can calculate bond prices by discounting their coupons according to the given curve. Assuming that the first two bonds come from the first issuer and the second two from the other, we set the relevant engine to each bond; and finally, we can output their prices.

   auto engine1 = ext::make_shared<DiscountingBondEngine>(
       issuerHandle1);
   auto engine2 = ext::make_shared<DiscountingBondEngine>(
       issuerHandle2);

   bonds[0]->setPricingEngine(engine1);
   bonds[1]->setPricingEngine(engine1);
   bonds[2]->setPricingEngine(engine2);
   bonds[3]->setPricingEngine(engine2);

   for (auto b : bonds)            // prints: 96.7459
      std::cout << b->cleanPrice() //         84.3484
                << "\n";           //         99.3479
                                   //         81.9757

The benefits of using handles

Now, let’s suppose you want to create a modified curve in order to simulate what happens if the credit rating of the first issuer worsens; or maybe you want to change the set of instruments on which you’re fitting one of your curves, or again your system tells you that one of the curves has changed.

Whatever the reason, we can create a new curve and pass it to the linkTo method of the corresponding handle: in this example, to simulate a worsening of the credit, we use a ZeroSpreadedTermStructure instance which adds a spread on top of the existing curve. After doing this, we ask the bonds for their prices, and the first two bonds will return updated figures. No need to tell the bonds or the engines that their discount curve has changed; the library takes care of that.

   auto shiftedCurve1 =
       ext::make_shared<ZeroSpreadedTermStructure>(
           Handle<YieldTermStructure>(issuerCurve1),
           Handle<Quote>(
               ext::make_shared<SimpleQuote>(0.001)));

   issuerHandle1.linkTo(shiftedCurve1);

   for (auto b : bonds)            // prints: 96.5572
      std::cout << b->cleanPrice() //         83.7121
                << "\n";           //         99.3479
                                   //         81.9757
}

This, quite simply, is the reason for the existence of handles and the central role they play in the design of QuantLib: the ability to manage different term structures, quotes, and any kind of market dependencies without having to update each instrument explicitly (or without recreating the engine, as I did in the code from last issue in the interest of simplicity.)

And now, we can dig deeper in the way handles work. Or you could switch from the browser to your IDE for a while, and excitedly—nay, feverishly update your QuantLib-based code to take advantage of these new possibilities, and come back later for the rest of the article. I’ll wait.

Not just pointers

Oh, you’re back. Or you simply didn’t fall for my cheap columnist tricks. Either way, we can proceed.

You probably guessed that the Handle class works as some kind of pointer, keeping a reference to an external object and thus providing the means to observe the updated state of the object if it changes. You might also be thinking, “Wasn’t std::shared_ptr enough for that? And by the way, what’s this ext namespace I see you’re using for make_shared instead of std?”

Let me sidestep the question about ext for a bit; you can read it as std in the meantime. As for shared_ptr: yes, it would be enough for some cases, but not for all.

As a general rule which might deserve to be stated explicitly (and please bear with me a minute if this is old news for you), when we say that we’re “passing an object by pointer”, as in the constructor of the following class:

class C {
    A* p2;
  public:
    C(A* p1) : p2(p1) {}
};

what really happens is that we’re passing a pointer by copy; i.e., the stored p2 is a copy of the passed p1. Since they happen to be pointers, the end result is that they contain the same address and thus refer to the same instance of the A class.

For instance, in the context of QuantLib: C might be an instrument, and might be given a pointer p1 to a curve (A) bootstrapped over a set of quoted OIS rates. If the curve updated its nodes because the quotes changed, the copied pointer p2 would allow you to access the updated curve—which updated its nodes but is still the same instance.

What happens if we do the following, though?

A a1;
A* p1 = &a1;

C c(p1);

A a2;
p1 = &a2;

We have an instance a1; we take a pointer p1, let it point to a1 and pass it to c, which stores a copy of the pointer. Later, we create another instance a2 and update p1 so that it points to a2.

Flash quiz: what instance of A does c.p2 access, now? Exactly: the original instance a1. We modified p1, but this didn’t modify c.p2 which is a distinct copy.

Adding another level of indirection

If you wanted c to access the new object a2, the semantics you need are those of pointers to pointers. The class C needs to be written as:

class C {
    A** pp2;
  public:
    C(A** pp1) : pp2(pp1) {}
};

What does this give us? We can now write:

A a1;
A* p1 = &a1;
A** pp1 = &p1;

C c(pp1);

A a2;
p1 = &a2;

The class C now takes a copy of the pointer-to-pointer pp1, which points to p1. Flash quiz: when we update p1, what instance of A can c access? As before, the pointer-to-pointer c.pp2 was not modified; but this time, this means that it still points to p1 and it can see its updated value, which now stores the address of (drum roll) a2.

This is exactly the semantics of handles in QuantLib; they allow you to change the object they point to, and make the change accessible to any other object that stores a copy of the same handle. I’ll gloss over the implementation here; you can find it described in my book Implementing QuantLib or on this blog.

It sounds complicated. Why not setters?

Well, yes, you could have a setDiscountCurve method declared in the engine. But you’d have to keep track of which engines are using each curve. And of which indexes need forecast curves. And any number of other objects.

Also, if you, say, model your discount curves as a risk-free curve plus a credit spread, you’d want to set those components, not the whole discount curve. And as I mentioned, if you have other instruments in the portfolio, you’ll soon find yourself in a mess of setDiscountCurve, setForecastCurve, setDefaultProbability and so on. All in all, handles sound less complicated to me.

Why not just pointers to pointers then?

That wouldn’t be wrong, either. However, using a specific Handle class is more convenient, for a number of reasons. The simplest is that in 2000, when we started (or in 2010, for that matter,) C++ didn’t have auto to infer types or using to declare template aliases, and writing ext::shared_ptr<ext::shared_ptr<YieldTermStructure> > was a lot less convenient than Handle<YieldTermStructure>.

However—and here we go into some general principles—there are far stronger reasons for creating a wrapper class. If we used the pointer classes provided by the standard (or, in 2000, by Boost,) we would provide to the user the interface of that class; nothing more and nothing less. Instead, a wrapper class allows us to define a custom interface more suited to our needs.1

The advantage of doing this is twofold. On the one hand, it makes it possible to add behavior; for instance, relinking a handle also sends notifications to all dependent objects (again, a lot more details are in Implementing QuantLib.) But on the other hand, and just as importantly, it makes it possible to remove behavior and thus hide all the methods of shared_ptr that might cause problems.

Case in point: in QuantLib we have RelinkableHandle, which declares a linkTo method, as well as a Handle class which doesn’t. A RelinkableHandle can be converted to a Handle, but not the other way around; much like a non-const object can be converted to a const. The idea is that the main function, or some higher-level procedure that manages the calculations, will create relinkable handles and manage them. When the handles are passed and stored into instruments or other objects, they are passed as Handle so they can’t be relinked.

If we didn’t restrict the interface (or if we passed a pointer to pointer) it would be possible for an instrument to link, say, its volatility handle to something else in order to perform some internal calculation; but in doing so, it would affect other instruments which might also share the same handle (like the bonds from the same issuer, in the code example).

How do we know this? Years ago, we had to pull a release because of a problem like the one I just described. We only had Handle at the time, and it always allowed relinking—and options would merrily relink their handles while calculating their implied volatility and leave them modified. Hence, RelinkableHandle and Handle. Like for const, conventions are great, but having the compiler check them is even better.

So, what’s the ext namespace?

Oh, right—I did promise. In short: we have a compilation flag that allows us to define ext::shared_ptr as either boost::shared_ptr (the current default) or std::shared_ptr. We did the same for other classes such as std::function or std::tuple, each with its own compilation flag.

This gives us a migration path from using the Boost classes (the only alternative for the first decade of QuantLib) to using the ones in the C++ standard, without breaking other people’s code in the switch.

Boost is still the default for some of these classes; recently, we changed the default for function and tuple, with the long-term plan to deprecate the switch and just use the standard classes.

For shared_ptr, the decision will be a bit more complicated. There’s a significant difference in behavior between boost::shared_ptr and std::shared_ptr; the first can be configured so that trying to access a null shared_ptr raises an exception, while the second doesn’t check for null pointers and lets you access them and crash the program.

Therefore, it’s possible that we’ll keep boost::shared_ptr around as an alternative for quite a while, for instance when generating the Python wrappers for QuantLib. Unlike the C++ community, Python programmers tend to frown upon segmentation faults.

See you next time!

Notes

  1. This also holds for containers such as vector or list, and often also for basic types such as int, double or string. Search for “primitive obsession” if you want more info. Among others, GeePaw Hill and Ron Jeffries have some good content about it.