Skip to content
All library documents

Accruing Coupons on CORRA-Linked Floating-Rate Bonds

Article Quant Q&A · Author: Bill Qiu

Summary

The document investigates a small accrued-interest discrepancy when valuing a Canadian floating-rate note linked to CORRA in QuantLib. It describes a setup with an overnight index, a forecast curve, historical CORRA fixings, a spread, and quarterly coupon dates, then compares the clean price and accrued interest with Bloomberg. The reported clean price is close, while accrued interest differs slightly.

The central issue is whether the coupon should use a single fixing or compounded overnight rates. The questioner observes that the code appears to use the fixing at the start of the current coupon period and asks how overnight compounding should be handled. However, the document contains no accepted answer or resolution, so it does not establish the correct QuantLib configuration or the source of the discrepancy. The pricing example highlights that coupon conventions, fixing timing, day counts, and accrued-interest rules must be checked against the bond’s terms and market conventions before treating a platform comparison as definitive.

Key ideas

  • The example values a CORRA-linked Canadian floating-rate note using an overnight index and a forecast curve.
  • The question reports a small accrued-interest mismatch against Bloomberg despite a close clean price.
  • The key uncertainty is whether coupon accrual uses one fixing or compounded overnight fixings.
  • The document does not resolve the correct convention or identify the cause of the difference.

Tags

Full text
# CORRA linked FRN's accrual interest calculation using the QuantLib


# CORRA linked FRN's accrual interest calculation using the QuantLib












In Canada, the CDOR index will be phased out in June 2024, and the replacement is the CORRA (Canadian Overnight Repo Rate Average), which is an OvernightIndex used for evaluating the FRN in Canada. Here is an example bond, the CUSIP is 13607HMS8, maturity date is 2025-03-04, issue date is 2021-02-25, and first settlement date is 2021-03-04, below is my testing code to get the clean price and accrual interest for the evaluation date 2022-04-29, and settlement date is 2022-05-03. I compare the results with the Bloomberg, the clean price is pretty close, but the accrual interest has 2 cents difference, which caught my attention at once, because I feel the accrual interest should be the exactly the same, since the coupon on the last reset date should be the same. Below is my testing code, during debug I see the current payment period is from 2022-03-04 to 2022-06-06, and it used the CORRA rate (0.43%) on 2022-03-04 as the fixing to calculate the rate = gear * fixing + spread, the spread is 0.46%, so the rate = 1 * 0.43% + 0.46% = 0.89%, but some of my friends told me for the overnightIndex should have some kind of compounding, using the single day fixing on 2022-03-04 to calculate the rate is not correct. I thought the OvernightIndex will automatically handle the daily compounding, but it did not, so can someone please help?

