Chapter 7, part 2 of 6: examples of discretized assets
Hello everybody.
This week, the second part of a series on the QuantLib tree framework that started in the previous post. As usual, it’s taken from my book.
In case you missed it: we set the dates for my next Introduction to QuantLib Development course, which will be in London from June 29th to July 1st. More info at this link. You can get an early-bird discount until April 30th.
Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the buttons for that are in the footer. Also, make sure to check my Training page.
Example: discretized bonds
Vanilla bonds (zero-coupon, fixed-rate and floating-rate) are simple enough to work as first examples, but at the same time provide a range of features large enough for me to make a few points.
Well, maybe not the zero-coupon bond. It’s probably the simplest possible asset (bar one with no payoff) so it’s not that interesting on its own; however, it’s going to be useful as a helper class in the examples that follow, since it provides a way to estimate discount factors on the lattice.
Its implementation is shown in the following listing.
It defines no mandatory times (because it will be happy to be
initialized at any time you choose), it performs no adjustments
(because nothing ever happens during its life), and its reset
method
simply fills each value in the array with one unit of currency. Thus,
if you initialize an instance of his class at a time \( T \) and
roll it back on a lattice until an earlier time \( t \), the array
values will then equal the discount factors from \( T \) to \( t
\) as seen from the corresponding lattice nodes.
Things start to get more interesting when we turn to fixed-rate bonds. QuantLib doesn’t provide discretized fixed-rate bonds at this time; the listing below shows a simple implementation which works as an example, but should still be improved to enter the library.
The constructor takes and stores a vector of times holding the payment
schedule, the vector of the coupon amounts, and the amount of the
redemption. Note that the type of the arguments is different from what
the corresponding instrument class is likely to store (say, a vector
of CashFlow
instances); this implies that the pricing engine will
have to perform some conversion work before instantiating the
discretized asset.
The presence of a payment schedule implies that, unlike the
zero-coupon bond above, this bond cannot be instantiated at just any
time; and in fact, this class implements the mandatoryTimes
method
by returning the vector of the payment times. Rollback on the lattice
will have to stop at each such time, and initialization will have to
be performed at the maturity time of the bond, i.e., the latest of the
returned times. When one does so, the reset
method will fill each
value in the array with the redemption amount and then call the
adjustValues
method, which will take care of the final coupon.
In order to enable adjustValues
to do so, this class overrides the
virtual postAdjustValuesImpl
method. (Why the post- version of the
method and not the pre-, you say? Bear with me a bit longer: all
will become clear.) The method loops over the payment times and
checks, by means of the probably poorly named isOnTime
method,
whether any of them equals the current asset time. If this is the
case, we add the corresponding coupon amount to the asset values. For
readability, the actual work is factored out in the addCoupon
method. The coupon amounts will be automatically discounted as the
asset is rolled back on the lattice.
Finally, let’s turn to the floating-rate bond, shown in the listing that follows; this, too, is a simplified implementation.
The constructor takes and stores the vector of payment times, the vector of fixing times, and the notional of the bond; there are no coupon amounts, since they will be estimated during the calculation. For simplicity of implementation, we’ll assume that the accrual time for the i-th coupon equals the time between its fixing time and its payment time.
The mandatoryTimes
method returns the union of payment times and
fixing times, since we’ll need to work on both during the
calculations. The reset
method is similar to the one for fixed-rate
bonds, and fills the array with the redemption value (which equals the
notional of the bond) before calling adjustValues
.
Adjustment is performed in the overridden preAdjustValuesImpl
method. (Yes, the pre- version. Patience.) It loops over the fixing
times, checks whether any of them equals the current time, and if so
calls addCoupon
method.
Now, like the late Etta James in one of her hits, you’d be justified in shouting “Stop the wedding”. Of course the coupon should be added at the payment date, right? Well, yes; but the problem is that we’re going backwards in time. In general, at the payment date we don’t have enough information to add the coupon; it can only be estimated based on the value of the rate at an earlier time that we haven’t yet reached. Therefore, we have to keep rolling back on the lattice until we get to the fixing date, at which point we can calculate the coupon amount and add it to the bond. In this case, we’ll have to take care ourselves of discounting from the payment date, since we passed that point already.
That’s exactly what the addCoupon
method does. First of all, it
instantiates a discount bond at the payment time \( T \) and rolls
it back to the current time, i.e., the fixing date \( t \), so that
its value equal at the \( j \)-th node the discount factors \( D_j
\) between \( t \) and \( T \). From those, we could estimate the
floating rates \( r_j \) (since it must hold that \( 1 + r_j(T-t) =
1/D_j \)) and then the coupon amounts; but with a bit of algebra, we
can find a simpler calculation. The coupon amount \( C_j \) is given
by \( Nr_j(T-t) \), with \( N \) being the notional; and since the
relation above tells us that \( r_j(T-t) = 1/D_j - 1 \), we can
substitute that to find that \( C = N(1/D_j - 1) \). Now, remember
that we already rolled back to the fixing date, so if we add the
amount here we also have to discount it because it won’t be rolled
back from the payment date. This means that we’ll have to multiply it
by \( D_j \), and thus the amount to be added to the \( j \)-th
value in the array is simply \( C_j = N(1/D_j - 1)D_j = N(1-D_j)
\). The final expression is the one that can be seen in the
implementation of addCoupon
.
As you probably noted, the above hinges on the assumption that the
accrual time equals the time between payment and fixing time. If this
were not the case, the calculation would no longer simplify and we’d
have to change the implementation; for instance, we might instantiate
a first discount bond at the accrual end date to estimate the floating
rate and the coupon amount, and a second one at the payment date to
calculate the discount factors to be used when adding the coupon
amount to the bond value. Of course, the increased accuracy would
cause the performance to degrade since addCoupon
would roll back two
bonds, instead of one. You can choose either implementation based on
your requirements.
Example: discretized option
Sorry to have kept you waiting, folks. Here is where I finally explain
the pre- vs post-adjustment choice, after the previous example helped
me put my ducks in a row. I’ll do so by showing an example of an asset
class (the DiscretizedOption
class, shown in the listing below) that
can be used to wrap an underlying asset and obtain an option to enter
the same: for instance, it could take a swap and yield a swaption. The
implementation shown here is a slightly simplified version of the one
provided by QuantLib, since it assumes a Bermudan exercise (or
European, if one passes a single exercise time). Like the
implementation in the library, it also assumes that there’s no premium
to pay in order to enter the underlying deal.
Onwards. The constructor takes and, as usual, stores the underlying asset and the exercise times; nothing to write much about.
The mandatoryTimes
method takes the vector of times required by the
underlying and adds the option’s exercise times. Of course, this is
done so that both the underlying and the option can be priced on the
same lattice; the sequence of operations to get the option price will
be something like:
in which, first, we instantiate both underlying and option and retrieve the mandatory times from the latter; then, we create the lattice and initialize both assets (usually at different times, e.g., the maturity date for a swap and the latest exercise date for the corresponding swaption); and finally, we roll back the option and get its price. As we’ll see in a minute, the option also takes care of rolling the underlying back as needed.
Back to the class implementation. The reset
method performs the
sanity check that underlying and option were initialized on the same
lattice, fills the values with zeroes (what you end up with if you
don’t exercise), and then calls adjustValues
to take care of a
possible exercise.
Which brings us to the crux of the matter, i.e., the
postAdjustValuesImpl
method. The idea is simple enough: if we’re on
an exercise time, we check whether keeping the option is worth more
than entering the underlying asset. To do so, we roll the underlying
asset back to the current time, compare values at each node, and set
the option value to the maximum of the two; this latest part is
abstracted out in the applyExerciseCondition
method.
The tricky part of the problem is that the underlying might need to perform an adjustment of its own when rolled back to the current time. Should this be done before or after the option looks at the underlying values?
It depends on the particular adjustment. Let’s look at the bonds in
the previous example. If the underlying is a discretized fixed-rate
bond, and if the current time is one of its payment times, it needs to
adjust its values by adding a coupon. This coupon, though, is being
paid now and thus is no longer part of the asset if we exercise and
enter it. Therefore, the decision to exercise must be based on the
bond value without the coupon; i.e., we must call the
applyExerciseCondition
method before adjusting the underlying.
The discretized floating-rate bond is another story. It adjusts the
values if the current time is one of its fixing times; but in this
case the corresponding coupon is just starting and will be paid at the
end of the period, and so must be added to the bond value before we
decide about exercise. Thus, the conclusion is the opposite: we must
call applyExerciseCondition
after adjusting the underlying.
What should the option do? It can’t distinguish between the two cases,
since it doesn’t know the specific behavior of the asset it was
passed; therefore, it lets the underlying itself sort it out. First,
it rolls the underlying back to the current time, but without
performing the final adjustment (that’s what the partialRollback
method does); instead, it calls the underlying’s preAdjustValues
method. Then, if we’re on an exercise time, it performs its own
adjustment; and finally, it calls the underlying’s postAdjustValues
method.
This is the reason the DiscretizedAsset
class has both a
preAdjustValues
and a postAdjustValues
method; they’re there so
that, in case of asset composition, the underlying can choose on which
side of the fence to be when some other adjustment (such as an
exercise) is performed at the same time. In the case of our previous
example, the fixed-rate bond will add its coupon in postAdjustValues
and have it excluded from the future bond value, while the
floating-rate bond will add its coupon in preAdjustValues
and have
it included.
Unfortunately, this solution is not very robust. For instance, if the exercise dates were a week or two before the coupon dates (as is often the case) the option would break for fixed-rate coupons, since it would have no way to stop them from being added before the adjustment. The problem can be solved: in the library, this is done for discretized interest-rate swaps by adding fixed-rate coupons on their start dates, much in the same way as floating-rate coupons. Another way to fix the issue would be to roll the underlying back only to the date when it’s actually entered, then to make a copy of it and roll the copy back to the exercise date without performing any adjustment. Both solutions are somewhat clumsy at this time; it would be better if QuantLib provided some means to do it more naturally.