Cash flows and bonds in QuantLib
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!