Posted by
Dirk Eddelbuettel on
URL: http://quantlib.414.s1.nabble.com/Bond-duration-incorrect-tp4434.html
Rainy day here so I got to play a little, at long last, with the fixed income
functions. I can't make sense of duration() in CashFlows/analysis.{cpp,hpp}
In particular this line seems wrong:
totalNPV -= marketPrice;
before the sum of present value times time (here variable totalDuration) gets
divided by totalNPV as in
return totalDuration/totalNPV;
which is supported via quick googling [
http://www.finpipe.com/duration.htm ]
and one of the investments texts here.
Likewise, modified duration is normaly given as Macaulay Duration divided by
(1 + y / nbcoupons) -- so that a second fix would be
case Duration::Modified:
//return totalDuration / totalNPV / y;
return totalDuration / totalNPV / (1 + y/rate.frequency());
A simplistic program, taken from one of the regression test cases, is below
and I added a local copy of the duration function from analysis.cpp.
I may have overlooked something, but I guess we should at least add some
validated regression tests for some duration, convexity, ... cases.
Any volunteers?
Oh, and one last question: why isn't the redemption at par payment already
included when I call cashflow() on a bond?
Dirk
#include <iostream>
#include <ql/quantlib.hpp>
using namespace std;
using namespace QuantLib;
Time myDuration(const std::vector<boost::shared_ptr<CashFlow> >& cashflows,
Real marketPrice,
const InterestRate& rate,
Duration::Type type,
Date settlementDate) {
if (settlementDate == Date())
settlementDate = Settings::instance().evaluationDate();
Real totalDuration = 0.0;
Real totalNPV = 0.0;
Rate y = 0.0;
if (type == Duration::Macaulay || type == Duration::Modified) {
y = Cashflows::irr(cashflows, marketPrice, rate.dayCounter(),
rate.compounding(), rate.frequency(), settlementDate);
cout << "Y is " << y << " " << rate.frequency() << endl;
}
for (Size i = 0; i < cashflows.size(); i++) {
Time t = rate.dayCounter().yearFraction(settlementDate,
cashflows[i]->date());
Real c = cashflows[i]->amount();
DiscountFactor discount;
if (type == Duration::Macaulay)
discount = std::exp(-y*t);
else
discount = rate.discountFactor(t);
totalNPV += c * discount;
totalDuration += t * c * discount;
}
//totalNPV -= marketPrice; // edd 28-Jan-2006: this is probably in error
if (totalNPV == 0.0)
// what to do?
return 0.0;
switch (type){
case Duration::Modified:
//return totalDuration / totalNPV / y; // edd 28-Jan-2006: error?
return totalDuration / totalNPV / (1 + y/rate.frequency());
case Duration::Simple:
case Duration::Macaulay:
return totalDuration/totalNPV;
default:
QL_FAIL("unknown duration type");
}
}
int main(void) {
Calendar calendar = TARGET();
Date today = calendar.adjust(Date::todaysDate());
Settings::instance().evaluationDate() = today;
Real redemption = 100.0; // bond pay $100 face at term
Integer issueMonth = -12; // bond issued 12 months ago
Integer bondTerm = 5; // bond has a 5yr term-to-maturity
Real bondCoupon = 0.05; // bond has 5% coupon
Frequency bondCoupFreq = Semiannual; // bond pays coupon annually
DayCounter bondDayCount = Thirty360();
Compounding compounding = Compounded;
BusinessDayConvention convention = ModifiedFollowing;
Integer settlementDays = 3;
Rate currentYield = 0.0475;
Date dated = calendar.advance(today, issueMonth, Months);
Date issue = dated;
Date maturity = calendar.advance(issue, bondTerm, Years);
boost::shared_ptr<YieldTermStructure>
flatRate(new FlatForward(dated, currentYield, bondDayCount,
compounding, bondCoupFreq));
FixedCouponBond bond(issue, dated, maturity, settlementDays,
std::vector<Rate>(1, bondCoupon),
bondCoupFreq, bondDayCount,
calendar, convention, redemption,
Handle<YieldTermStructure>(flatRate));
Real cleanPrice = bond.cleanPrice(currentYield, compounding);
Real dirtyPrice = bond.dirtyPrice(currentYield, compounding);
Rate calculated = bond.yield(cleanPrice, compounding);
Real accrued = bond.accruedAmount();
cout << "Dirty Price: " << dirtyPrice << endl;
cout << "Clean Price: " << cleanPrice << endl;
cout << "Yield : " << calculated << endl;
cout << "Accrued : " << accrued << endl;
// need to explicitly push redemption cash flow into cf vector -- why ?
std::vector<boost::shared_ptr<CashFlow> > cfVec(bond.cashflows());
cfVec.push_back(bond.redemption());
Real irr = Cashflows::irr(cfVec, cleanPrice,
bondDayCount, compounding, bondCoupFreq, issue);
cout << "Irr : " << irr << endl;
InterestRate rate(calculated, bondDayCount, compounding, bondCoupFreq);
cout << "Rate : " << rate << endl;
// Time duration = Cashflows::duration(cfVec, cleanPrice, rate,
// Duration::Modified, dated);
Time macDuration = myDuration(cfVec, cleanPrice, rate,
Duration::Macaulay, dated);
Time modDuration = myDuration(cfVec, cleanPrice, rate,
Duration::Modified, dated);
Time simDuration = myDuration(cfVec, cleanPrice, rate,
Duration::Simple, dated);
cout << "MacDuration: " << macDuration << endl;
cout << "ModDuration: " << modDuration << endl;
cout << "SimDuration: " << simDuration << endl;
for (unsigned int i=0; i<cfVec.size(); i++) {
cout << "Date " << i << " :\t" << cfVec[i]->date() << "\t"
<< "Amount: " << cfVec[i]->amount() << endl;
}
}
--
Hell, there are no rules here - we're trying to accomplish something.
-- Thomas A. Edison