Welcome back.

This post was originally published in the May 2024 issue of Wilmott Magazine. The full source code is available on my Tutorial page, together with code from other articles and, when available, either the articles themselves or corresponding blog posts like this one.

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.

Cash flows and bonds in QuantLib

Hello again. In my previous article, we got as far as building sequences of regular dates (schedules, in QuantLib lingo) and I hinted that they could be used in turn to build sequences of coupons.

In this installment, we’ll do just that; and furthermore, we’ll see how to use those coupons to build a couple of simple bonds and a more tricky one.

As usual, the C++ code I’ll describe is runnable (given a working QuantLib installation). I’ll get a few things out of the way first, namely, the includes and a helper function to output information on a series of cashflows:

#include <ql/instruments/bond.hpp>
#include <ql/instruments/bonds/fixedratebond.hpp>
#include <ql/instruments/bonds/amortizingfixedratebond.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/time/schedule.hpp>
#include <ql/settings.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <iostream>
#include <iomanip>

using namespace QuantLib;

void display(const Leg& cashflows) {
   std::cout << std::setw(10) << "date";
   std::cout << std::setw(12) << "nominal";
   std::cout << std::setw(10) << "rate";
   std::cout << std::setw(12) << "amount";
   std::cout << std::endl;

   std::cout << std::fixed << std::setprecision(2);

   for (auto c : cashflows) {
      std::cout << io::iso_date(c->date());
      auto coupon = ext::dynamic_pointer_cast<Coupon>(c);
      if (coupon) {
         std::cout << std::setw(12) << coupon->nominal();
         std::cout << std::setw(10) << coupon->rate();
      } else {
         std::cout << std::setw(12) << "n/a";
         std::cout << std::setw(10) << "n/a";
      }
      std::cout << std::setw(12) << c->amount();
      std::cout << std::endl;
   }
   std::cout << std::endl;
};

A simple fixed-rate bond

As an example, we’re going to create a simple 5-years bond paying 3% semiannually. The first thing we do is a call to MakeSchedule, like the ones I described in the previous issue; it takes the start and end date for the coupons, their frequency, and some other information such as the calendar used to adjust dates when they fall on a holiday.