```
#include <ql/qldefines.hpp>
#if !defined(BOOST_ALL_NO_LIB) && defined(BOOST_MSVC)
#  include <ql/auto_link.hpp>
#endif
#include <ql/instruments/bonds/zerocouponbond.hpp>
#include <ql/instruments/bonds/floatingratebond.hpp>
#include <ql/pricingengines/bond/discountingbondengine.hpp>
#include <ql/cashflows/couponpricer.hpp>
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/termstructures/yield/bondhelpers.hpp>
#include <ql/termstructures/yield/overnightindexfutureratehelper.hpp>
#include <ql/termstructures/yield/zerocurve.hpp>
#include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp>
#include <ql/indexes/ibor/euribor.hpp>
#include <ql/indexes/ibor/usdlibor.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/calendars/unitedstates.hpp>
#include <ql/time/daycounters/actualactual.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/time/calendars/canada.hpp>
#include <ql/math/interpolations/linearinterpolation.hpp>

#include <iostream>
#include <iomanip>

using namespace QuantLib;

int main(int, char* []) {

try {

    std::cout << std::endl;

    /*********************
     ***  MARKET DATA  ***
     *********************/

    Calendar calendar = Canada();
    DayCounter dayCounter = Actual365Fixed();
    Currency currency = CADCurrency();

    Date settlementDate(3, May, 2022);
    // must be a business day
    settlementDate = calendar.adjust(settlementDate);

    Integer fixingDays = 2;
    Natural settlementDays = 2;

    Date todaysDate = calendar.advance(settlementDate, -fixingDays, Days);
    // nothing to do with Date::todaysDate
    Settings::instance().evaluationDate() = todaysDate;

    std::cout << "Today: " << todaysDate.weekday()
        << ", " << todaysDate << std::endl;

    std::cout << "Settlement date: " << settlementDate.weekday()
        << ", " << settlementDate << std::endl;

    //
    // Building of the CORRA forecasting curve
    //
    std::vector<ext::shared_ptr<RateHelper>> corraFutureHelpers;
    std::map<Date, Real> futurePrices;
    futurePrices[Date(15, June, 2022)] = 99.12;
    futurePrices[Date(21, September, 2022)] = 98.08;
    futurePrices[Date(21, December, 2022)] = 97.3;
    futurePrices[Date(15, March, 2023)] = 96.99;
    futurePrices[Date(21, June, 2023)] = 96.96;
    futurePrices[Date(20, September, 2023)] = 96.93;
    futurePrices[Date(20, December, 2023)] = 96.90;
    futurePrices[Date(20, March, 2024)] = 96.87;
    futurePrices[Date(19, June, 2024)] = 96.83;
    futurePrices[Date(18, September, 2024)] = 96.80;
    futurePrices[Date(18, December, 2024)] = 96.78;
    futurePrices[Date(19, March, 2025)] = 96.76;

    const ext::shared_ptr<OvernightIndex> corraIndex(new OvernightIndex("CORRA", Natural(fixingDays), currency, calendar, dayCounter));

    std::map<Date, Real>::iterator it = futurePrices.begin();
    // Iterate over the map using Iterator till end.
    while (it != futurePrices.end()) {
        // Accessing KEY from element pointed by it.
        Date date = it->first;
        Real price = it->second;
        ext::shared_ptr<Quote> quoteHandle(new SimpleQuote(price));
        ext::shared_ptr<RateHelper> futureRateHelper(new OvernightIndexFutureRateHelper(
            Handle<Quote>(quoteHandle),
            todaysDate,
            date,
            corraIndex));
        corraFutureHelpers.push_back(futureRateHelper);
        it++;
    }

    ext::shared_ptr<YieldTermStructure> corraForecastTermStructure(new PiecewiseYieldCurve<Discount, LogLinear>(settlementDate, corraFutureHelpers, dayCounter));

    //
    // Loading the CIBC AA discount curve
    //
    std::vector<Rate> rates;
    std::vector<Date> dates;
    std::map<Date, Rate> curvePoints;
    curvePoints[Date(29, April, 2022)] = 0.01175992;
    curvePoints[Date(29, May, 2022)] = 0.01279924;
    curvePoints[Date(29, June, 2022)] = 0.01479078;
    curvePoints[Date(29, July, 2022)] = 0.016785321;
    curvePoints[Date(29, August, 2022)] = 0.018477552;
    curvePoints[Date(29, September, 2022)] = 0.020169783;
    curvePoints[Date(29, October, 2022)] = 0.021807427;
    curvePoints[Date(29, November, 2022)] = 0.023499658;
    curvePoints[Date(29, December, 2022)] = 0.025165168;
    curvePoints[Date(29, January, 2023)] = 0.026893393;
    curvePoints[Date(28, February, 2023)] = 0.028565869;
    curvePoints[Date(29, March, 2023)] = 0.030182596;
    curvePoints[Date(29, April, 2023)] = 0.031910822;
    curvePoints[Date(29, July, 2023)] = 0.032000324;
    curvePoints[Date(29, October, 2023)] = 0.034663883;
    curvePoints[Date(29, January, 2024)] = 0.036994209;
    curvePoints[Date(29, April, 2024)] = 0.037462802;
    curvePoints[Date(29, July, 2024)] = 0.037931396;
    curvePoints[Date(29, October, 2024)] = 0.038628441;
    curvePoints[Date(29, January, 2025)] = 0.039433535;
    curvePoints[Date(29, April, 2025)] = 0.038443031;
    curvePoints[Date(29, July, 2025)] = 0.036722382;
    curvePoints[Date(29, October, 2025)] = 0.038809456;
    curvePoints[Date(29, January, 2026)] = 0.04069903;
    curvePoints[Date(29, April, 2026)] = 0.040963241;
    curvePoints[Date(29, October, 2026)] = 0.041631359;
    curvePoints[Date(29, April, 2027)] = 0.041631359;

    std::map<Date, Rate>::iterator itPoints = curvePoints.begin();
    // Iterate over the map using Iterator till end.
    while (itPoints != curvePoints.end()) {
        // Accessing KEY from element pointed by it.
        Date date = itPoints->first;
        Rate point = itPoints->second;
        dates.push_back(date);
        rates.push_back(point);
        itPoints++;
    }

    ext::shared_ptr<YieldTermStructure> cibcDiscountTermStructure(new InterpolatedZeroCurve<Linear>(dates, rates, dayCounter, calendar, Linear()));

    // Term structures that will be used for pricing:
    // the one used for discounting cash flows
    RelinkableHandle<YieldTermStructure> discountingTermStructure;
    // the one used for forward rate forecasting
    RelinkableHandle<YieldTermStructure> forecastingTermStructure;

    /*********************
    * BONDS TO BE PRICED *
    **********************/

    // Common data
    Real faceAmount = 100;

    // Pricing engine
    ext::shared_ptr<PricingEngine> bondEngine(new DiscountingBondEngine(discountingTermStructure));

    // Floating rate bond (CORRA index)
    // Populate all the past CORRA rate fixing which I downloaded from the Bank Of Canada web site, 
    const ext::shared_ptr<OvernightIndex> corraIndex1(new OvernightIndex("CORRA", Natural(fixingDays), currency, calendar, dayCounter, forecastingTermStructure));
    corraIndex1->addFixing(Date(4, March, 2021), 0.0017);
    corraIndex1->addFixing(Date(4, June, 2021), 0.0018);
    corraIndex1->addFixing(Date(7, September, 2021), 0.0018);
    corraIndex1->addFixing(Date(1, December, 2021), 0.0013);
    corraIndex1->addFixing(Date(2, December, 2021), 0.0015);
    corraIndex1->addFixing(Date(3, December, 2021), 0.0015);
    corraIndex1->addFixing(Date(6, December, 2021), 0.0015);
    corraIndex1->addFixing(Date(7, December, 2021), 0.0012);
    corraIndex1->addFixing(Date(8, December, 2021), 0.0008);
    corraIndex1->addFixing(Date(9, December, 2021), 0.001);
    corraIndex1->addFixing(Date(10, December, 2021), 0.0005);
    corraIndex1->addFixing(Date(13, December, 2021), 0.0005);
    corraIndex1->addFixing(Date(14, December, 2021), 0.0007);
    corraIndex1->addFixing(Date(15, December, 2021), 0.0007);
    corraIndex1->addFixing(Date(16, December, 2021), 0.0009);
    corraIndex1->addFixing(Date(17, December, 2021), 0.0008);
    corraIndex1->addFixing(Date(20, December, 2021), 0.0008);
    corraIndex1->addFixing(Date(21, December, 2021), 0.0008);
    corraIndex1->addFixing(Date(22, December, 2021), 0.0008);
    corraIndex1->addFixing(Date(23, December, 2021), 0.0014);
    corraIndex1->addFixing(Date(24, December, 2021), 0.0013);
    corraIndex1->addFixing(Date(29, December, 2021), 0.0012);
    corraIndex1->addFixing(Date(30, December, 2021), 0.0012);
    corraIndex1->addFixing(Date(31, December, 2021), 0.001);
    corraIndex1->addFixing(Date(4, January, 2022), 0.0012);
    corraIndex1->addFixing(Date(5, January, 2022), 0.0012);
    corraIndex1->addFixing(Date(6, January, 2022), 0.0013);
    corraIndex1->addFixing(Date(7, January, 2022), 0.0015);
    corraIndex1->addFixing(Date(10, January, 2022), 0.0017);
    corraIndex1->addFixing(Date(11, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(12, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(13, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(14, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(17, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(18, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(19, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(20, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(21, January, 2022), 0.0016);
    corraIndex1->addFixing(Date(24, January, 2022), 0.0018);
    corraIndex1->addFixing(Date(25, January, 2022), 0.0017);
    corraIndex1->addFixing(Date(26, January, 2022), 0.0016);
    corraIndex1->addFixing(Date(27, January, 2022), 0.0016);
    corraIndex1->addFixing(Date(28, January, 2022), 0.0015);
    corraIndex1->addFixing(Date(31, January, 2022), 0.0013);

    corraIndex1->addFixing(Date(1, February, 2022), 0.0013);
    corraIndex1->addFixing(Date(2, February, 2022), 0.0013);
    corraIndex1->addFixing(Date(3, February, 2022), 0.0013);
    corraIndex1->addFixing(Date(4, February, 2022), 0.0013);
    corraIndex1->addFixing(Date(7, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(8, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(9, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(10, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(11, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(14, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(15, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(16, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(17, February, 2022), 0.0018);
    corraIndex1->addFixing(Date(18, February, 2022), 0.0019);
    corraIndex1->addFixing(Date(22, February, 2022), 0.0019);
    corraIndex1->addFixing(Date(23, February, 2022), 0.0019);
    corraIndex1->addFixing(Date(24, February, 2022), 0.0019);
    corraIndex1->addFixing(Date(25, February, 2022), 0.0019);
    corraIndex1->addFixing(Date(28, February, 2022), 0.0019);

    corraIndex1->addFixing(Date(1, March, 2022), 0.0018);
    corraIndex1->addFixing(Date(2, March, 2022), 0.0017);
    corraIndex1->addFixing(Date(3, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(4, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(7, March, 2022), 0.0042);
    corraIndex1->addFixing(Date(8, March, 2022), 0.0042);
    corraIndex1->addFixing(Date(9, March, 2022), 0.0042);
    corraIndex1->addFixing(Date(10, March, 2022), 0.0042);
    corraIndex1->addFixing(Date(11, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(14, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(15, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(16, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(17, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(18, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(21, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(22, March, 2022), 0.0043);
    corraIndex1->addFixing(Date(23, March, 2022), 0.0044);
    corraIndex1->addFixing(Date(24, March, 2022), 0.0045);
    corraIndex1->addFixing(Date(25, March, 2022), 0.0045);
    corraIndex1->addFixing(Date(28, March, 2022), 0.0045);
    corraIndex1->addFixing(Date(29, March, 2022), 0.0045);
    corraIndex1->addFixing(Date(30, March, 2022), 0.0045);
    corraIndex1->addFixing(Date(31, March, 2022), 0.0042);
    corraIndex1->addFixing(Date(1, April, 2022), 0.0042);
    corraIndex1->addFixing(Date(4, April, 2022), 0.0044);
    corraIndex1->addFixing(Date(5, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(6, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(7, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(8, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(11, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(12, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(13, April, 2022), 0.0045);
    corraIndex1->addFixing(Date(14, April, 2022), 0.0095);
    corraIndex1->addFixing(Date(18, April, 2022), 0.0093);
    corraIndex1->addFixing(Date(19, April, 2022), 0.0093);
    corraIndex1->addFixing(Date(29, April, 2022), 0.0092);
    corraIndex1->addFixing(Date(2, May, 2022), 0.0093);
    corraIndex1->addFixing(Date(3, May, 2022), 0.0093);
    corraIndex1->addFixing(Date(4, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(5, May, 2022), 0.0095);
    corraIndex1->addFixing(Date(6, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(9, May, 2022), 0.0095);
    corraIndex1->addFixing(Date(10, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(11, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(12, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(13, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(16, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(17, May, 2022), 0.0096);
    corraIndex1->addFixing(Date(18, May, 2022), 0.0096);

    Schedule floatingBondSchedule(
        Date(4, March, 2021),
        Date(4, March, 2025),
        Period(Quarterly),
        calendar,
        ModifiedFollowing, // need to play around this setting
        ModifiedFollowing,
        DateGeneration::Backward,
        false);

    DayCounter floater_day_count = ActualActual(ActualActual::ISMA);
    std::vector<Rate> spreads;
    spreads.push_back(0.0046);
    FloatingRateBond floatingRateBond(
        settlementDays,
        faceAmount,
        floatingBondSchedule,
        corraIndex1,
        floater_day_count,
        ModifiedFollowing,
        Natural(0),
        // Gearings
        std::vector<Rate>(),
        // Spreads
        spreads,
        // Caps
        std::vector<Rate>(),
        // Floors
        std::vector<Rate>(),
        // Fixing in arrears, need to play around this value
        false,
        Real(100.0),
        Date(25, February, 2021));

    floatingRateBond.setPricingEngine(bondEngine);

    // Yield curve bootstrapping
    forecastingTermStructure.linkTo(corraForecastTermStructure);
    discountingTermStructure.linkTo(cibcDiscountTermStructure);

    /***************
     * BOND PRICING *
     ****************/

    std::cout << std::endl;

    Leg leg = floatingRateBond.cashflows();
    Leg::const_iterator i;
    for (i = leg.begin(); i < leg.end(); ++i) {
        std::cout << "cashflow date: "
            << (*i)->date() << " cashflow amount: " << (*i)->amount()
            << std::endl;
    }

    std::cout << "Previous cashflow date: "
        << floatingRateBond.previousCashFlowDate()
        << std::endl;

    std::cout << "Next cashflow date: "
        << floatingRateBond.nextCashFlowDate()
        << std::endl;

    std::cout << "Accrued coupon: "
        << floatingRateBond.accruedAmount()
        << std::endl;

    std::cout << "Previous coupon: "
        << io::rate(floatingRateBond.previousCouponRate())
        << std::endl;

    std::cout << "Next coupon: "
        << io::rate(floatingRateBond.nextCouponRate())
        << std::endl;

    std::cout << "Net present value: "
        << floatingRateBond.NPV()
        << std::endl;

    std::cout << "Clean price: "
        << floatingRateBond.cleanPrice()
        << std::endl;

    std::cout << "Dirty price: "
        << floatingRateBond.dirtyPrice()
        << std::endl;

    std::cout << "Yield: "
        << floatingRateBond.yield(dayCounter, Compounded, Semiannual)
        << std::endl;

    std::cout << std::endl;

    return 0;

}
catch (std::exception& e) {
    std::cerr << e.what() << std::endl;
    return 1;
}
catch (...) {
    std::cerr << "unknown error" << std::endl;
    return 1;
}
```

}

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.