C++:实现量化bonds 债券测试实例
#include "bonds.hpp"
#include "utilities.hpp"
#include <ql/cashflows/iborcoupon.hpp>
#include <ql/instruments/bonds/fixedratebond.hpp>
#include <ql/instruments/bonds/floatingratebond.hpp>
#include <ql/instruments/bonds/zerocouponbond.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/calendars/unitedstates.hpp>
#include <ql/time/calendars/unitedkingdom.hpp>
#include <ql/time/calendars/australia.hpp>
#include <ql/time/calendars/brazil.hpp>
#include <ql/time/calendars/southafrica.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/time/daycounters/actualactual.hpp>
#include <ql/time/daycounters/business252.hpp>
#include <ql/indexes/ibor/usdlibor.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <ql/time/schedule.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/cashflows/couponpricer.hpp>
#include <ql/cashflows/cashflows.hpp>
#include <ql/pricingengines/bond/discountingbondengine.hpp>
#include <ql/pricingengines/bond/bondfunctions.hpp>
#include <ql/termstructures/credit/flathazardrate.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/currencies/europe.hpp>
#include <ql/pricingengines/bond/riskybondengine.hpp>
using namespace QuantLib;
using namespace boost::unit_test_framework;
#define ASSERT_CLOSE(name, settlement, calculated, expected, tolerance) \
if (std::fabs(calculated-expected) > tolerance) {
\
BOOST_ERROR("Failed to reproduce " << name << " at " << settlement \
<< "\n calculated: " << std::setprecision(8) << calculated \
<< "\n expected: " << std::setprecision(8) << expected); \
}
namespace bonds_test {
struct CommonVars {
Calendar calendar;
Date today;
Real faceAmount;
SavedSettings backup;
CommonVars() {
calendar = TARGET();
today = calendar.adjust(Date::todaysDate());
Settings::instance().evaluationDate() = today;
faceAmount = 1000000.0;
}
};
void checkValue(Real value, Real expectedValue, Real tolerance, const std::string& msg) {
if (std::fabs(value - expectedValue) > tolerance) {
BOOST_ERROR(msg
<< std::fixed
<< "\n calculated: " << value
<< "\n expected: " << expectedValue
<< "\n tolerance: " << tolerance
<< "\n error: " << value - expectedValue);
}
}
}
void BondTest::testYield() {
BOOST_TEST_MESSAGE("Testing consistency of bond price/yield calculation...");
using namespace bonds_test;
CommonVars vars;
Real tolerance = 1.0e-7;
Size maxEvaluations = 100;
Integer issueMonths[] = {
-24, -18, -12, -6, 0, 6, 12, 18, 24 };
Integer lengths[] = {
3, 5, 10, 15, 20 };
Natural settlementDays = 3;
Real coupons[] = {
0.02, 0.05, 0.08 };
Frequency frequencies[] = {
Semiannual, Annual };
DayCounter bondDayCount = Thirty360(Thirty360::BondBasis);
BusinessDayConvention accrualConvention = Unadjusted;
BusinessDayConvention paymentConvention = ModifiedFollowing;
Real redemption = 100.0;
Rate yields[] = {
0.03, 0.04, 0.05, 0.06, 0.07 };
Compounding compounding[] = {
Compounded, Continuous };
for (int issueMonth : issueMonths) {
for (int length : lengths) {
for (Real& coupon : coupons) {
for (auto& frequencie : frequencies) {
for (auto& n : compounding) {
Date dated = vars.calendar.advance(vars.today, issueMonth, Months);
Date issue = dated;
Date maturity = vars.calendar.advance(issue, length, Years);
Schedule sch(dated, maturity, Period(frequencie), vars.calendar,
accrualConvention, accrualConvention, DateGeneration::Backward,
false);
FixedRateBond bond(settlementDays, vars.faceAmount, sch,
std::vector<Rate>(1, coupon), bondDayCount,
paymentConvention, redemption, issue);
for (Real m : yields) {
Real price =
BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequencie);
Rate calculated = BondFunctions::yield(
bond, price, bondDayCount, n, frequencie, Date(), tolerance,
maxEvaluations, 0.05, Bond::Price::Clean);
if (std::fabs(m - calculated) > tolerance) {
Real price2 = BondFunctions::cleanPrice(
bond, calculated, bondDayCount, n, frequencie);
if (std::fabs(price - price2) / price > tolerance) {
BOOST_ERROR("\nyield recalculation failed:"
"\n issue: "
<< issue << "\n maturity: " << maturity
<< "\n coupon: " << io::rate(coupon)
<< "\n frequency: " << frequencie
<< "\n yield: " << io::rate(m)
<< (n == Compounded ? " compounded" : " continuous")
<< std::setprecision(7) << "\n clean price: "
<< price << "\n yield': " << io::rate(calculated)
<< "\n clean price': " << price2);
}
}
price = BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequencie);
calculated = BondFunctions::yield(
bond, price, bondDayCount, n, frequencie, Date(), tolerance,
maxEvaluations, 0.05, Bond::Price::Dirty);
if (std::fabs(m - calculated) > tolerance) {
Real price2 = BondFunctions::dirtyPrice(
bond, calculated, bondDayCount, n, frequencie);
if (std::fabs(price - price2) / price > tolerance) {
BOOST_ERROR("\nyield recalculation failed:"
"\n issue: "
<< issue << "\n maturity: " << maturity
<< "\n coupon: " << io::rate(coupon)
<< "\n frequency: " << frequencie
<< "\n yield: " << io::rate(m)
<< (n == Compounded ? " compounded" : " continuous")
<< std::setprecision(7) << "\n dirty price: "
<< price << "\n yield': " << io::rate(calculated)
<< "\n dirty price': " << price2);
}
}
}
}
}
}
}
}
}
void BondTest::testAtmRate() {
BOOST_TEST_MESSAGE("Testing consistency of bond price/ATM rate calculation...");
using namespace bonds_test;
CommonVars vars;
Real tolerance = 1.0e-7;
Integer issueMonths[] = {
-24, -18, -12, -6, 0, 6, 12, 18, 24 };
Integer lengths[] = {
3, 5, 10, 15, 20 };
Natural settlementDays = 3;
Real coupons[] = {
0.02, 0.05, 0.08 };
Frequency frequencies[] = {
Semiannual, Annual };
DayCounter bondDayCount = Thirty360(Thirty360::BondBasis);
BusinessDayConvention accrualConvention = Unadjusted;
BusinessDayConvention paymentConvention = ModifiedFollowing;
Real redemption = 100.0;
Handle<YieldTermStructure> disc(flatRate(vars.today,0.03,Actual360()));
ext::shared_ptr<PricingEngine> bondEngine(new DiscountingBondEngine(disc));
for (int issueMonth : issueMonths) {
for (int length : lengths) {
for (Real& coupon : coupons) {
for (auto& frequencie : frequencies) {
Date dated = vars.calendar.advance(vars.today, issueMonth, Months);
Date issue = dated;
Date maturity = vars.calendar.advance(issue, length, Years);
Schedule sch(dated, maturity, Period(frequencie), vars.calendar,
accrualConvention, accrualConvention, DateGeneration::Backward,
false);
FixedRateBond bond(settlementDays, vars.faceAmount, sch,
std::vector<Rate>(1, coupon), bondDayCount,
paymentConvention, redemption, issue);
bond.setPricingEngine(bondEngine);
Real price = bond.cleanPrice();
Rate calculated =
BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price);
if (std::fabs(coupon - calculated) > tolerance) {
BOOST_ERROR("\natm rate recalculation failed:"
"\n today: "
<< vars.today << "\n settlement date: " << bond.settlementDate()
<< "\n issue: " << issue << "\n maturity: "
<< maturity << "\n coupon: " << io::rate(coupon)
<< "\n frequency: " << frequencie
<< "\n clean price: " << price
<< "\n dirty price: " << price + bond.accruedAmount()
<< "\n atm rate: " << io::rate(calculated));
}
}
}
}
}
}
void BondTest::testZspread() {
BOOST_TEST_MESSAGE("Testing consistency of bond price/z-spread calculation...");
using namespace bonds_test;
CommonVars vars;
Real tolerance = 1.0e-7;
Size maxEvaluations = 100;
Handle<YieldTermStructure> discountCurve(
flatRate(vars.today,0.03,Actual360()));
Integer issueMonths[] = {
-24, -18, -12, -6, 0, 6, 12, 18, 24 };
Integer lengths[] = {
3, 5, 10, 15, 20 };
Natural settlementDays = 3;
Real coupons[] = {
0.02, 0.05, 0.08 };
Frequency frequencies[] = {
Semiannual, Annual };
DayCounter bondDayCount = Thirty360(Thirty360::BondBasis);
BusinessDayConvention accrualConvention = Unadjusted;
BusinessDayConvention paymentConvention = ModifiedFollowing;
Real redemption = 100.0;
Spread spreads[] = {
-0.01, -0.005, 0.0, 0.005, 0.01 };
Compounding compounding[] = {
Compounded, Continuous };
for (int issueMonth : issueMonths) {
for (int length : lengths) {
for (Real& coupon : coupons) {
for (auto& frequencie : frequencies) {
for (auto& n : compounding) {
Date dated = vars.calendar.advance(vars.today, issueMonth, Months);
Date issue = dated;
Date maturity = vars.calendar.advance(issue, length, Years);
Schedule sch(dated, maturity, Period(frequencie), vars.calendar,
accrualConvention, accrualConvention, DateGeneration::Backward,
false);
FixedRateBond bond(settlementDays, vars.faceAmount, sch,
std::vector<Rate>(1, coupon), bondDayCount,
paymentConvention, redemption, issue);
for (Real spread : spreads) {
Real price = BondFunctions::cleanPrice(bond, *discountCurve, spread,
bondDayCount, n, frequencie);
Spread calculated = BondFunctions::zSpread(
bond, price, *discountCurve, bondDayCount, n, frequencie, Date(),
tolerance, maxEvaluations);
if (std::fabs(spread - calculated) > tolerance) {
Real price2 = BondFunctions::cleanPrice(
bond, *discountCurve, calculated, bondDayCount, n, frequencie);
if (std::fabs(price - price2) / price > tolerance) {
BOOST_ERROR("\nZ-spread recalculation failed:"
"\n issue: "
<< issue << "\n maturity: " << maturity
<< "\n coupon: " << io::rate(coupon)
<< "\n frequency: " << frequencie
<< "\n Z-spread: " << io::rate(spread)
<< (n == Compounded ? " compounded"