int main() {

   Settings::instance().evaluationDate() =
       Date(15, January, 2024);

   Schedule schedule =
       MakeSchedule()
       .from(Date(8, February, 2021))
       .to(Date(8, February, 2026))
       .withFrequency(Semiannual)
       .withCalendar(TARGET())
       .withConvention(Following)
       .backwards();

As a reminder, the understanding is that the first date in the schedule is the start of the first coupon; the second date is both the end of the first coupon and the start of the second; the third date is the end of the second coupon and the start of the third; until we get to the last date, which is the end of the last coupon.

The actual task of building the coupons is done by FixedRateLeg, another function provided by the library, which is the next thing we call (after defining some other bits of information such as the day-count convention for accruing the coupon rate). It takes the schedule, to be used as described above, and the other data we need.

   int settlementDays = 3;
   auto issueDate =
       Date(5, February, 2021);
   auto dayCount =
       Thirty360(Thirty360::BondBasis);

   Leg fixedCoupons =
      FixedRateLeg(schedule)
       .withCouponRates(0.03, dayCount)
       .withNotionals(10000.0);

FixedRateLeg, like MakeSchedule, has a number of methods we can chain (remember last article?) to specify the coupons. Here we’re doing the base minimum; we’re passing the rate to be paid by the coupons, together with their day-count convention, and the notional used for the calculation of the interest. If needed, we could pass additional specifications: for instance, a payment lag with respect to the end of the coupons, or a number of ex-coupon days.

The name of the return type, Leg, is probably more suited to swaps than to bonds. Like many things in the library, which grew bit by bit, it was defined in a particular context and it stuck. It’s not a class in its own right, but an alias for std::vector<ext::shared_ptr<CashFlow>> which would be a mouthful to use; CashFlow is the base class for cash flows, and derived classes model both coupons (an interest paid based on some kind of rate, fixed or otherwise) and more simple payments like the final redemption of a bond.

The last step, in order to get a working bond instance, is passing the coupons and some additional information to the constructor of the Bond class. The latter not only stores them, but also figures out that the bond must make a final payment to reimburse its notional. We can see it by asking the bond for its cash flows (via its cashflows method) and passing them to the display function defined at the beginning of the code (and not worth your time going over, since it just deals with tabulating), which outputs the data in the table below; the last row corresponds to the redemption.

   auto bond1 =
       Bond(settlementDays,
            TARGET(),
            issueDate,
            fixedCoupons);

   display(bond1.cashflows());
date nominal rate amount
2021-08-09 10000.00 0.03 150.83
2022-02-08 10000.00 0.03 149.17
2022-08-08 10000.00 0.03 150.00
2023-02-08 10000.00 0.03 150.00
2023-08-08 10000.00 0.03 150.00
2024-02-08 10000.00 0.03 150.00
2024-08-08 10000.00 0.03 150.00
2025-02-10 10000.00 0.03 151.67
2025-08-08 10000.00 0.03 148.33
2026-02-09 10000.00 0.03 150.83
2026-02-09 n/a n/a 10000.00

Finally, we can check that the bond is working by asking, for instance, what would be the yield for a clean price of 98; the result is about 4.02%, in case you’re curious.

   Rate y = bond1.yield({98.0, Bond::Price::Clean},
                        dayCount,
                        Compounded, Semiannual);

   std::cout << std::setprecision(4)
             << io::rate(y)
             << std::endl << std::endl;

A shortcut to the same bond

As you might have guessed, a fixed-rate bond is common enough that we have a more convenient way to instantiate it. Instead of the two separate calls to FixedRateLeg and the Bond constructor, we can call the constructor of the derived FixedRateBond class and pass the same information. Displaying the cash flows of the resulting bond shows that they are the same as the ones in the table I’ve already shown above.

   auto bond1b =
       FixedRateBond(
          settlementDays,
          10000.0,
          schedule,
          {0.03},
          dayCount);

   display(bond1b.cashflows());

A note on overloading

If you were to head over to GitHub and look at changes in the FixedRateBond class across versions, you would see that it used to have multiple constructors, but most of them were deprecated and now only one remains. Is that some kind of design principle that should be followed?

Not really, no. In this case, it was a matter of balancing convenience of use in C++ against other languages, such as Python, to which the library is exported. Overloaded constructors would be more convenient in C++, as they would provide shortcuts to different use cases. However, with our current toolchain (the excellent SWIG) they would make it impossible to use keyword arguments in Python; instead, with a single constructor, we can enable them in our Python wrappers and allow writing calls like

bond = FixedRateBond(
    settlement_days,
    10000.0,
    schedule,
    [0.03],
    day_count,
    firstPeriodDayCounter=another_day_count,
)

where we don’t need to pass (like in C++) all the default parameters defined between the two day counts by the constructor.

So, who should win? C++ or Python? I don’t have any figures about the number of people that use QuantLib in either language, but in this case C++ could graciously yield without suffering much harm; as we’ve seen, FixedRateLeg already gave it a pretty convenient way to instantiate various use cases. As usual, the mileage in your code might vary.

An amortizing fixed-rate bond

I mentioned above that FixedRateLeg has a number of additional methods I didn’t show; moreover, the ones I did show are overloaded and can take additional information. In the next call, for instance, the code passes a sequence of different notionals for the generated coupons, starting from 10000 in the first year and decreasing by 2000 each year; that is, every second coupon, since the schedule is semiannual.

   Leg amortizingCoupons =
      FixedRateLeg(schedule)
       .withCouponRates(0.03, dayCount)
       .withNotionals({
           10000.0, 10000.0,
           8000.0, 8000.0,
           6000.0, 6000.0,
           4000.0, 4000.0,
           2000.0, 2000.0
       });

In this case, passing the returned coupons to the Bond constructor results in an amortizing bond; displaying its cash flows gives the output you can see in the next table. As before, the machinery in the constructor looked at the notionals of the coupons and figured out that, when the notional varies between two consecutive coupons, a notional payment needed to be inserted.

   auto bond2 =
       Bond(settlementDays,
            TARGET(),
            issueDate,
            amortizingCoupons);

   display(bond2.cashflows());
date nominal rate amount
2021-08-09 10000.00 0.03 150.83
2022-02-08 10000.00 0.03 149.17
2022-02-08 n/a n/a 2000.00
2022-08-08 8000.00 0.03 120.00
2023-02-08 8000.00 0.03 120.00
2023-02-08 n/a n/a 2000.00
2023-08-08 6000.00 0.03 90.00
2024-02-08 6000.00 0.03 90.00
2024-02-08 n/a n/a 2000.00
2024-08-08 4000.00 0.03 60.00
2025-02-10 4000.00 0.03 60.67
2025-02-10 n/a n/a 2000.00
2025-08-08 2000.00 0.03 29.67
2026-02-09 2000.00 0.03 30.17
2026-02-09 n/a n/a 2000.00

More cash flows and bonds

The library, of course, is not limited to fixed-rate coupons. For instance, a call like

    Leg cashflows =
        IborLeg(schedule, euribor6m)
        .withSpreads(0.001)
        .withNotionals(10000.0);

would generate coupons paying the fixings of the 6-months Euribor rate plus 10 basis points. You’ll forgive me for only showing this in passing; adding it as an example to the code would require setting up the index and its forecast curve, and might muddle it. The QuantLib site provides documentation for these coupons and more.

A tricky case: a payment-in-kind bond

In payment-in-kind (PIK) bonds, the interest accrued during a coupon period is not paid off but added to the bond as additional notional. This doesn’t fit very well the currently available coupon types, whose notional is supposed to be known upon construction, so we don’t have a function such as FixedRateLeg or IborLeg available.

However, for fixed-rate PIK bonds we can work around the limitation. The idea is to build the coupons one by one, each time using information from the previous coupon.

The first one, pikCoupons[0] in the code, uses the initial notional (10000). The first and second date in the schedule are the start and end date, the latter being also used as the payment date. The rate and day count are the same as before, and will stay the same for all coupons.

   Leg pikCoupons(schedule.size()-1);

   pikCoupons[0] =
      ext::make_shared<FixedRateCoupon>(
         schedule[1],
         10000.0,
         0.03,
         dayCount,
         schedule[0],
         schedule[1]);

The remaining coupons are then built one by one, each time taking the amount from the previous coupon and adding it to the notional; in the code, you can see previous->nominal() + previous->amount() being passed as the notional for each new coupon (the call to dynamic_pointer_cast to define previous is needed because, as I mentioned, a leg is a vector of pointers to CashFlow, the base class, and we need the interface of the derived class Coupon to retrieve the notional.)

   for (Size i=1; i<pikCoupons.size(); ++i) {
      auto previous =
          ext::dynamic_pointer_cast<Coupon>(
             pikCoupons[i-1]);
      pikCoupons[i] =
         ext::make_shared<FixedRateCoupon>(
            schedule[i+1],
            previous->nominal() + previous->amount(),
            0.03,
            dayCount,
            schedule[i],
            schedule[i+1]);
   }

Finally, we can build a Bond instance by passing the list of coupons, and as in the previous cases we can display the resulting cash flows; you can look at them in the last table.

   auto bond3 =
       Bond(settlementDays,
            TARGET(),
            issueDate,
            pikCoupons);

   display(bond3.cashflows());

}
date nominal rate amount
2021-08-09 10000.00 0.03 150.83
2021-08-09 n/a n/a -150.83
2022-02-08 10150.83 0.03 151.42
2022-02-08 n/a n/a -151.42
2022-08-08 10302.25 0.03 154.53
2022-08-08 n/a n/a -154.53
2023-02-08 10456.78 0.03 156.85
2023-02-08 n/a n/a -156.85
2023-08-08 10613.64 0.03 159.20
2023-08-08 n/a n/a -159.20
2024-02-08 10772.84 0.03 161.59
2024-02-08 n/a n/a -161.59
2024-08-08 10934.43 0.03 164.02
2024-08-08 n/a n/a -164.02
2025-02-10 11098.45 0.03 168.33
2025-02-10 n/a n/a -168.33
2025-08-08 11266.78 0.03 167.12
2025-08-08 n/a n/a -167.12
2026-02-09 11433.90 0.03 172.46
2026-02-09 n/a n/a 11433.90

For each date before maturity, we see two opposite payments: the 3% interest payment (with a positive sign since it’s made to the holder of the bond) as well as a negative payment that models the same amount being immediately put by the holder into the bond. At maturity, we also have two payments; however, this time they are the 3% interest payment and the final reimbursement. Together, they give the final payment.

As before, we didn’t create the negative payments explicitly; the Bond constructor created them automatically to account for the change in notional between consecutive coupons. This feature was coded in order to generate amortizing payments in case of decreasing notionals, but it works in the opposite direction just as well.

Just a workaround

This works without problems only if the coupon rate is fixed. For floating-rate bonds, we can use the same workaround; but the resulting cash flows and the bond will need to be thrown away and rebuilt when the forecasting curve changes, because the coupon rates and therefore the notional of the following coupons will also change.

This prevents the idiomatic usage of the library, which would be to create the instrument and have it react automatically when market quotes change.

The actual solution would be to code another derived class to model the coupons of a PIK bond; in the implementation, each coupon after the first should stores a link to the previous one. It might be a nice exercise, if you’re familiar with the library and want to make a contribution. See you next time!