Hello, dear reader.

Today’s post was originally published in the September 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.

Adding a new cash flow to QuantLib, part II

Welcome back. So, where were we?

A quick recap

In the article in the previous issue, I described part of the class hierarchy that has CashFlow at its root and models different types of cash flows and coupons; in particular, and more to our purpose, floating-rate coupons.

And in case I need to refresh said purpose in your memory: we want to implement a coupon whose rate must be interpolated between the fixings of two quoted indexes; such a stub can be sometimes found at the beginning of a floating-rate bond or an interest-rate swap. For instance, it might be a four-months coupon whose rate should be interpolated between the quoted three-months and six-months Euribor fixings, followed by semiannual coupons paying the six-months Euribor rate.

Now, given the coupon we want to interpolate and given the existing hierarchy (sketched in the figure below), the question becomes: where in this hierarchy can we find a peg on which to hang the new class?

A sample case

Instead of just pondering the possibilities, we can write some sample code and try them out. Since, as I said, you can download the complete code, here I’ll skip a couple of utility functions in the interest of brevity (the first displays data on a series of cash flows and the second calculates the interpolated rate between two fixings) and show instead the contents of the main function, which sets up the example I mentioned above: it creates a semiannual schedule with an initial short stub spanning four months, from April to August 2023, as well as instances of the three-months and six-months Euribor indexes, each with its forecast curve and a few past fixings stored in memory.

   auto today = Date(18, March, 2024);
   Settings::instance().evaluationDate() = today;
   IborCoupon::Settings::instance().createIndexedCoupons();

   Schedule schedule =
      MakeSchedule()
         .from(Date(8, April, 2023))
         .withFirstDate(Date(8, August, 2023))
         .to(Date(8, February, 2028))
         .withFrequency(Semiannual)
         .withCalendar(TARGET())
         .backwards();

   auto forecastCurve3m = ext::make_shared<FlatForward>(
      today, 0.030, Actual360());

   auto index3m = ext::make_shared<Euribor3M>(
      Handle<YieldTermStructure>(forecastCurve3m));
   index3m->addFixing(Date(5, April, 2023), 0.03055);

   auto forecastCurve6m = ext::make_shared<FlatForward>(
      today, 0.035, Actual360());

   auto index6m = ext::make_shared<Euribor6M>(
      Handle<YieldTermStructure>(forecastCurve6m));
   index6m->addFixing(Date(5, April, 2023), 0.03339);
   index6m->addFixing(Date(4, August, 2023), 0.0394);
   index6m->addFixing(Date(6, February, 2024), 0.03922);

How not to do it

The first bond we create based on those objects, bond0, is not correct. The code builds it by creating a sequence of coupons paying six-months Euribor and passing them unchanged to the bond constructor.

   int settlementDays = 3;
   double faceAmount = 1000000.0;
   auto dayCounter = index6m->dayCounter();

   Leg coupons = IborLeg(schedule, index6m)
                    .withNotionals(faceAmount)
                    .withPaymentDayCounter(dayCounter);

   Date issueDate = schedule[0];

   Bond bond0(settlementDays, schedule.calendar(),
              issueDate, coupons);

Inspecting the bond coupons we get the results below: the coupons from the second onwards are correct, but the first coupon needs to be changed so that it interpolates between the three-months and six-months index fixings.

date nominal rate amount
2023-08-08 1000000.00 3.339 11037.25
2024-02-08 1000000.00 3.940 20137.78
2024-08-08 1000000.00 3.922 19827.89
2025-02-10 1000000.00 3.532 18247.83
2025-08-08 1000000.00 3.531 17557.66
2026-02-09 1000000.00 3.532 18148.84
2026-08-10 1000000.00 3.531 17851.92
2027-02-08 1000000.00 3.531 17853.66
2027-08-09 1000000.00 3.531 17851.92
2028-02-08 1000000.00 3.531 17951.76
2028-02-08 n/a n/a 1000000.00

First try: a special IBOR coupon

