# How to sum interest rate curves in QuantLib

C++ code taken from Bonds.cpp and slightly amended:

#include <ql/quantlib.hpp>
#include <boost/timer.hpp>
#include <iostream>
#include <iomanip>

using namespace QuantLib;

int main(int, char* []) {

try {

// Just a couple of parameters

Calendar calendar = TARGET();
Date settlementDate(18, September, 2008);
Integer fixingDays = 3;
Natural settlementDays = 3;
Date todaysDate = calendar.advance(settlementDate, -fixingDays, Days);
Settings::instance().evaluationDate() = todaysDate;

/* Now let's introduce two curves: the first one is a swap curve built from
deposits and IRS, the second one is a credit spread curve which should be
added to the former one to get a proper yield curve */

// Swap curve

DayCounter termStructureDayCounter = ActualActual(ActualActual::ISDA);
double tolerance = 1.0e-5;

// Deposits

Rate d1wQuote = 0.043375;
Rate d1mQuote = 0.031875;
Rate d3mQuote = 0.0320375;
Rate d6mQuote = 0.03385;
Rate d9mQuote = 0.0338125;
Rate d1yQuote = 0.0335125;

boost::shared_ptr<Quote> d1wRate(new SimpleQuote(d1wQuote));
boost::shared_ptr<Quote> d1mRate(new SimpleQuote(d1mQuote));
boost::shared_ptr<Quote> d3mRate(new SimpleQuote(d3mQuote));
boost::shared_ptr<Quote> d6mRate(new SimpleQuote(d6mQuote));
boost::shared_ptr<Quote> d9mRate(new SimpleQuote(d9mQuote));
boost::shared_ptr<Quote> d1yRate(new SimpleQuote(d1yQuote));

// IRS

Rate s2yQuote = 0.0295;
Rate s3yQuote = 0.0323;
Rate s5yQuote = 0.0359;
Rate s10yQuote = 0.0412;
Rate s15yQuote = 0.0433;

boost::shared_ptr<Quote> s2yRate(new SimpleQuote(s2yQuote));
boost::shared_ptr<Quote> s3yRate(new SimpleQuote(s3yQuote));
boost::shared_ptr<Quote> s5yRate(new SimpleQuote(s5yQuote));
boost::shared_ptr<Quote> s10yRate(new SimpleQuote(s10yQuote));
boost::shared_ptr<Quote> s15yRate(new SimpleQuote(s15yQuote));

// Rate Helper

// Deposits
DayCounter depositDayCounter = Actual360();

boost::shared_ptr<RateHelper> d1w(new DepositRateHelper(
Handle<Quote>(d1wRate),
1*Weeks, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d1m(new DepositRateHelper(
Handle<Quote>(d1mRate),
1*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d3m(new DepositRateHelper(
Handle<Quote>(d3mRate),
3*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d6m(new DepositRateHelper(
Handle<Quote>(d6mRate),
6*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d9m(new DepositRateHelper(
Handle<Quote>(d9mRate),
9*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d1y(new DepositRateHelper(
Handle<Quote>(d1yRate),
1*Years, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));

// Setup IRS
Frequency swFixedLegFrequency = Annual;
DayCounter swFixedLegDayCounter = Thirty360(Thirty360::European);
boost::shared_ptr<IborIndex> swFloatingLegIndex(new Euribor6M);

const Period forwardStart(1*Days);

boost::shared_ptr<RateHelper> s2y(new SwapRateHelper(
Handle<Quote>(s2yRate), 2*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> s3y(new SwapRateHelper(
Handle<Quote>(s3yRate), 3*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> s5y(new SwapRateHelper(
Handle<Quote>(s5yRate), 5*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> s10y(new SwapRateHelper(
Handle<Quote>(s10yRate), 10*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> s15y(new SwapRateHelper(
Handle<Quote>(s15yRate), 15*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));

// A depo-swap curve
std::vector<boost::shared_ptr<RateHelper> > depoSwapInstruments;
depoSwapInstruments.push_back(d1w);
depoSwapInstruments.push_back(d1m);
depoSwapInstruments.push_back(d3m);
depoSwapInstruments.push_back(d6m);
depoSwapInstruments.push_back(d9m);
depoSwapInstruments.push_back(d1y);
depoSwapInstruments.push_back(s2y);
depoSwapInstruments.push_back(s3y);
depoSwapInstruments.push_back(s5y);
depoSwapInstruments.push_back(s10y);
depoSwapInstruments.push_back(s15y);
boost::shared_ptr<YieldTermStructure> depoSwapTermStructure(
new PiecewiseYieldCurve<Discount,LogLinear>(
settlementDate, depoSwapInstruments,
termStructureDayCounter,
tolerance));

return 0;

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


If I'm not wrong, so far we've been obtaining a kind of risk free discount curve.

Now let me to introduce an additional credit spread curve in the form of an object handled by RateHelper (kinda snippet to be inserted after the depo-swap curve of the code above):

        ...

boost::shared_ptr<RateHelper> credit1y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> credit2y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> credit3y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> credit6y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> credit9y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));
boost::shared_ptr<RateHelper> credit10y(new SwapRateHelper(
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex, Handle<Quote>(),forwardStart));

std::vector<boost::shared_ptr<RateHelper> > creditInstruments;
creditInstruments.push_back(credit1y);
creditInstruments.push_back(credit2y);
creditInstruments.push_back(credit3y);
creditInstruments.push_back(credit6y);
creditInstruments.push_back(credit9y);
creditInstruments.push_back(credit10y);
boost::shared_ptr<YieldTermStructure> creditTermStructure(
new PiecewiseYieldCurve<Discount,LogLinear>(
settlementDate, creditInstruments,
termStructureDayCounter,
tolerance));
...


The object creditTermStructure could represent, as instance, a CDS spread curve, or such a term structure.

Having both the depoSwapTermStructure and the creditTermStructure built by the code above, I would like to produce a new object of class YieldTermStructure by summing depoSwapTermStructure and creditTermStructure taking into account the misaligned time knots, that is, the code must sum the rates by the appropriate tenors (1Y depo-swap + 1Y credit spread, 2Y depo-swap + 2Y credit spread... and so on. As of the lacking credit spread maturities, such as 18Y, it should be interpolate known knots).

How is it possible to do such a thing using QuantLib?

-
What are the spreads you're using for the second curve? You're passing them to SwapRateHelpers, so you're using them as swap rates; that is, you'll build a curve on which swaps paying those (fixed) rates are priced at par. Is this correct? –  Luigi Ballabio Nov 8 '13 at 17:01
Hi, Luigi. Actually the one you've mentioned above is a clarification which I didn't want to involve in this issue. You may consider the snippet just an attempt to build a generic rate curve, regardless of what instruments it comes from. As instance, what if they were CDS spreads instead of swap rates? –  Lisa Ann Nov 8 '13 at 17:25

There's no class at this time to add two curves as you want, but it won't be much difficult to write it.

The closest you'll get in the library is the ZeroSpreadedTermStructure class, that shows the general idea: it inherits from YieldTermStructure (by way of ZeroYieldStructure) takes a YieldTermStructure and a spread (constant, in this case) and override its own methods so that they return the sum of the two: for instance,

Rate ZeroSpreadedTermStructure::forwardImpl(Time t) const {
return originalCurve_->forwardRate(t, t, comp_, freq_, true)
}


In your case, you'll have to write a similar class that takes two Handles to YieldTermStructure instead. Somewhat surprisingly, this will make your job easier. You can still take ZeroSpreadedTermStructure as a model for other tasks such as registering with the needed observers, but all you have to do in this case is inherit from YieldTermStructure directly and override the single discountImpl method:

DiscountFactor YourClass::discountImpl(Time t) const {
return baseCurve_->discount(t, true)
}


since the sum of the rates implies the product of the corresponding discount factors.

If you need more information, a description of the YieldTermStructure hierarchy and the methods it implements (which is kind of longish to include here) is available at http://implementingquantlib.blogspot.com/2013/09/chapter-3-part-2-of-n-yield-term.html and later posts.

And of course, once you have your curve working, you're welcome to contribute it to QuantLib. The easier way would be to get a GitHub account and follow the instructions in the readme at https://github.com/lballabio/quantlib.

-
One question on the implementation of ZeroSpreadedTermStructure: in the declaration of ZeroSpreadedTermStructure::forwardImpl I see the comment /* This method must disappear should the spread become a curve */. Besides, ZeroSpreadedTermStructure inherits ZeroYieldStructure which inherits YieldTermStructure which only requires to implement discountImpl, while forwardImpl is required for ForwardRateStructure, which is not a base class. So why is forwardImpl implemented in ZeroSpreadedTermStructure? And what is the meaning of the comment I reported above? –  Enrico Detoma Nov 11 '13 at 14:27
I think it's a leftover from a previous implementation. The idea was that of a shortcut to gain some performance; a constant spread over zero rates gives an equal constant spread on forwards, so we coded it directly instead of relying on the default implementation (which performed a numerical differentiation). But you're right, it's not used anymore and should disappear. –  Luigi Ballabio Nov 11 '13 at 16:17
As for the comment: if the spread were time-dependent, the shortcut would no longer apply. –  Luigi Ballabio Nov 11 '13 at 16:22
Thank you for clarifying! –  Enrico Detoma Nov 11 '13 at 17:44