Re: Duration and Convexity of a Floating Rate Bond
Posted by asavoldi on Dec 05, 2013; 3:32pm
URL: http://quantlib.414.s1.nabble.com/Duration-and-Convexity-of-a-Floating-Rate-Bond-tp14689p14691.html
So far, I've been able to simulate a floating rate bond with the following code. Surprisingly, the duration, which is expected to be the residual time until the following coupon (should be less than 0.25 being the period quarterly), is evaluated as the residual time to expiration (in this case the calculated duration is about 2 that is the time to expiration)
Is anyone able to provide an explanation about this result?
---------------------------------
Today: Monday, September 15th, 2008
Settlement date: Thursday, September 18th, 2008
Floating Rate Bond
Net present value = 102.359
Clean price = 101.797
Dirty price = 102.359
Accrued coupon = 0.562113
Previous coupon = 2.886250 %
Next coupon = 3.429841 %
Yield = 2.200956 %
Sample indirect computations (for the floating rate bond):
Yield to Clean Price = 101.797
Clean Price to Yield = 2.200956 %
Macaulay Duration = 2.01408
Modified Duration = 2.00306
Convexity = 4.60674
Price Duration = 103.836
Price Convexity = 103.86
#include <ql/quantlib.hpp>
#include <iostream>
#include <iomanip>
using namespace QuantLib;
#if defined(QL_ENABLE_SESSIONS)
namespace QuantLib {
Integer sessionId() { return 0; }
}
#endif
/*
Definitions:
gearings: optional multipliers of the LIBOR fixing (some bonds might pay, for instance, 0.8 times the LIBOR)
spreads: are the added spreads. In your case, the gearing is 1 and the spread is 0.0140 (that is, 140 bps; rates
and spread must be expressed in decimal form).
curve: the curve you want to use for discounting. Using the swap curve plus a spread is a possibility. However, note
that the CDS spread might not be an exact proxy for the spread to be applied. At the very least, the CDS spread
is a quarterly rate simply compounded, whereas (depending on what classes are exported) you might need a continuously
compounded rate in order to spread the swap curve. You'll have to perform the conversion first.
index: an instance of an index class such as Euribor3M, whose constructor in turn would take the swap curve.
*/
void floatingRateBondIndicators() {
//*********************
//*** MARKET DATA ***
//*********************
Calendar calendar = TARGET();
Date settlementDate(18, September, 2008);
// must be a business day
settlementDate = calendar.adjust(settlementDate);
Integer fixingDays = 3;
Natural settlementDays = 3;
Date todaysDate = calendar.advance(settlementDate, -fixingDays, Days);
// nothing to do with Date::todaysDate
Settings::instance().evaluationDate() = todaysDate;
std::cout << "---------------------------------" << std::endl;
std::cout << "Today: " << todaysDate.weekday() << ", " << todaysDate << std::endl;
std::cout << "Settlement date: " << settlementDate.weekday() << ", " << settlementDate << std::endl;
// Building of the bonds discounting yield curve
//*********************
//*** RATE HELPERS ***
//*********************
// RateHelpers are built from the above quotes together with
// other instrument dependant infos. Quotes are passed in
// relinkable handles which could be relinked to some other
// data source later.
// Common data
// ZC rates for the short end
Rate zc3mQuote=0.0096;
Rate zc6mQuote=0.0145;
Rate zc1yQuote=0.0194;
boost::shared_ptr<Quote> zc3mRate(new SimpleQuote(zc3mQuote));
boost::shared_ptr<Quote> zc6mRate(new SimpleQuote(zc6mQuote));
boost::shared_ptr<Quote> zc1yRate(new SimpleQuote(zc1yQuote));
DayCounter zcBondsDayCounter = Actual365Fixed();
boost::shared_ptr<RateHelper> zc3m(new DepositRateHelper(Handle<Quote>(zc3mRate), 3*Months, fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter));
boost::shared_ptr<RateHelper> zc6m(new DepositRateHelper(Handle<Quote>(zc6mRate), 6*Months, fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter));
boost::shared_ptr<RateHelper> zc1y(new DepositRateHelper(Handle<Quote>(zc1yRate), 1*Years, fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter));
// setup bonds
Real redemption = 100.0;
const Size numberOfBonds = 5;
Date issueDates[] = {
Date (15, March, 2005),
Date (15, June, 2005),
Date (30, June, 2006),
Date (15, November, 2002),
Date (15, May, 1987)
};
Date maturities[] = {
Date (31, August, 2010),
Date (31, August, 2011),
Date (31, August, 2013),
Date (15, August, 2018),
Date (15, May, 2038)
};
Real couponRates[] = {
0.02375,
0.04625,
0.03125,
0.04000,
0.04500
};
Real marketQuotes[] = {
100.390625,
106.21875,
100.59375,
101.6875,
102.140625
};
std::vector< boost::shared_ptr<SimpleQuote> > quote;
for (Size i=0; i<numberOfBonds; i++) {
boost::shared_ptr<SimpleQuote> cp(new SimpleQuote(marketQuotes[i]));
quote.push_back(cp);
}
RelinkableHandle<Quote> quoteHandle[numberOfBonds];
for (Size i=0; i<numberOfBonds; i++) {
quoteHandle[i].linkTo(quote[i]);
}
// Definition of the rate helpers
std::vector<boost::shared_ptr<FixedRateBondHelper> > bondsHelpers;
for (Size i=0; i<numberOfBonds; i++) {
Schedule schedule(issueDates[i], maturities[i], Period(Semiannual), UnitedStates(UnitedStates::GovernmentBond),Unadjusted, Unadjusted, DateGeneration::Backward, false);
boost::shared_ptr<FixedRateBondHelper> bondHelper(new FixedRateBondHelper(
quoteHandle[i],
settlementDays,
100.0,
schedule,
std::vector<Rate>(1,couponRates[i]),
ActualActual(ActualActual::Bond),
Unadjusted,
redemption,
issueDates[i]));
bondsHelpers.push_back(bondHelper);
}
//*********************
//** 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-bond curve
std::vector<boost::shared_ptr<RateHelper> > bondInstruments;
// Adding the ZC bonds to the curve for the short end
bondInstruments.push_back(zc3m);
bondInstruments.push_back(zc6m);
bondInstruments.push_back(zc1y);
// Adding the Fixed rate bonds to the curve for the long end
for (Size i=0; i<numberOfBonds; i++) {
bondInstruments.push_back(bondsHelpers[i]);
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------
//bondDiscountingTermStructure => Discounting Curve (this is one of the curves that must be present in order to evaluate properly a floating Rate Bond)
//--------------------------------------------------------------------------------------------------------------------------------------------------------
boost::shared_ptr<YieldTermStructure> bondDiscountingTermStructure(
new PiecewiseYieldCurve<Discount,LogLinear>(settlementDate, bondInstruments, termStructureDayCounter, tolerance)
);
//-------------------------------------------------------------------------------------
// Building of the Libor forecasting curve
// 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;
// swaps
Rate s2yQuote=0.0295;
Rate s3yQuote=0.0323;
Rate s5yQuote=0.0359;
Rate s10yQuote=0.0412;
Rate s15yQuote=0.0433;
//********************
//*** QUOTES ***
//********************
// SimpleQuote stores a value which can be manually changed;
// other Quote subclasses could read the value from a database
// or some kind of data feed.
// deposits
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));
// swaps
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 HELPERS ***
//*********************
// RateHelpers are built from the above quotes together with
// other instrument dependant infos. Quotes are passed in
// relinkable handles which could be relinked to some other
// data source later.
// 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 swaps
Frequency swFixedLegFrequency = Annual;
BusinessDayConvention swFixedLegConvention = Unadjusted;
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));
//*********************
//** CURVE BUILDING **
//*********************
// Any DayCounter would be fine.
// ActualActual::ISDA ensures that 30 years is 30.0
// 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));
// 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
boost::shared_ptr<PricingEngine> bondEngine(new DiscountingBondEngine(discountingTermStructure));
// Floating rate bond (3M USD Libor + 0.1%)
// Should and will be priced on another curve later...
RelinkableHandle<YieldTermStructure> liborTermStructure;
const boost::shared_ptr<IborIndex> libor3m(new USDLibor(Period(3,Months),liborTermStructure));
libor3m->addFixing(Date(17, July, 2008),0.0278625); //278.625 bps; this is spot value of LIBOR rate
Schedule floatingBondSchedule(
Date(21, October, 2005), //issue day
Date(21, October, 2010), //maturity day
Period(Quarterly), //frequency
UnitedStates(UnitedStates::NYSE), //calendar
Unadjusted, //day convention
Unadjusted, //day termination
DateGeneration::Backward, //date generation
true //end of the month
);
FloatingRateBond floatingRateBond(
settlementDays, //days for bond settlement [Natural settlementDays]
faceAmount, //face amount (e.g. 100) [Real faceAmount]
floatingBondSchedule, //schedule object [const Schedule& schedule]
libor3m, //IborIndex -> linked index used for coupon discounting [const boost::shared_ptr<IborIndex>& index]
Actual360(), //paymentDayCounter [const DayCounter& paymentDayCounter]
ModifiedFollowing, //paymentConvention [BusinessDayConvention paymentConvention]
Natural(2), //[Natural fixingDays]
// Gearings
std::vector<Real>(1, 1.0), //[const std::vector<Real>& gearings] -> multiplier
// Spreads
std::vector<Rate>(1, 0.001), //[const std::vector<Spread>& spreads] -> rate added to index curve
// Caps
std::vector<Rate>(), //[const std::vector<Rate>& caps] -> upper limit which limit the coupon rate
// Floors
std::vector<Rate>(), //[const std::vector<Rate>& floors] -> minimum rate guaranteed as coupon rate
// Fixing in arrears
true, //[bool inArrears]
Real(100.0), //[Real redemption]
Date(21, October, 2005)); //[const Date& issueDate]
//-----------------------
//Setting price enginge
//-----------------------
floatingRateBond.setPricingEngine(bondEngine);
//-----------------------
// Coupon pricers
//-----------------------
boost::shared_ptr<IborCouponPricer> pricer(new BlackIborCouponPricer);
// optionLet volatilities
Volatility volatility = 0.0;
Handle<OptionletVolatilityStructure> vol;
vol = Handle<OptionletVolatilityStructure>(boost::shared_ptr<OptionletVolatilityStructure>(new ConstantOptionletVolatility(settlementDays, calendar, ModifiedFollowing, volatility, Actual365Fixed())));
pricer->setCapletVolatility(vol);
setCouponPricer(floatingRateBond.cashflows(),pricer);
//-----------------------------------------------------------------------------------
// Yield curve bootstrapping
//-----------------------------------------------------------------------------------
forecastingTermStructure.linkTo(depoSwapTermStructure);
discountingTermStructure.linkTo(bondDiscountingTermStructure);
// We are using the depo & swap curve to estimate the future Libor rates
liborTermStructure.linkTo(depoSwapTermStructure);
//****************
//* BOND PRICING *
//****************
std::cout << "Floating Rate Bond" << std::endl;
//Net present value
Real npv = floatingRateBond.NPV();
std::cout << "Net present value = " << npv << std::endl;
//Clean price
Real cleanPrice = floatingRateBond.cleanPrice();
std::cout << "Clean price = " << cleanPrice << std::endl;
//Dirty price
Real dirtyPrice = floatingRateBond.dirtyPrice();
std::cout << "Dirty price = " << dirtyPrice << std::endl;
//Accrued amount
Real accruedAmount = floatingRateBond.accruedAmount();
std::cout << "Accrued coupon = " << accruedAmount << std::endl;
//Previous coupon
Real previousCoupon = floatingRateBond.previousCouponRate();
std::cout << "Previous coupon = " << io::rate(previousCoupon) << std::endl;
//Next coupon
Real nextCoupon = floatingRateBond.nextCouponRate();
std::cout << "Next coupon = " << io::rate(nextCoupon) << std::endl;
//yield to maturity
Real yield = floatingRateBond.yield(Actual360(),Compounded,Annual);
std::cout << "Yield = " << io::rate(yield) << std::endl;
std::cout << std::endl;
// Other computations
std::cout << "Sample indirect computations (for the floating rate bond): " << std::endl;
//clean price from yield to maturity
Real cleanPriceFromYield = floatingRateBond.cleanPrice(floatingRateBond.yield(Actual360(),Compounded,Annual),Actual360(),Compounded,Annual,settlementDate);
std::cout << "Yield to Clean Price = " << cleanPriceFromYield << std::endl;
//yield to maturity from clean price
Real yieldFromCleanPrice = floatingRateBond.yield(floatingRateBond.cleanPrice(),Actual360(),Compounded,Annual,settlementDate);
std::cout << "Clean Price to Yield = " << io::rate(yieldFromCleanPrice) << std::endl;
DayCounter dayCounter = ActualActual(ActualActual::Bond);
Compounding interestCompounding = Compounding::Compounded;
Frequency frequency = Frequency::Quarterly;
//Macauly Duration
Time macDuration = BondFunctions::duration(floatingRateBond,yield,dayCounter,interestCompounding,frequency,Duration::Macaulay,todaysDate);
std::cout << "Macaulay Duration = " << macDuration << std::endl;
//Modified duration
Time modDuration = BondFunctions::duration(floatingRateBond,yield,dayCounter,interestCompounding,frequency,Duration::Modified,todaysDate);
std::cout << "Modified Duration = " << modDuration << std::endl;
//Convexity
Real convexity = BondFunctions::convexity(floatingRateBond,yield,dayCounter,interestCompounding,frequency,todaysDate);
std::cout << "Convexity = " << convexity << std::endl;
//Estimate new bond price for an increase in interest rate of 1% using modified duration
Real priceDuration = cleanPrice + cleanPrice * (modDuration * .01);
std::cout << "Price Duration = " << priceDuration << std::endl;
//Estimate new bond price for an increase in interest rate of 1% using duration and convexity
Real priceConvexity = cleanPrice + cleanPrice * (modDuration * .01 + (.5 * convexity * std::pow(.01, 2)));
std::cout << "Price Convexity = " << priceConvexity << std::endl;
}