Chapter 7, part 6 of 6: an example of tree-based engine
Hello everybody.
A bit of trivia: one week ago, on April 20th, the QuantLib repository was forked for the 400th time (and they keep coming: we’re up to 407 already as I write this post). Kudos to GitHub user donglijiujiu—which doesn’t win any prize, apart from this shout-out.
This week: the final part of the series on tree that started a few weeks ago.
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.
Tree-based engines
As you might have guessed, a tree-based pricing engine will perform few actual computations; its main job will rather be to instantiate and drive the needed discretized asset and lattice. (If you’re pattern-minded, you can have your pick here. This implementation has suggestions of the Adapter, Mediator, or Facade pattern, even though it doesn’t match any of them exactly.)
Example: callable fixed-rate bonds
As an example, I’ll sketch the implementation of a tree-based pricing
engine for callable fixed-rate bonds. For the sake of brevity, I’ll
skip the description of the CallableBond
class. (To be specific, the
class name should be CallableFixedRateBond
; but that would get old
very quickly here, so please allow me to use the shorter name.)
Instead, I’ll just show its inner arguments
and results
classes,
which act as its interface with the pricing engine and which you can
see in the listing below together with the corresponding engine
class. If you’re interested in a complete implementation, you can look
for it in QuantLib’s experimental folder.
Now, let’s move into engine territory. In order to implement the
behavior of the instrument, we’ll need a discretized asset; namely,
the DiscretizedCallableBond
class, shown in the next listing.
To prevent much aggravation, its constructor takes and stores an
instance of the arguments
class. This avoids having to spell out the
list of needed data in at least three places: the declaration of the
data members, the constructor, and the client code that instantiates
the discretized asset. Besides the arguments
instance, the
constructor is also passed a reference date and a day counter that are
used in its body to convert the several bond dates into corresponding
times. (The conversion is somewhat verbose, which suggests that we
might be missing an abstraction here. However, “time converter” sounds
a bit too vague. If you find it, please do let me know. The thing has
been bugging me for a while.)
Next comes the required DiscretizedAsset
interface. The
mandatoryTimes
method collects the redemption time, the coupon
times, and the callability times filtering out the negative ones; and
the reset
method resizes the array of the values, sets each one to
the redemption amount, and proceeds to perform the needed
adjustments—that is, the more interesting part of the class.
Being rather specialized, it is pretty unlikely that this class will
be composed with others; therefore, it doesn’t really matter in this
case whether the adjustments go into preAdjustValuesImpl
or
postAdjustValuesImpl
. However, for sake of illustration, I’ll
separate the callability from the coupon payments and manage them as
pre- and post-adjustment, respectively.
The preAdjustValuesImpl
loops over the callability times, checks
whether any of them equals the current time, and calls the
applyCallability
} method if this is the case. The
postAdjustValuesImpl
does the same, but checking the coupon times
and calling the addCoupon
method instead.
The applyCallability
method is passed the index of the callability
being exercised; it checks its type (both callable and puttable bonds
are supported) and sets the value at each node to the value after
exercise. The logic is simple enough: at each node, given the
estimated value of the rest of the bond (that is, the current asset
value) and the exercise premium, the issuer will choose the lesser of
the two values while the holder will choose the greater. The
addCoupon
method is much simpler, and just adds the coupon amount to
each of the values.
As you might have noticed, this class assumes that the exercise dates coincide with the coupon dates; it won’t work if an exercise date is a few days before a coupon payment (the coupon amount would be added to the asset values before the exercise condition is checked). Of course, this is often the case, and it should be accounted for. Currently, the library implementation sidesteps the problem by adjusting each exercise date so that it equals the nearest coupon date. A better choice would be to detect which coupons are affected; each of them would be put into a new asset, rolled back until the relevant exercise time, and added after the callability adjustment.
Finally, the listing below shows the TreeCallableBondEngine
class.
Its constructor takes and stores a handle to a short-rate model that will provide the lattice, the total number of time steps we want the lattice to have, and an optional reference date and day counter; the body just registers to the handle.
The calculate
method is where everything happens. By the time it is
called, the engine arguments have been filled by the instrument, so
that base is covered; the other data we need are a date and a day
counter for time conversion. Not all short-rate models can provide
them, so, in a boring few lines of code not shown here, the engine
tries to downcast the model to some specific class that does; if it
fails, it falls back to using the ones optionally passed to the
constructor.
At that point, the actual calculations can begin. The engine instantiates the discretized bond, asks it for its mandatory times, and uses them to build a time grid; then, the grid is passed to the model which returns a corresponding lattice based on the short-rate dynamics. All that remains is to initialize the bond at its redemption time (which in the current code is recalculated explicitly, but could be retrieved as the largest of the mandatory times), roll it back to the present time, and read its value.