Login  Register

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;        
       
         
}