The benefits of procrastination
Welcome back.
This post comes courtesy of a QuantLib user who submitted an interesting pull request to GitHub and gave me permission to write about it (thanks, Guillaume). It’s something that might bite you, too. This might go into Implementing QuantLib at some point, but for the time being, here it is.
The background: we have an ImpliedTermStructure
class in the library
that takes an existing interest-rate curve and creates another one
based on the first but with a different reference date. (I posted a
screencast about it a few years ago, and
the corresponding notebook is in the
QuantLib Python Cookbook.)
The main bit of business logic in the class is the calculation of the
implied discount factors, implemented as:
void ImpliedTermStructure::discountImpl() {
...
return originalCurve_->discount(originalTime, true) /
originalCurve_->discount(ref, true);
}
that is, the discount \( \tilde{B}(d) \) between the reference date \( d_0 \) of the implied term structure and some date \( d \) is calculated as the ratio between the discounts \( B(d) \) and \( B(d_0) \) as given by the original term structure. The observation relevant to this post is that \( d \) changes at each invocation, while \( d_0 \) stays the same; however, we can’t precalculate \( B(d_0) \) at construction time because the original curve (which is stored in a handle) might change; therefore, the current implementation retrieves it at each call.
The idea in the pull request was that this was wasteful, and that the
discount \( B(d_0) \) could be calculated when the original curve
notifies us of a change, that is, in the update
method, and cached
between changes. A simplified version of this would read:
void ImpliedTermStructure::update() {
...
df_ = originalCurve_->discount(ref, true);
YieldTermStructure::update();
}
void ImpliedTermStructure::discountImpl() {
...
return originalCurve_->discount(originalTime, true) / df_;
}
The above looks correct, but it has a serious drawback, which tells me
that we might have designed a dangerous framework. The problem is
that calculations performed inside the update
method might be
wasted, because they might not be needed before another change
happens; this is why most implementations (in the LazyObject
class, for
instance, or in the TermStructure
class) only set some
kind of out-of-date flag.
How does this apply to the implementation above? Let’s say that the original curve is built by a bootstrap over a set of market quotes, and let’s say that the quotes change. We would set new values to all of them, probably with something like the following pseudocode:
for each quote:
quote.setValue(new_value)
It seems innocent, doesn’t it? In the current implementation, it is. Each time around the loop, one of the quotes is set a new value and notifies the bootstrapped curve, which is invalidated and in turn forwards the notification to the implied curve; and that’s all that happens.
The proposed implementation, instead, makes a world of difference. Each time around the loop, when the notification comes, the implied curve asks for the discount factor, and this trigger a bootstrap of the original curve (which otherwise wouldn’t be able to return it). The loop would cause as many bootstraps as the number of quotes—each one of them but the last performed over an only-partially-updated set of values.
The lesson to take away here is that the update
method should never
perform any calculations, but should instead defer them to a later
time. In this case, for instance, we could declare the stored
discount factor as a boost::optional
and write:
void ImpliedTermStructure::update() {
...
df_ = boost::none;
YieldTermStructure::update();
}
void ImpliedTermStructure::discountImpl() {
if (!df_) {
...
df_ = originalCurve_->discount(ref, true);
}
...
return originalCurve_->discount(originalTime, true) / *df_;
}
In this implementation, update
merely invalidates the cache; the
original curve is not asked for results until they’re actually needed,
and thus no bootstrap is performed during the loop that sets the
quotes.
Follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS: the buttons for 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.