Your first thought, and a perfectly reasonable one, might be that this coupon is irregular but still a Euribor coupon after all. I won’t argue with that; in fact, I’ll go ahead and implement a new class inheriting from IborCoupon. Let’s call it InterpolatedIborCoupon1.

   class InterpolatedIborCoupon1 : public IborCoupon {
      ext::shared_ptr<IborIndex> otherIndex_;
    public:
      InterpolatedIborCoupon1(
         const Date& paymentDate,
         Real nominal,
         const Date& startDate,
         const Date& endDate,
         Natural fixingDays,
         const ext::shared_ptr<IborIndex>& shorterIndex,
         const ext::shared_ptr<IborIndex>& longerIndex)
      : IborCoupon(paymentDate,
                   nominal,
                   startDate,
                   endDate,
                   fixingDays,
                   longerIndex),
        otherIndex_(shorterIndex) {
         registerWith(otherIndex_);
      }
      Rate indexFixing() const override {
         return interpolatedRate(
            fixingDate(), accrualStartDate(),
            accrualEndDate(), otherIndex_, iborIndex());
      }
      ext::shared_ptr<IborIndex> shorterIndex() const {
         return otherIndex_;
      }
      ext::shared_ptr<IborIndex> longerIndex() const {
         return iborIndex();
      }
   };

The implementation turns out to be simple: we need to declare a constructor and to override the indexFixing method from the base class so that it returns the interpolated fixing (the other methods delegate to this one).

However, there are a couple of issues with this. Mind you, the class works: when we create another bond (bond1 in the code) after replacing the first coupon and print out its cash flows, we get the results in the next table, where the amount paid by the first coupon is the one we wanted. But there are hints that our mental model might not be solid.

   auto stub =
      ext::dynamic_pointer_cast<IborCoupon>(coupons[0]);

   auto newStub1 =
      ext::make_shared<InterpolatedIborCoupon1>(
         stub->date(), stub->nominal(),
         stub->accrualStartDate(), stub->accrualEndDate(),
         stub->fixingDays(), index3m, index6m);
   newStub1->setPricer(
      ext::make_shared<BlackIborCouponPricer>());

   coupons[0] = newStub1;

   Bond bond1(settlementDays, schedule.calendar(),
              issueDate, coupons);
date nominal rate amount
2023-08-08 1000000.00 3.141 10384.19
2024-02-08 1000000.00 3.940 20137.78
2024-08-08 1000000.00 3.922 19827.89
2025-02-10 1000000.00 3.532 18247.83
2025-08-08 1000000.00 3.531 17557.66
2026-02-09 1000000.00 3.532 18148.84
2026-08-10 1000000.00 3.531 17851.92
2027-02-08 1000000.00 3.531 17853.66
2027-08-09 1000000.00 3.531 17851.92
2028-02-08 1000000.00 3.531 17951.76
2028-02-08 n/a n/a 1000000.00

A shaky implementation?

First: the constructor takes the date information for the coupon, as well as the two indexes we need to interpolate. The base-class constructor? It asks for the same date information, and for the index it’s supposed to pay. We have two, and which one we should pass is anybody’s guess; I’m passing the longer one here, but it’s entirely arbitrary.

This causes some of the methods in the class interface to return an answer which doesn’t make a lot of sense, or is just wrong. In our example, calling the index() method of the coupon will return the 6-months Euribor and fail to mention the second index and the interpolation being performed; other methods will fail in a similar way. And unfortunately, overriding index is not possible; it is virtual, but its signature declares that it returns a single index instance, so we can’t return two.

The problem, as I mentioned in the previous article, is that the IborCoupon class and its base FloatingRateCoupon class assume a single underlying index. Our coupon looks a lot like an IBOR coupon, but it doesn’t match its interface.

For a deeper discussion on this, search the Internet for “circle-ellipse problem”. Is a circle also an ellipse? The answer is not obvious in object-oriented programming. It’s all about the interfaces, both declared and inherited, that an object is supposed to provide.

