19#include <boost/make_shared.hpp>
20#include <boost/test/unit_test.hpp>
29#include <oret/toplevelfixture.hpp>
30#include <ql/termstructures/credit/flathazardrate.hpp>
31#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
32#include <ql/termstructures/yield/flatforward.hpp>
33#include <ql/time/calendars/target.hpp>
34#include <ql/time/daycounters/actualactual.hpp>
39using namespace boost::unit_test_framework;
50 asof_ = Date(3, Feb, 2016);
59 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.00));
61 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.00));
68 hEUR->addFixing(Date(1, Feb, 2016), -0.00191);
69 hEUR->addFixing(Date(1, Feb, 2017), -0.00191);
70 hEUR->addFixing(Date(1, Feb, 2018), -0.00191);
71 hEUR->addFixing(Date(1, Feb, 2019), -0.00191);
72 hEUR->addFixing(Date(31, Jan, 2019), -0.00191);
73 hEUR->addFixing(Date(30, Jan, 2020), -0.00191);
76 TestMarket(Real defaultFlatRate) :
MarketImpl(false) {
77 asof_ = Date(3, Feb, 2016);
85 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.00));
87 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.00));
91 Handle<YieldTermStructure> flatRateYts(Real forward) {
92 QuantLib::ext::shared_ptr<YieldTermStructure> yts(
new FlatForward(0, NullCalendar(), forward, ActualActual(ActualActual::ISDA)));
93 yts->enableExtrapolation();
94 return Handle<YieldTermStructure>(yts);
96 Handle<QuantExt::CreditCurve> flatRateDcs(Real forward) {
97 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> dcs(
98 new FlatHazardRate(asof_, forward, ActualActual(ActualActual::ISDA)));
99 return Handle<QuantExt::CreditCurve>(
100 QuantLib::ext::make_shared<QuantExt::CreditCurve>(Handle<DefaultProbabilityTermStructure>(dcs)));
108 string creditCurveId;
110 string referenceCurveId;
126 vector<Real> notionals;
130 QuantLib::ext::shared_ptr<ore::data::Bond> makeBond() {
134 LegData fixedLegData(QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, fixedRate)), isPayer, ccy,
135 fixedSchedule, fixDC, notionals);
139 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
141 issue, fixedLegData)));
145 QuantLib::ext::shared_ptr<ore::data::Bond> makeAmortizingFixedBond(
string amortType, Real
value,
bool underflow) {
149 LegData fixedLegData(QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, fixedRate)), isPayer, ccy,
150 fixedSchedule, fixDC, notionals, vector<string>(), conv,
false,
false,
false,
true,
"", 0,
151 "", {amortizationData});
155 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
157 issue, fixedLegData)));
161 QuantLib::ext::shared_ptr<ore::data::Bond> makeAmortizingFloatingBond(
string amortType, Real
value,
bool underflow) {
165 LegData floatingLegData(QuantLib::ext::make_shared<FloatingLegData>(
"EUR-EURIBOR-6M", 2,
false, spread), isPayer, ccy,
166 floatingSchedule, fixDC, notionals, vector<string>(), conv,
false,
false,
false,
true,
167 "", 0,
"", {amortizationData});
171 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
173 issue, floatingLegData)));
177 QuantLib::ext::shared_ptr<ore::data::Bond> makeAmortizingFixedBondWithChangingAmortisation(
string amortType1, Real value1,
178 bool underflow1,
string end1,
179 string amortType2, Real value2,
183 AmortizationData amortizationData1(amortType1, value1, start, end1, fixtenor, underflow1);
184 AmortizationData amortizationData2(amortType2, value2, end1, end, fixtenor, underflow2);
185 LegData fixedLegData(QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, fixedRate)), isPayer, ccy,
186 fixedSchedule, fixDC, notionals, vector<string>(), conv,
false,
false,
false,
true,
"", 0,
187 "", {amortizationData1, amortizationData2});
191 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
193 issue, fixedLegData)));
197 QuantLib::ext::shared_ptr<ore::data::Bond>
198 makeAmortizingFloatingBondWithChangingAmortisation(
string amortType1, Real value1,
bool underflow1,
string end1,
199 string amortType2, Real value2,
bool underflow2) {
203 AmortizationData amortizationData1(amortType1, value1, start, end1, fixtenor, underflow1);
204 AmortizationData amortizationData2(amortType2, value2, end1, end, fixtenor, underflow2);
205 LegData floatingLegData(QuantLib::ext::make_shared<FloatingLegData>(
"EUR-EURIBOR-6M", 2,
false, spread), isPayer, ccy,
206 floatingSchedule, fixDC, notionals, vector<string>(), conv,
false,
false,
false,
true,
207 "", 0,
"", {amortizationData1, amortizationData2});
211 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
213 issue, floatingLegData)));
217 QuantLib::ext::shared_ptr<ore::data::Bond> makeZeroBond() {
220 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
222 notional, end, ccy, issue)));
227 : ccy(
"EUR"), securityId(
"Security1"), creditCurveId(
"CreditCurve_A"), issuerId(
"CPTY_A"),
228 referenceCurveId(
"BANK_EUR_LEND"), isPayer(false), start(
"20160203"), end(
"20210203"), issue(
"20160203"),
239 notionals.push_back(10000000);
240 spread.push_back(0.0);
245void printBondSchedule(
const QuantLib::ext::shared_ptr<ore::data::Bond>& b) {
246 auto qlInstr = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(b->instrument()->qlInstrument());
247 BOOST_REQUIRE(qlInstr !=
nullptr);
248 BOOST_TEST_MESSAGE(
"Bond NPV=" << qlInstr->NPV() <<
", Schedule:");
249 Leg l = qlInstr->cashflows();
250 BOOST_TEST_MESSAGE(
" StartDate EndDate Nominal Rate Amount");
251 for (
auto const& c : l) {
252 auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c);
253 if (cpn !=
nullptr) {
254 BOOST_TEST_MESSAGE(QuantLib::io::iso_date(cpn->accrualStartDate())
255 <<
" " << QuantLib::io::iso_date(cpn->accrualEndDate()) << std::setw(12)
256 << cpn->nominal() << std::setw(12) << cpn->rate() << std::setw(12) << cpn->amount());
258 BOOST_TEST_MESSAGE(
" " << QuantLib::io::iso_date(c->date()) << std::setw(12) <<
" "
259 << std::setw(12) <<
" " << std::setw(12) << c->amount());
262 BOOST_TEST_MESSAGE(
"");
266void checkNominalSchedule(
const QuantLib::ext::shared_ptr<ore::data::Bond>& b,
const std::vector<Real> notionals) {
267 auto qlInstr = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(b->instrument()->qlInstrument());
268 BOOST_REQUIRE(qlInstr !=
nullptr);
269 Leg l = qlInstr->cashflows();
270 std::vector<Real> bondNotionals;
271 for (
auto const& c : l) {
272 auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c);
273 if (cpn !=
nullptr) {
274 bondNotionals.push_back(cpn->nominal());
277 BOOST_REQUIRE_EQUAL(bondNotionals.size(), notionals.size());
278 for (Size i = 0; i < notionals.size(); ++i)
279 BOOST_CHECK_CLOSE(bondNotionals[i], notionals[i], 1E-4);
284BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
286BOOST_AUTO_TEST_SUITE(BondTests)
289 BOOST_TEST_MESSAGE(
"Testing Zero Bond...");
292 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
293 Settings::instance().evaluationDate() = market->asofDate();
296 QuantLib::ext::shared_ptr<ore::data::Bond> bond = vars.makeZeroBond();
299 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
300 engineData->model(
"Bond") =
"DiscountedCashflows";
301 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
303 map<string, string> engineparams;
304 engineparams[
"TimestepPeriod"] =
"6M";
305 engineData->engineParameters(
"Bond") = engineparams;
307 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
309 bond->build(engineFactory);
311 Real npv = bond->instrument()->NPV();
312 Real expectedNpv = 9048374.18;
314 BOOST_CHECK_CLOSE(npv, expectedNpv, 1.0);
318 BOOST_TEST_MESSAGE(
"Testing Amortising Bonds...");
321 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
322 Date today = Date(30, Jan, 2021);
323 Settings::instance().evaluationDate() = today;
326 vector<QuantLib::ext::shared_ptr<ore::data::Bond>> bonds;
327 QuantLib::ext::shared_ptr<ore::data::Bond> bondFixedAmount = vars.makeAmortizingFixedBond(
"FixedAmount", 2500000,
true);
328 bonds.push_back(bondFixedAmount);
330 QuantLib::ext::shared_ptr<ore::data::Bond> bondRelativeInitial =
331 vars.makeAmortizingFixedBond(
"RelativeToInitialNotional", 0.25,
true);
332 bonds.push_back(bondRelativeInitial);
335 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
336 engineData->model(
"Bond") =
"DiscountedCashflows";
337 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
339 map<string, string> engineparams;
340 engineparams[
"TimestepPeriod"] =
"6M";
341 engineData->engineParameters(
"Bond") = engineparams;
343 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
347 for (
auto& b : bonds) {
348 b->build(engineFactory);
351 printBondSchedule(b);
353 Real npv = b->instrument()->NPV();
354 Real expectedNpv = 0.0;
356 BOOST_CHECK(std::fabs(npv - expectedNpv) < npvTol);
359 QuantLib::ext::shared_ptr<ore::data::Bond> bondRelativePrevious =
360 vars.makeAmortizingFixedBond(
"RelativeToPreviousNotional", 0.25,
true);
361 bondRelativePrevious->build(engineFactory);
362 printBondSchedule(bondRelativePrevious);
364 QuantLib::ext::shared_ptr<QuantLib::Instrument> inst1 = bondRelativePrevious->instrument()->qlInstrument();
365 QuantLib::ext::shared_ptr<QuantLib::Bond> qlBond1 = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(inst1);
366 Real expectedNotional = 3164062.5;
368 Real notional = qlBond1->notionals()[qlBond1->notionals().size() - 2];
370 BOOST_CHECK_CLOSE(notional, expectedNotional, 1);
372 QuantLib::ext::shared_ptr<ore::data::Bond> bondFixedAnnuity = vars.makeAmortizingFixedBond(
"Annuity", 2500000,
true);
373 bondFixedAnnuity->build(engineFactory);
374 printBondSchedule(bondFixedAnnuity);
376 QuantLib::ext::shared_ptr<QuantLib::Instrument> inst2 = bondFixedAnnuity->instrument()->qlInstrument();
377 QuantLib::ext::shared_ptr<QuantLib::Bond> qlBond2 = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(inst2);
378 expectedNotional = 1380908.447;
380 notional = qlBond2->notionals()[qlBond2->notionals().size() - 2];
382 BOOST_CHECK(std::fabs(notional - expectedNotional) < npvTol);
384 QuantLib::ext::shared_ptr<ore::data::Bond> bondFloatingAnnuity = vars.makeAmortizingFloatingBond(
"Annuity", 2500000,
true);
385 bondFloatingAnnuity->build(engineFactory);
386 printBondSchedule(bondFloatingAnnuity);
388 QuantLib::ext::shared_ptr<QuantLib::Instrument> inst3 = bondFloatingAnnuity->instrument()->qlInstrument();
389 QuantLib::ext::shared_ptr<QuantLib::Bond> qlBond3 = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(inst3);
390 Real expectedAmount = 93.41;
392 Real amount = qlBond3->cashflows()[qlBond3->cashflows().size() - 2]->amount();
394 BOOST_CHECK(std::fabs(amount - expectedAmount) < npvTol);
398 BOOST_TEST_MESSAGE(
"Testing Amortising Bonds with changing amortisation...");
401 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
402 Date today = Date(30, Jan, 2021);
403 Settings::instance().evaluationDate() = today;
406 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
407 engineData->model(
"Bond") =
"DiscountedCashflows";
408 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
409 map<string, string> engineparams;
410 engineparams[
"TimestepPeriod"] =
"6M";
411 engineData->engineParameters(
"Bond") = engineparams;
412 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
418 QuantLib::ext::shared_ptr<ore::data::Bond> bond1 = vars.makeAmortizingFixedBondWithChangingAmortisation(
419 "FixedAmount", 2500000,
true,
"05-02-2018",
"FixedAmount", 1250000,
true);
420 bond1->build(engineFactory);
421 printBondSchedule(bond1);
422 checkNominalSchedule(bond1, {1.0E7, 7.5E6, 6.25E6, 5.0E6, 3.75E6});
424 QuantLib::ext::shared_ptr<ore::data::Bond> bond2 = vars.makeAmortizingFixedBondWithChangingAmortisation(
425 "FixedAmount", 2500000,
true,
"05-02-2018",
"RelativeToInitialNotional", 0.1,
true);
426 bond2->build(engineFactory);
427 printBondSchedule(bond2);
428 checkNominalSchedule(bond2, {1.0E7, 7.5E6, 6.5E6, 5.5E6, 4.5E6});
430 QuantLib::ext::shared_ptr<ore::data::Bond> bond3 = vars.makeAmortizingFixedBondWithChangingAmortisation(
431 "RelativeToPreviousNotional", 0.1,
true,
"05-02-2018",
"Annuity", 1E6,
true);
432 bond3->build(engineFactory);
433 printBondSchedule(bond3);
434 checkNominalSchedule(bond3, {1.0E7, 9.0E6, 8.45247E6, 7.87393E6, 7.26645E6});
436 QuantLib::ext::shared_ptr<ore::data::Bond> bond4 = vars.makeAmortizingFixedBondWithChangingAmortisation(
437 "Annuity", 1E6,
true,
"05-02-2018",
"RelativeToPreviousNotional", 0.1,
true);
438 bond4->build(engineFactory);
439 printBondSchedule(bond4);
440 checkNominalSchedule(bond4, {1.0E7, 9.50012E6, 8.55011E6, 7.6951E6, 6.92559E6});
443 QuantLib::ext::shared_ptr<ore::data::Bond> bond5 = vars.makeAmortizingFloatingBondWithChangingAmortisation(
444 "FixedAmount", 2500000,
true,
"05-02-2018",
"FixedAmount", 1250000,
true);
445 bond5->build(engineFactory);
446 printBondSchedule(bond5);
447 checkNominalSchedule(bond5, {1.0E7, 7.5E6, 6.25E6, 5.0E6, 3.75E6});
449 QuantLib::ext::shared_ptr<ore::data::Bond> bond6 = vars.makeAmortizingFloatingBondWithChangingAmortisation(
450 "FixedAmount", 2500000,
true,
"05-02-2018",
"RelativeToInitialNotional", 0.1,
true);
451 bond6->build(engineFactory);
452 printBondSchedule(bond6);
453 checkNominalSchedule(bond6, {1.0E7, 7.5E6, 6.5E6, 5.5E6, 4.5E6});
456 QuantLib::ext::shared_ptr<ore::data::Bond> bond7 = vars.makeAmortizingFloatingBondWithChangingAmortisation(
457 "RelativeToPreviousNotional", 0.1,
true,
"05-02-2018",
"Annuity", 1E6,
true);
458 BOOST_CHECK_THROW(bond7->build(engineFactory), QuantLib::Error);
463 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
464 Date today = Date(30, Jan, 2021);
465 Settings::instance().evaluationDate() = today;
468 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
469 engineData->model(
"Bond") =
"DiscountedCashflows";
470 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
471 map<string, string> engineparams;
472 engineparams[
"TimestepPeriod"] =
"6M";
473 engineData->engineParameters(
"Bond") = engineparams;
474 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
480 auto fixedLegRateData = QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, 0.01));
481 LegData legdata1(fixedLegRateData, vars.isPayer, vars.ccy, schedule1, vars.fixDC, vars.notionals);
482 LegData legdata2(fixedLegRateData, vars.isPayer, vars.ccy, schedule2, vars.fixDC, vars.notionals);
484 QuantLib::ext::shared_ptr<ore::data::Bond> bond(
486 vars.settledays, vars.calStr, vars.issue, {legdata1, legdata2})));
487 bond->build(engineFactory);
488 printBondSchedule(bond);
489 auto qlInstr = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(bond->instrument()->qlInstrument());
490 BOOST_REQUIRE(qlInstr !=
nullptr);
492 BOOST_REQUIRE_EQUAL(qlInstr->cashflows().size(), 7);
493 BOOST_CHECK_EQUAL(qlInstr->cashflows()[0]->date(), Date(6, Feb, 2017));
494 BOOST_CHECK_EQUAL(qlInstr->cashflows()[1]->date(), Date(5, Feb, 2018));
496 BOOST_CHECK_EQUAL(qlInstr->cashflows()[2]->date(), Date(6, Aug, 2018));
497 BOOST_CHECK_EQUAL(qlInstr->cashflows()[3]->date(), Date(5, Feb, 2019));
498 BOOST_CHECK_EQUAL(qlInstr->cashflows()[4]->date(), Date(5, Aug, 2019));
499 BOOST_CHECK_EQUAL(qlInstr->cashflows()[5]->date(), Date(5, Feb, 2020));
500 BOOST_CHECK_EQUAL(qlInstr->cashflows()[6]->date(), Date(5, Feb, 2020));
504 BOOST_TEST_MESSAGE(
"Testing Bond price...");
507 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
508 Settings::instance().evaluationDate() = market->asofDate();
511 QuantLib::ext::shared_ptr<ore::data::Bond> bond = vars.makeBond();
514 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
515 engineData->model(
"Bond") =
"DiscountedCashflows";
516 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
518 map<string, string> engineparams;
519 engineparams[
"TimestepPeriod"] =
"6M";
520 engineData->engineParameters(
"Bond") = engineparams;
522 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
524 bond->build(engineFactory);
526 Real npv = bond->instrument()->NPV();
527 Real expectedNpv = 11403727.39;
529 BOOST_CHECK_CLOSE(npv, expectedNpv, 1.0);
533 BOOST_TEST_MESSAGE(
"Testing Bond price...");
536 QuantLib::ext::shared_ptr<Market> market1 = QuantLib::ext::make_shared<TestMarket>(0.0);
537 QuantLib::ext::shared_ptr<Market> market2 = QuantLib::ext::make_shared<TestMarket>(0.5);
538 QuantLib::ext::shared_ptr<Market> market3 = QuantLib::ext::make_shared<TestMarket>(0.99);
539 Settings::instance().evaluationDate() = market1->asofDate();
542 QuantLib::ext::shared_ptr<ore::data::Bond> bond = vars.makeBond();
545 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
546 engineData->model(
"Bond") =
"DiscountedCashflows";
547 engineData->engine(
"Bond") =
"DiscountingRiskyBondEngine";
548 map<string, string> engineparams;
549 engineparams[
"TimestepPeriod"] =
"6M";
550 engineData->engineParameters(
"Bond") = engineparams;
552 QuantLib::ext::shared_ptr<EngineFactory> engineFactory1 = QuantLib::ext::make_shared<EngineFactory>(engineData, market1);
553 QuantLib::ext::shared_ptr<EngineFactory> engineFactory2 = QuantLib::ext::make_shared<EngineFactory>(engineData, market2);
554 QuantLib::ext::shared_ptr<EngineFactory> engineFactory3 = QuantLib::ext::make_shared<EngineFactory>(engineData, market3);
556 bond->build(engineFactory1);
557 Real npv1 = bond->instrument()->NPV();
558 bond->build(engineFactory2);
559 Real npv2 = bond->instrument()->NPV();
560 bond->build(engineFactory3);
561 Real npv3 = bond->instrument()->NPV();
563 BOOST_CHECK((npv1 > npv2) && (npv2 > npv3));
568BOOST_AUTO_TEST_SUITE_END()
570BOOST_AUTO_TEST_SUITE_END()
Bond trade data model and serialization.
builder that returns an engine to price a bond instrument
Serializable object holding amortization rules.
Serializable object holding generic trade data, reporting dimensions.
Serializable Floating Leg Data.
Serializable object holding leg data.
static const string defaultConfiguration
Default configuration label.
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
map< pair< string, string >, Handle< QuantExt::CreditCurve > > defaultCurves_
map< pair< string, string >, Handle< IborIndex > > iborIndices_
map< pair< string, string >, Handle< Quote > > recoveryRates_
map< pair< string, string >, Handle< Quote > > securitySpreads_
Serializable schedule data.
Serializable object holding schedule Rules data.
SafeStack< ValueType > value
A class to hold pricing engine parameters.
trade envelope data model and serialization
QuantLib::ext::shared_ptr< IborIndex > parseIborIndex(const string &s, const Handle< YieldTermStructure > &h)
Convert std::string to QuantLib::IborIndex.
Map text representations to QuantLib/QuantExt types.
leg data model and serialization
An implementation of the Market class that stores the required objects in maps.
trade schedule data model and serialization
BOOST_AUTO_TEST_CASE(testZeroBond)