Chapter 2, part 2 of 4: Example
Greetings. This is the second in a series of four posts covering chapter 2 of the book; part 1, 3 and 4 can be found here, here and here. I look forward to your comments.
Registration for the next Introduction to QuantLib Development course is still open; it is the course that I teach based on the contents of the Implementing QuantLib blog and book, 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 buttons for that are in the footer. Also, make sure to check my Training page, even if you can’t make it to the next course.
Financial instruments and pricing engines
Example: interest-rate swap
In this post, I’ll show how a specific financial instrument can be implemented based on the facilities described in the previous one.
The chosen instrument is the interest-rate swap. As you surely know, it is a contract which consists in exchanging periodic cash flows. The net present value of the instrument is calculated by adding or subtracting the discounted cash-flow amounts depending on whether the cash flows are paid or received.
Not surprisingly, the swap is implemented as a new class deriving from
Instrument
. Its outline is shown in listing 2.4. (This
implementation is somewhat outdated. However, it is still used here
since it provides a simpler example.) It contains as data members the
objects needed for the calculations—namely, the cash flows on the
first and second leg and the yield term structure used to discount
their amounts—and two variables used to store additional
results. Furthermore, it declares methods implementing the
Instrument
interface and others returning the swap-specific
results. The class diagram of Swap
and the related classes is shown
in figure 2.1, at the bottom of the post.
Listing 2.4: Partial implementation of the Swap
class.
The fitting of the class to the Instrument
framework is done in
three steps, the third being optional depending on the derived
class. The first step is performed in the class constructor, which
takes as arguments (and copies into the corresponding data members)
the two sequences of cash flows to be exchanged and the yield term
structure to be used for discounting their amounts. The step itself
consists in registering the swap as an observer of both the cash flows
and the term structure. As previously explained, this enables them to
notify the swap and trigger its recalculation each time a change
occurs.
The second step is the implementation of the required interface.
The logic of the isExpired
method is simple enough; its body loops
over the stored cash flows checking their payment dates. As soon as it
finds a payment which still has not occurred, it reports the swap as
not expired. If none is found, the instrument has expired. In this
case, the setupExpired
method will be called. Its implementation
calls the base-class one, thus taking care of the data members
inherited from Instrument
; it then sets to 0 the swap-specific
results.
The last required method is performCalculations
. The calculation is
performed by calling two external functions from the Cashflows
class. (If you happen to feel slightly cheated, consider that the
point of this example is to show how to package calculations into a
class—not to show how to implement such calculations. Your curiosity
will be satisfied in a later chapter devoted to cash flows and related
functions.) The first one, namely, npv
, is a straightforward
translation of the algorithm outlined above: it cycles on a sequence
of cash flows adding the discounted amount of its future cash
flows. We set the NPV_
variable to the difference of the results
from the two legs. The second one, bps
, calculates the basis-point
sensitivity (BPS) of a sequence of cash flows. We call it once per leg
and store the results in the corresponding data members. Since the
result carries no numerical error, the errorEstimate_
variable is
set to Null<Real>()
—a specific floating-point value which is used as
a sentinel value indicating an invalid number. (NaN
might be a
better choice, but the means of detecting it are not portable. Another
possibility still to be investigated would be to use
boost::optional
.)
The third and final step only needs to be performed if—as in this
case—the class defines additional results. It consists in writing
corresponding methods (here, firstLegBPS
and secondLegBPS
) which
ensure that the calculations are (lazily) performed before returning
the stored results.
The implementation is now complete. Having been written on top of the
Instrument
class, the Swap
class will benefit from its code. Thus,
it will automatically cache and recalculate results according to
notifications from its inputs—even though no related code was written
in Swap
except for the registration calls.
Aside: handles and shared pointers
You might wonder why the Swap
constructor accepts the discount curve
as a handle (the smart pointer-to-pointer replacement I mentioned in
the previous post) and the cash flows as simple shared pointers. The
reason is that we might decide to switch to a different curve, which
can be done by means of the handle, whereas the cash flows are part of
the definition of the swap and are thus immutable.
Further developments
You might have noticed a shortcoming in my treatment of the previous
example and of the Instrument
class in general. Albeit generic, the
Swap
class we implemented cannot manage interest-rate swaps in which
the two legs are paid in different currencies. A similar problem would
arise if the user were to add the values of two instruments whose
values are not in the same currency; the user would have to convert
manually one of the values to the currency of the other before adding
the two.
Such problems stem from a single weakness of the implementation: we
used the Real
type (i.e., a simple floating-point number) to
represent the value of an instrument or a cash flow. Therefore, such
results miss the currency information which is attached to them in the
real world.
The weakness might be removed if we were to express such results by
means of the Money
class. Instances of such class contain currency
information; moreover, depending on user settings, they are able to
automatically perform conversion to a common currency upon addition or
subtraction.
However, this would be a major change, affecting a large part of the code base in a number of ways. Therefore, it will need some serious thinking before we tackle it (if we do tackle it at all).
Another (and more subtle) shortcoming is that the Swap
class fails
to distinguish explicitly between two components of the abstraction it
represents. Namely, there is no clear separation between the data
specifying the contract (the cash-flow specification) and the market
data used to price the instrument (the current discount curve).
The solution is to store in the instrument only the first group of data (i.e., those that would be in its term sheet) and keep the market data elsewhere. (Beside being conceptually clearer, this would prove useful to external functions implementing serialization and deserialization of the instrument—for instance, to and from the FpML format.) The means to do this will be the subject of the next post.
Figure 2.1: Class diagram of the Swap
class.