Bumping Interest Rate Curves in QuantLib
Bumping Interest Rate Curves in QuantLib
The QuantLib library implements the Observer
pattern in many of
its data structures and functions. This means that a link to data used
to build objects such as interest curves is retained and if this input
data is changed after the construction of the curve the curve itself
is automatically updated. This means that bumping the curve is easy
since it is not necessary to explicitly rebuild the curves.
Below is an example illustrating this. Some things to note:
-
Input data for the curve are kept together in a simple structure
-
The curve can be bumped directly by changing a market quote
-
The change in the market quote automatically triggers a recalibration of the curve
For more information on QuantLib see our dedicated pages.
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*!
Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
Copyright (C) 2003, 2004, 2005, 2006, 2007 StatPro Italia srl
Copyright (C) 2004 Ferdinando Ametrano
copyright (C) 2011 Bojan Nikolic <bojan@bnikolic.co.uk>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Example of shocking interest rate curves by using the Handle/Quote
mechanism. Reworked using code and data from the swapvaluation.cpp
example in QuantLib.
*/
#include <boost/format.hpp>
#include <ql/quantlib.hpp>
using namespace QuantLib;
/// A structure to hold all the market quotes for a single curve
/// together
struct CurveData {
boost::shared_ptr<SimpleQuote> d1wRate;
boost::shared_ptr<SimpleQuote> d1mRate;
boost::shared_ptr<SimpleQuote> d3mRate;
boost::shared_ptr<SimpleQuote> d6mRate;
boost::shared_ptr<SimpleQuote> d9mRate;
boost::shared_ptr<SimpleQuote> d1yRate;
boost::shared_ptr<SimpleQuote> fra3x6Rate;
boost::shared_ptr<SimpleQuote> fra6x9Rate;
boost::shared_ptr<SimpleQuote> fra6x12Rate;
boost::shared_ptr<SimpleQuote> fut1Price;
boost::shared_ptr<SimpleQuote> fut2Price;
boost::shared_ptr<SimpleQuote> fut3Price;
boost::shared_ptr<SimpleQuote> fut4Price;
boost::shared_ptr<SimpleQuote> fut5Price;
boost::shared_ptr<SimpleQuote> fut6Price;
boost::shared_ptr<SimpleQuote> fut7Price;
boost::shared_ptr<SimpleQuote> fut8Price;
boost::shared_ptr<SimpleQuote> s2yRate;
boost::shared_ptr<SimpleQuote> s3yRate;
boost::shared_ptr<SimpleQuote> s5yRate;
boost::shared_ptr<SimpleQuote> s10yRate;
boost::shared_ptr<SimpleQuote> s15yRate;
CurveData():
d1wRate(new SimpleQuote()),
d1mRate(new SimpleQuote()),
d3mRate(new SimpleQuote()),
d6mRate(new SimpleQuote()),
d9mRate(new SimpleQuote()),
d1yRate(new SimpleQuote()),
fra3x6Rate(new SimpleQuote()),
fra6x9Rate(new SimpleQuote()),
fra6x12Rate(new SimpleQuote()),
fut1Price(new SimpleQuote()),
fut2Price(new SimpleQuote()),
fut3Price(new SimpleQuote()),
fut4Price(new SimpleQuote()),
fut5Price(new SimpleQuote()),
fut6Price(new SimpleQuote()),
fut7Price(new SimpleQuote()),
fut8Price(new SimpleQuote()),
s2yRate(new SimpleQuote()),
s3yRate(new SimpleQuote()),
s5yRate(new SimpleQuote()),
s10yRate(new SimpleQuote()),
s15yRate(new SimpleQuote())
{
}
};
// Fill out the curve with sample market data (from more optimistic
// times!)
void sampleMktData(CurveData &cd)
{
(*cd.d1wRate)=0.0382;
(*cd.d1mRate)=0.0372;
(*cd.d3mRate)=0.0363;
(*cd.d6mRate)=0.0353;
(*cd.d9mRate)=0.0348;
(*cd.d1yRate)=0.0345;
(*cd.fra3x6Rate)=0.037125;
(*cd.fra6x9Rate)=0.037125;
(*cd.fra6x12Rate)=0.037125;
(*cd.fut1Price)=96.2875;
(*cd.fut2Price)=96.7875;
(*cd.fut3Price)=96.9875;
(*cd.fut4Price)=96.6875;
(*cd.fut5Price)=96.4875;
(*cd.fut6Price)=96.3875;
(*cd.fut7Price)=96.2875;
(*cd.fut8Price)=96.0875;
// swaps
(*cd.s2yRate)=0.037125;
(*cd.s3yRate)=0.0398;
(*cd.s5yRate)=0.0443;
(*cd.s10yRate)=0.05165;
(*cd.s15yRate)=0.055175;
}
/// Build a yield curve. Note that the resulting curve is linked to
/// the supplied data and will change with it
boost::shared_ptr<YieldTermStructure> buildCurve(const CurveData &cd)
{
Calendar calendar = TARGET();
Date settlementDate(1, September, 2010);
// must be a business day
settlementDate = calendar.adjust(settlementDate);
Integer fixingDays = 2;
Date todaysDate = calendar.advance(settlementDate, -fixingDays, Days);
// nothing to do with Date::todaysDate
Settings::instance().evaluationDate() = todaysDate;
todaysDate = Settings::instance().evaluationDate();
DayCounter depositDayCounter = Actual360();
boost::shared_ptr<RateHelper> d1w(new DepositRateHelper(
Handle<Quote>(cd.d1wRate),
1*Weeks, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d1m(new DepositRateHelper(
Handle<Quote>(cd.d1mRate),
1*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d3m(new DepositRateHelper(
Handle<Quote>(cd.d3mRate),
3*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d6m(new DepositRateHelper(
Handle<Quote>(cd.d6mRate),
6*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d9m(new DepositRateHelper(
Handle<Quote>(cd.d9mRate),
9*Months, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> d1y(new DepositRateHelper(
Handle<Quote>(cd.d1yRate),
1*Years, fixingDays,
calendar, ModifiedFollowing,
true, depositDayCounter));
// setup FRAs
boost::shared_ptr<RateHelper> fra3x6(new FraRateHelper(
Handle<Quote>(cd.fra3x6Rate),
3, 6, fixingDays, calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> fra6x9(new FraRateHelper(
Handle<Quote>(cd.fra6x9Rate),
6, 9, fixingDays, calendar, ModifiedFollowing,
true, depositDayCounter));
boost::shared_ptr<RateHelper> fra6x12(new FraRateHelper(
Handle<Quote>(cd.fra6x12Rate),
6, 12, fixingDays, calendar, ModifiedFollowing,
true, depositDayCounter));
// setup futures
// Rate convexityAdjustment = 0.0;
Integer futMonths = 3;
Date imm = IMM::nextDate(settlementDate);
boost::shared_ptr<RateHelper> fut1(new FuturesRateHelper(
Handle<Quote>(cd.fut1Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut2(new FuturesRateHelper(
Handle<Quote>(cd.fut2Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut3(new FuturesRateHelper(
Handle<Quote>(cd.fut3Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut4(new FuturesRateHelper(
Handle<Quote>(cd.fut4Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut5(new FuturesRateHelper(
Handle<Quote>(cd.fut5Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut6(new FuturesRateHelper(
Handle<Quote>(cd.fut6Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut7(new FuturesRateHelper(
Handle<Quote>(cd.fut7Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
imm = IMM::nextDate(imm+1);
boost::shared_ptr<RateHelper> fut8(new FuturesRateHelper(
Handle<Quote>(cd.fut8Price),
imm,
futMonths, calendar, ModifiedFollowing,
true, depositDayCounter));
// setup swaps
Frequency swFixedLegFrequency = Annual;
BusinessDayConvention swFixedLegConvention = Unadjusted;
DayCounter swFixedLegDayCounter = Thirty360(Thirty360::European);
boost::shared_ptr<IborIndex> swFloatingLegIndex(new Euribor6M);
boost::shared_ptr<RateHelper> s2y(new SwapRateHelper(
Handle<Quote>(cd.s2yRate), 2*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex));
boost::shared_ptr<RateHelper> s3y(new SwapRateHelper(
Handle<Quote>(cd.s3yRate), 3*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex));
boost::shared_ptr<RateHelper> s5y(new SwapRateHelper(
Handle<Quote>(cd.s5yRate), 5*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex));
boost::shared_ptr<RateHelper> s10y(new SwapRateHelper(
Handle<Quote>(cd.s10yRate), 10*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex));
boost::shared_ptr<RateHelper> s15y(new SwapRateHelper(
Handle<Quote>(cd.s15yRate), 15*Years,
calendar, swFixedLegFrequency,
swFixedLegConvention, swFixedLegDayCounter,
swFloatingLegIndex));
/*********************
** CURVE BUILDING **
*********************/
// Any DayCounter would be fine.
// ActualActual::ISDA ensures that 30 years is 30.0
DayCounter termStructureDayCounter =
ActualActual(ActualActual::ISDA);
double tolerance = 1.0e-15;
// 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));
// A depo-futures-swap curve
std::vector<boost::shared_ptr<RateHelper> > depoFutSwapInstruments;
depoFutSwapInstruments.push_back(d1w);
depoFutSwapInstruments.push_back(d1m);
depoFutSwapInstruments.push_back(fut1);
depoFutSwapInstruments.push_back(fut2);
depoFutSwapInstruments.push_back(fut3);
depoFutSwapInstruments.push_back(fut4);
depoFutSwapInstruments.push_back(fut5);
depoFutSwapInstruments.push_back(fut6);
depoFutSwapInstruments.push_back(fut7);
depoFutSwapInstruments.push_back(fut8);
depoFutSwapInstruments.push_back(s3y);
depoFutSwapInstruments.push_back(s5y);
depoFutSwapInstruments.push_back(s10y);
depoFutSwapInstruments.push_back(s15y);
boost::shared_ptr<YieldTermStructure> depoFutSwapTermStructure(
new PiecewiseYieldCurve<Discount,LogLinear>(
settlementDate, depoFutSwapInstruments,
termStructureDayCounter,
tolerance));
// A depo-FRA-swap curve
std::vector<boost::shared_ptr<RateHelper> > depoFRASwapInstruments;
depoFRASwapInstruments.push_back(d1w);
depoFRASwapInstruments.push_back(d1m);
depoFRASwapInstruments.push_back(d3m);
depoFRASwapInstruments.push_back(fra3x6);
depoFRASwapInstruments.push_back(fra6x9);
depoFRASwapInstruments.push_back(fra6x12);
depoFRASwapInstruments.push_back(s2y);
depoFRASwapInstruments.push_back(s3y);
depoFRASwapInstruments.push_back(s5y);
depoFRASwapInstruments.push_back(s10y);
depoFRASwapInstruments.push_back(s15y);
boost::shared_ptr<YieldTermStructure> depoFRASwapTermStructure(
new PiecewiseYieldCurve<Discount,LogLinear>(
settlementDate, depoFRASwapInstruments,
termStructureDayCounter,
tolerance));
return depoFRASwapTermStructure;
}
int main(void)
{
CurveData cd;
// Fill out with some sample market data
sampleMktData(cd);
// Build a curve linked to this market data
boost::shared_ptr<YieldTermStructure> ocurve=buildCurve(cd);
// Print month headings
for (size_t i=0; i<24; ++i)
std::cout << boost::format("Month %i , ") %i;
std::cout<<std::endl;
// Print original curve
for (size_t i=0; i<24; ++i)
std::cout << boost::format("%g , ") % ocurve->zeroRate( i / 12.0, Compounded ).rate();
std::cout<<std::endl;
// Shock the 3month depo
(*cd.d3mRate)=cd.d3mRate->value()+0.01;
// Print the bumped, rebuilt curve
for (size_t i=0; i<24; ++i)
std::cout << boost::format("%g ,") % ocurve->zeroRate( i / 12.0, Compounded ).rate();
std::cout<<std::endl;
}