Chapter 5, part 1 of n: Parameterized models and calibration
Hello everybody.
New series of post, this time on chapter 5. This is content that I haven’t yet published in book form: in fact, I’m still writing it (as you might have guessed from the title of the post: I’m not sure how many posts this series will last). I look forward to your feedback.
Registration for the next Introduction to QuantLib Development course is still open: it is the three-day course that I teach based on the contents of this blog and of my book (plus several exercises; bring your compiler) and you can find more information, a brochure and a booking form by clicking on this link.
Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the widgets for that are in the sidebar, at the top right of the page. Also, make sure to check my Training page.
Parameterized models and calibration
CRITICS OF the practice of calibration argue that its very existence is a sign of a problem. After all, if physicists had to recalibrate the universal constant of gravitation yearly, it would probably mean that the formula is invalid (or that there’s something wrong with the idea of natural laws altogether, which is way scarier. This doesn’t seem to be the case for physics. The jury is still out for quantitative finance.)
For better or for worse, QuantLib supports calibration to market data because, well, that’s what people need to do. Much like C++ or Smith & Wesson, we might add some safety but we assume that users know what they’re doing, even if this gives them the possibility to shoot their foot off.
The calibration framework is one of the oldest parts of the library and has received little attention in the last few years; so it’s likely that, as I write this chapter, I’ll find and describe a number of things that could be improved—by breaking backward compatibility, I’m afraid, so they’ll have to wait. In the meantime, you can learn from our blunders.
Onwards. The framework enables us to write code such as, for instance,
The above works because of the interplay of two classes called
CalibratedModel
and CalibrationHelper
; the HullWhite
class
inherits from the former, while the elements of marketSwaptions
are
instances of a class that inherits from the latter (we’re also using
accessory classes, such as Simplex
, that implement optimization
methods; but I’ll postpone their description to appendix A). Since
they work together, describing either class before the other will
cause some vagueness and hand-waving. Bear with me as I try to
minimize the inconvenience (pun not intended).
The CalibrationHelper class
I’ll describe the CalibrationHelper
class first, since it depends
only indirectly on the model (in fact, it doesn’t use the model
interface directly at all). Its implementation is shown in listing
5.1.
Listing 5.1: Implementation of the CalibrationHelper
class.
The purpose of the class is similar—the name is a giveaway,
isn’t it?—to that of the BootstrapHelper
class, described in
chapter 3 (which, unfortunately, didn’t show up as posts yet). It
models a single quoted instrument (a “node” of the model, whatever
that might be) and provides the means to calculate the instrument
value according to the model and to check how far off it is from the
market value. Actually, the value isn’t the only possibility; we’ll
get to this in a bit.
CalibrationHelper
inherits from LazyObject
, that you know by
now. The reason is that it might need some preliminary calculation:
the target value of the optimization (say, the value) might not be
available directly, for instance because the market quotes the
corresponding implied volatility instead. The calculation to go from
the one to the other must be done just once, before the calibration,
and is done lazily as the market quote changes.
The constructor takes three arguments—each one maybe a bit less generic than I’d like, even though I only have minor complaints. The first argument is a handle to the quoted volatility; the assumption here is that, whatever the model is, that’s how the market quotes the relevant instruments.
The second argument is a handle to a term structure, that we assume to need for the calculations. That’s probably true, but it is only ever used by derived classes, not here; so I would have preferred it to be declared there, along with any other data they might need.
Finally, the third argument specifies how the calibration error is defined. It’s an enumeration that can take one of three values, meaning to take the relative error between the market price and the model price, or the absolute error between the prices, or the absolute error between the quoted volatility and the (Black) volatility implied by the model price. In principle, we might have used a Strategy pattern instead; but I’m not sure that the generalization is worth the added complexity, especially as I don’t have a possible fourth case in mind.
The body of the constructor is not shown here for brevity, but it does the usual things: it stores its arguments in the corresponding data members and registers with those that might change.
As I said, the LazyObject
machinery is used when market data change;
accordingly, the required performCalculations
method transforms the
quoted volatility into a market price and stores it. The actual
calculation depends on the particular instrument, so it’s delegated to
a purely virtual blackPrice
method; the evident assumption is that a
Black model was used to quote the market volatility. Finally, a
marketValue
method exposes the calculated price.
Note that, unfortunately, we need all three of the above methods. Yes,
I know, it bugs me too. Obviously, performCalculations
is required
by the LazyObject
interface; but what about blackPrice
and
marketValue
? Can’t we collapse them into one? Well, no. We want the
marketValue
inspector to be lazy, and therefore it must call
performCalculations
; thus, it can’t be the same as blackPrice
,
which is called by the performCalculations
method. (They also have
a different interface, since blackPrice
takes the volatility as an
argument; but that could have been managed by giving it a default
argument falling back to the stored volatility value.)
The next set of methods deals with the model-based calculations which
are executed during calibration. The purely virtual modelValue
, when
implemented in derived classes, must return the value of the
instrument according to the model; the calibrationError
method, that
I’ll describe in more detail later, returns some kind of difference
between the market and model values; and the setPricingEngine
brings
the model into play.
The idea here is that the engine that we’re storing has a pointer to
the model, and can be used to price the instrument represented by the
helper and thus give us a market value. All the helpers currently in
the library implement the modelValue
method as a straightforward
translation of the idea: they set the given engine to the instrument
they store, and ask it for its NPV (you’ll see it spelled out in next
post.)
In fact, the implementations are so similar that I wonder if we could
have provided a common one in the base class. Had we added a pointer
to an Instrument
instance as a data member, the method would just
be:
and with a bit more care, we could have set the pricing engine just
once, at the beginning of the calibration, instead of each time the
model value is recalculated. The downside of this would have been that
the results of the methods would have been dependent on the order in
which they were called; for instance, a call to the modelValue
right
after a call to marketValue
might have returned the Black price if
the latter had set a different engine to the instrument. According to
Murphy’s law, this would have bitten us back.
A last remark about the setPricingEngine
method: when I saw it, I
first thought that there was a bug in it, and that it should register
with the engine. Actually, it shouldn’t: the engine is used for the
model value only, and must not trigger the LazyObject
machinery that
recalculates the market value.
The last two methods are utilities that can be used to help the
calibration. The impliedVolatility
method uses a one-dimensional
solver to invert the blackPrice
method; that is, to find the
volatility that yields the corresponding Black price. Its
implementation is shown in the listing along with the sketch of an
accessory inner class ImpliedVolatilityHelper
that provides the
objective function for the solver.
The addTimesTo
method is to be used with tree-based methods (we’ll
get to those in chapter 7). It adds to the passed list a set of times
that are of importance to the underlying instrument (e.g., payment
times, exercise times, or fixing times) and therefore must be included
in any time grid to be used. If I were to write it now, I’d just
return the times instead of extending the given list, but that’s a
minor point. Another point is that, as I said, this method is tied to
a particular category of models, that is, tree-based ones, and might
not make sense for all helpers. Thus, I would provide an empty
implementation so that derived classes don’t necessarily have to
provide one. However, I wouldn’t try to move this method in some other
class—say, a derived class meant to model helpers for tree-based
models. On the one hand, it would add complexity and give almost no
upside; and on the other hand, we can’t even categorize helpers in
this way: any given helper could be used with both types of models.
Finally, the listing shows the implementation of the
calibrationError
method. It is called by the calibration routine
every time new parameters are set to the method, and returns an error
that tells up how far we are from market data. The definition of “how
far” is given by the stored enumeration value; if can be the relative
difference of the model and market prices, their absolute difference,
or the difference between the quoted volatility and the one implied by
the model price.
In next post: an example of calibration helper.