The issue can be mitigated somehow by declaring other methods, like shorterIndex and longerIndex in the example, that match the coupon better. But the other methods remain.

I mentioned a couple of issues. The second is that this coupon works because the pricer we’re using doesn’t happen to call any of its methods besides indexFixing. If someone wrote another pricer that did, the problem above would suddenly become a lot less abstract.

Second try: an entire new kind of coupon

Ok, what if we choose not to consider this as an IBOR coupon? In this case, we have to go up the inheritance chain (shown in the figure at the beginning of the post) to find another base class. The next class up is FloatingRateCoupon, but it relies on a single index and would have the same interface mismatch we found with IborCoupon. This leaves us with Coupon.

As I’m fond of saying, designing software is about tradeoffs. The Coupon class is high enough in the hierarchy—that is, it’s generic enough—that it doesn’t force on us any interface choice, as IborCoupon did. However, this also means that we’ll have to implement quite a bit of functionality that IborCoupon already provided.

Let’s call our second attempt InterpolatedIborCoupon2.

   class InterpolatedIborCoupon2 : public Coupon {
      ext::shared_ptr<IborIndex> shorterIndex_;
      ext::shared_ptr<IborIndex> longerIndex_;
      mutable Rate rate_;
      Date fixingDate_;
    public:
      InterpolatedIborCoupon2(
         const Date& paymentDate,
         Real nominal,
         const Date& startDate,
         const Date& endDate,
         Natural fixingDays,
         const ext::shared_ptr<IborIndex>& shorterIndex,
         const ext::shared_ptr<IborIndex>& longerIndex)
      : Coupon(paymentDate, nominal, startDate, endDate),
        shorterIndex_(shorterIndex),
        longerIndex_(longerIndex) {
         registerWith(shorterIndex_);
         registerWith(longerIndex_);
         fixingDate_ =
            shorterIndex_->fixingCalendar().advance(
               startDate, -fixingDays, Days);
      }
      void performCalculations() const override {
         rate_ = interpolatedRate(
            fixingDate_, accrualStartDate(),
            accrualEndDate(), shorterIndex_, longerIndex_);
      }
      Rate rate() const override {
         calculate();
         return rate_;
      }
      Real amount() const override {
         return nominal() * rate() * accrualPeriod();
      }
      Real accruedAmount(const Date& d) const override {
         return nominal() * rate() *
                dayCounter().yearFraction(
                   accrualStartDate(), d);
      }
      DayCounter dayCounter() const override {
         return shorterIndex_->dayCounter();
      }
      ext::shared_ptr<IborIndex> shorterIndex() const {
         return shorterIndex_;
      }
      ext::shared_ptr<IborIndex> longerIndex() const {
         return longerIndex_;
      }
   };

Not surprisingly, its constructor takes the same arguments as our previous class; but this time, we’re only passing some of the information (the nominal and the coupon dates) to the base class constructor. The two indexes are stored in data members of this class, which allows it to provide two reasonable inspectors for them.

We also need to implement a number of calculations. First of all, the rate, which (as before) is given by a call to the interpolateRate function. It is calculated in the performCalculation method, because cash flows participate in the Observer pattern (remember the November 2023 issue?) and this is how we ensure that they are cached between notifications. If you want details, chapter 2 of Implementing QuantLib has plenty of them. The rate method ensures that calculations are up to date and then returns the rate.

Besides the rate, though, there are other calculations required by the interface of Coupon; namely, the total amount and the accrued amount at a given date, returned in the obvious way. We also need to return the day counter, but we delegate that to one of the indexes.

And once we have the class, we can use it to replace the first coupon and create another bond (bond2 in the code); printing out its cash flows gives the same results already shown for the previous bond. The class works as we wanted.

   auto newStub2 =
      ext::make_shared<InterpolatedIborCoupon2>(
         stub->date(), stub->nominal(),
         stub->accrualStartDate(), stub->accrualEndDate(),
         stub->fixingDays(), index3m, index6m);

   coupons[0] = newStub2;

   Bond bond2(settlementDays, schedule.calendar(),
              issueDate, coupons);

