"Implementing QuantLib" as a paperback: five years later
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!