Welcome back.

Recently I realized that it’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? 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’re now well beyond that, using C++17 as the minimum required language version.

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.

Of course, these notes also apply to the digital version of Implementing QuantLib available on Leanpub. But I should warn you: you can’t leave the digital edition nonchalantly on your coffee table to show your guests that you’re a discerning quant.

Chapter 1: introduction

This is still a valid introduction to the book, and it is also proof that I’ve been using em-dashes for years—well before people started to associate them with AI-generated content. So don’t worry if you see me sprinkle some of them here. This content is still 100% organic.

Chapter 2: financial instruments and pricing engines

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’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, chi nasce tondo non può morire quadrato.

Chapter 3: term structures

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 BootstrapError class which is instantiated and passed to the solver during bootstrapping: you can still see it in our GitHub repository. The whole class is now replaced by a much simpler lambda:

    auto error = [&](Rate guess) {
        Traits::updateGuess(ts_->data_, guess, i);
        ts_->interpolation_.update();
        return helper->quoteError();
    };

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 A QuantLib Guide.

Chapter 4: cash flows and coupons

A notable change here is that the CashFlow class now inherits from LazyObject. Therefore, most of the calculations that were previously performed directly in methods like amount were moved into performCalculations instead, with amount usually implemented as, for instance,

    Real FixedRateCoupon::amount() const {
        calculate();
        return amount_;
    }

User-defined classes don’t need to be changed to adapt to this; the old implementation will still work, but it won’t take advantage of lazy calculations.

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.

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’s possible to enable keyword arguments in Python (which would be prevented if the constructor were overloaded).

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 Rate or a Handle<Quote> can now declare a single constructor taking a std::variant<Rate, Handle<Quote>> and sort it out internally.

Finally, a smaller change: some methods like Bond::yield that used to take a price as a simple number now take it as a small Bond::Price structure containing both the price and an enum specifying the type (clean or dirty).

Chapter 5: parameterized models and calibration

No major changes here; instead, we managed to fix a number of smaller things I complained about in the book. The base class BlackCalibrationHelper no longer stores a yield term structure; its inner class ImpliedVolatilityHelper was replaced by a lambda; the HestonModelHelper class moved all its calculations in the performCalculations method instead of spreading them around the class; and using a later C++ standard allowed the inner class CalibrationFunction to avoid being declared as a friend of CalibratedModel.

Chapter 6: the Monte Carlo framework

The most relevant change here is that the Disposable 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.

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.

Chapter 7: the tree framework

No change here, almost literally: in the past five years, the only commits in this part of the code were automated changes by clang-tidy (like replacing type declarations with auto or replacing pass-by-reference and copy with pass-by-value and move).

Chapter 8: the finite-difference framework

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’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 BoundaryCondition and StepCondition 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.

There were no relevant changes in the new framework.

Chapter 9: conclusion

Like the intro, it is still valid—except for the part where I describe writing a book as a daunting task. After Implementing QuantLib, QuantLib Python Cookbook and A QuantLib Guide, it seems clear that I’ll write one at the slightest provocation.

Appendices

No relevant changes here, apart from the disappearance of Disposable that I have already mentioned. You can find more about the Observer pattern in A QuantLib Guide, but it adds to the description in Implementing QuantLib rather than replacing it.

The verdict

In short, I was happy to find that Implementing QuantLib still holds its own surprisingly well. If you’re curious about the architecture of the library, it’s still the book to read.

See you next time!