So, is it better or worse than the first? It’s not easy to say, and as a matter of fact we might be going into matters of mere taste. The advantage of this class is an interface that won’t risk confusing the users and returning wrong results. The disadvantage is that it needs to reimplement a bunch of virtual methods; and also, do we really want this coupon not to be an IBOR coupon? Your mileage might vary.

Third try: a special IBOR index

But wait, what if we think laterally? What if we circle back to IborCoupon and provide a single index which is the interpolation of the two given indexes? This results in the InterpolatedIborIndex class; but (spoiler alert) it doesn’t turn out to be a great idea.

   class InterpolatedIborIndex : public IborIndex {
      Date couponStartDate_, couponEndDate_;
      ext::shared_ptr<IborIndex> shorterIndex_;
      ext::shared_ptr<IborIndex> longerIndex_;
    public:
      InterpolatedIborIndex(
         const Date& couponStartDate,
         const Date& couponEndDate,
         const ext::shared_ptr<IborIndex>& shorterIndex,
         const ext::shared_ptr<IborIndex>& longerIndex)
      : IborIndex(*longerIndex),
        couponStartDate_(couponStartDate),
        couponEndDate_(couponEndDate),
        shorterIndex_(shorterIndex),
        longerIndex_(longerIndex) {
         registerWith(shorterIndex_);
         registerWith(longerIndex_);
      }
      Rate fixing(const Date& fixingDate,
                  bool = false) const override {
         return interpolatedRate(
            fixingDate, couponStartDate_, couponEndDate_,
            shorterIndex_, longerIndex_);
      }
      Rate forecastFixing(
         const Date& fixingDate) const override {
         return fixing(fixingDate);
      }
      Rate pastFixing(
         const Date& fixingDate) const override {
         return fixing(fixingDate);
      }
   };

It is inherited from IborIndex, which makes it possible to pass it with IborCoupon, and overrides its relevant methods so that they return the interpolated rate. Its constructor takes the two indexes, stores them, and sets things up so that any notifications from them are forwarded. However, that’s not all the information that the constructor must take. The start and end date of the coupon are also needed for the calculation, and since they can’t be passed to the fixing method (whose interface is defined in the base class) they must be passed to the constructor and stored.

This makes the index not quite as generic as I would like; that is, we can’t create an instance of this index based on, say, 3-months and 6-months Euribor and use it for different bonds or coupons. Any instance is tied to a specific coupon and can only be used with it.

The real deal breaker, though, is that this class doesn’t always work. It does in this case: after creating an instance of the index and the corresponding Euribor coupon, the code builds yet another bond (bond3). If we print its cash flows, we get again the results in the last table above.

   auto newIndex = ext::make_shared<InterpolatedIborIndex>(
      stub->accrualStartDate(), stub->accrualEndDate(),
      index3m, index6m);

   auto newStub3 = ext::make_shared<IborCoupon>(
      stub->date(), stub->nominal(),
      stub->accrualStartDate(), stub->accrualEndDate(),
      stub->fixingDays(), newIndex);
   newStub3->setPricer(
      ext::make_shared<BlackIborCouponPricer>());

   coupons[0] = newStub3;

   Bond bond3(settlementDays, schedule.calendar(),
              issueDate, coupons);

However, this only happens because the coupon was fixed in the past. If it was to be fixed in the future and therefore forecast, it would give the wrong result, because IborCoupon cheats. For the sake of performance, it’s declared as a friend of IborIndex and calls a private, non-virtual method forecastFixing instead of the one we could override in this code. (Years ago, this was necessary to be able to bootstrap a curve in real time from Excel. It might not be necessary now, but for the time being it’s in the code.)

Summary

Among the three alternatives I described, there seems to be a clear loser (the interpolated index) but not a clear winner. The one objective consideration might be that the first class is less robust against changes in the pricer implementation; other differences might be more subjective. So, as usual, no definitive design answers from yours truly except “it depends.”

Thanks for reading, and see you next time!