1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2012, 2013 Grzegorz Andruszkiewicz
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "catbonds.hpp"
21#include "utilities.hpp"
22#include <ql/types.hpp>
23#include <ql/experimental/catbonds/catbond.hpp>
24#include <ql/experimental/catbonds/catrisk.hpp>
25#include <ql/experimental/catbonds/montecarlocatbondengine.hpp>
26#include <ql/instruments/bonds/floatingratebond.hpp>
27#include <ql/time/calendars/target.hpp>
28#include <ql/time/calendars/unitedstates.hpp>
29#include <ql/time/calendars/brazil.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
31#include <ql/time/daycounters/thirty360.hpp>
32#include <ql/time/daycounters/actual360.hpp>
33#include <ql/time/daycounters/actualactual.hpp>
34#include <ql/time/daycounters/business252.hpp>
35#include <ql/indexes/ibor/usdlibor.hpp>
36#include <ql/quotes/simplequote.hpp>
37#include <ql/utilities/dataformatters.hpp>
38#include <ql/time/schedule.hpp>
39#include <ql/cashflows/fixedratecoupon.hpp>
40#include <ql/cashflows/simplecashflow.hpp>
41#include <ql/cashflows/couponpricer.hpp>
42#include <ql/cashflows/cashflows.hpp>
43#include <ql/cashflows/iborcoupon.hpp>
44#include <ql/pricingengines/bond/discountingbondengine.hpp>
45#include <ql/pricingengines/bond/bondfunctions.hpp>
46#include <ql/termstructures/yield/flatforward.hpp>
47
48using namespace QuantLib;
49using namespace boost::unit_test_framework;
50
51namespace catbonds_test {
52 std::pair<Date, Real> data[] = {std::pair<Date, Real>(Date(1, February, 2012), 100), std::pair<Date, Real>(Date(1, July, 2013), 150), std::pair<Date, Real>(Date(5, January, 2014), 50)};
53 ext::shared_ptr<std::vector<std::pair<Date, Real> > > sampleEvents(new std::vector<std::pair<Date, Real> >(data, data+3));
54
55 Date eventsStart(1, January, 2011);
56 Date eventsEnd(31, December, 2014);
57}
58
59void CatBondTest::testEventSetForWholeYears() {
60 BOOST_TEST_MESSAGE("Testing that catastrophe events are split correctly for periods of whole years...");
61
62 using namespace catbonds_test;
63
64 EventSet catRisk(sampleEvents, eventsStart, eventsEnd);
65 ext::shared_ptr<CatSimulation> simulation = catRisk.newSimulation(start: Date(1, January, 2015), end: Date(31, December, 2015));
66
67 BOOST_REQUIRE(simulation);
68
69 std::vector<std::pair<Date, Real> > path;
70
71 BOOST_REQUIRE(simulation->nextPath(path));
72 BOOST_CHECK_EQUAL(Size(0), path.size());
73
74 BOOST_REQUIRE(simulation->nextPath(path));
75 BOOST_CHECK_EQUAL(Size(1), path.size());
76 BOOST_CHECK_EQUAL(Date(1, February, 2015), path.at(0).first);
77 BOOST_CHECK_EQUAL(100, path.at(0).second);
78
79 BOOST_REQUIRE(simulation->nextPath(path));
80 BOOST_CHECK_EQUAL(Size(1), path.size());
81 BOOST_CHECK_EQUAL(Date(1, July, 2015), path.at(0).first);
82 BOOST_CHECK_EQUAL(150, path.at(0).second);
83
84 BOOST_REQUIRE(simulation->nextPath(path));
85 BOOST_CHECK_EQUAL(Size(1), path.size());
86 BOOST_CHECK_EQUAL(Date(5, January, 2015), path.at(0).first);
87 BOOST_CHECK_EQUAL(50, path.at(0).second);
88
89 BOOST_REQUIRE(!simulation->nextPath(path));
90}
91
92
93void CatBondTest::testEventSetForIrregularPeriods() {
94 BOOST_TEST_MESSAGE("Testing that catastrophe events are split correctly for irregular periods...");
95
96 using namespace catbonds_test;
97
98 EventSet catRisk(sampleEvents, eventsStart, eventsEnd);
99 ext::shared_ptr<CatSimulation> simulation = catRisk.newSimulation(start: Date(2, January, 2015), end: Date(5, January, 2016));
100
101 BOOST_REQUIRE(simulation);
102
103 std::vector<std::pair<Date, Real> > path;
104
105 BOOST_REQUIRE(simulation->nextPath(path));
106 BOOST_CHECK_EQUAL(Size(0), path.size());
107
108 BOOST_REQUIRE(simulation->nextPath(path));
109 BOOST_CHECK_EQUAL(Size(2), path.size());
110 BOOST_CHECK_EQUAL(Date(1, July, 2015), path.at(0).first);
111 BOOST_CHECK_EQUAL(150, path.at(0).second);
112 BOOST_CHECK_EQUAL(Date(5, January, 2016), path.at(1).first);
113 BOOST_CHECK_EQUAL(50, path.at(1).second);
114
115 BOOST_REQUIRE(!simulation->nextPath(path));
116}
117
118
119void CatBondTest::testEventSetForNoEvents () {
120 BOOST_TEST_MESSAGE("Testing that catastrophe events are split correctly when there are no simulated events...");
121
122 using namespace catbonds_test;
123
124 ext::shared_ptr<std::vector<std::pair<Date, Real> > > emptyEvents(new std::vector<std::pair<Date, Real> >());
125 EventSet catRisk(emptyEvents, eventsStart, eventsEnd);
126 ext::shared_ptr<CatSimulation> simulation = catRisk.newSimulation(start: Date(2, January, 2015), end: Date(5, January, 2016));
127
128 BOOST_REQUIRE(simulation);
129
130 std::vector<std::pair<Date, Real> > path;
131
132 BOOST_REQUIRE(simulation->nextPath(path));
133 BOOST_CHECK_EQUAL(Size(0), path.size());
134
135 BOOST_REQUIRE(simulation->nextPath(path));
136 BOOST_CHECK_EQUAL(Size(0), path.size());
137
138 BOOST_REQUIRE(!simulation->nextPath(path));
139}
140
141void CatBondTest::testBetaRisk() {
142 BOOST_TEST_MESSAGE("Testing that beta risk gives correct terminal distribution...");
143
144 const size_t PATHS = 1000000;
145 BetaRisk catRisk(100.0, 100.0, 10.0, 15.0);
146 ext::shared_ptr<CatSimulation> simulation = catRisk.newSimulation(start: Date(2, January, 2015), end: Date(2, January, 2018));
147 BOOST_REQUIRE(simulation);
148
149 std::vector<std::pair<Date, Real> > path;
150 Real sum = 0.0;
151 Real sumSquares = 0.0;
152 Real poissonSum = 0.0;
153 Real poissonSumSquares = 0.0;
154
155
156 for(size_t i=0; i<PATHS; ++i)
157 {
158 if (!simulation->nextPath(path))
159 BOOST_FAIL("No next path available");
160 Real processValue = 0.0;
161 for (auto& j : path)
162 processValue += j.second;
163 sum+=processValue;
164 sumSquares+=processValue*processValue;
165 poissonSum+=path.size();
166 poissonSumSquares+=path.size()*path.size();
167 }
168 Real poissonMean = poissonSum/PATHS;
169 QL_CHECK_CLOSE(Real(3.0/100.0), poissonMean, 2);
170 Real poissonVar = poissonSumSquares/PATHS - poissonMean*poissonMean;
171 QL_CHECK_CLOSE(Real(3.0/100.0), poissonVar, 5);
172
173 Real expectedMean = 3.0*10.0/100.0;
174 Real actualMean = sum/PATHS;
175 #ifdef _LIBCPP_VERSION
176 QL_CHECK_CLOSE(expectedMean, actualMean, 5);
177 #else
178 QL_CHECK_CLOSE(expectedMean, actualMean, 1);
179 #endif
180
181 Real expectedVar = 3.0*(15.0*15.0+10*10)/100.0;
182 Real actualVar = sumSquares/PATHS - actualMean*actualMean;
183 #ifdef _LIBCPP_VERSION
184 QL_CHECK_CLOSE(expectedVar, actualVar, 10);
185 #else
186 QL_CHECK_CLOSE(expectedVar, actualVar, 1);
187 #endif
188}
189
190namespace catbonds_test {
191
192 struct CommonVars {
193 // common data
194 Calendar calendar;
195 Date today;
196 Real faceAmount;
197
198 // setup
199 CommonVars() {
200 calendar = TARGET();
201 today = calendar.adjust(Date::todaysDate());
202 Settings::instance().evaluationDate() = today;
203 faceAmount = 1000000.0;
204 }
205 };
206}
207
208void CatBondTest::testRiskFreeAgainstFloatingRateBond() {
209 BOOST_TEST_MESSAGE("Testing floating-rate cat bond against risk-free floating-rate bond...");
210
211 using namespace catbonds_test;
212
213 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
214
215 CommonVars vars;
216
217 Date today(22,November,2004);
218 Settings::instance().evaluationDate() = today;
219
220 Natural settlementDays = 1;
221
222 Handle<YieldTermStructure> riskFreeRate(flatRate(today,forward: 0.025,dc: Actual360()));
223 Handle<YieldTermStructure> discountCurve(flatRate(today,forward: 0.03,dc: Actual360()));
224
225 ext::shared_ptr<IborIndex> index(new USDLibor(6*Months, riskFreeRate));
226 Natural fixingDays = 1;
227
228 Real tolerance = 1.0e-6;
229
230 ext::shared_ptr<IborCouponPricer> pricer(new
231 BlackIborCouponPricer(Handle<OptionletVolatilityStructure>()));
232
233 // plain
234
235 Schedule sch(Date(30,November,2004),
236 Date(30,November,2008),
237 Period(Semiannual),
238 UnitedStates(UnitedStates::GovernmentBond),
239 ModifiedFollowing, ModifiedFollowing,
240 DateGeneration::Backward, false);
241
242 ext::shared_ptr<CatRisk> noCatRisk(new EventSet(
243 ext::make_shared<std::vector<std::pair<Date, Real> > >(),
244 Date(1, Jan, 2000), Date(31, Dec, 2010)));
245
246 ext::shared_ptr<EventPaymentOffset> paymentOffset(new NoOffset());
247 ext::shared_ptr<NotionalRisk> notionalRisk(new DigitalNotionalRisk(paymentOffset, 100));
248
249 FloatingRateBond bond1(settlementDays, vars.faceAmount, sch,
250 index, ActualActual(ActualActual::ISMA),
251 ModifiedFollowing, fixingDays,
252 std::vector<Real>(), std::vector<Spread>(),
253 std::vector<Rate>(), std::vector<Rate>(),
254 false,
255 100.0, Date(30,November,2004));
256
257 FloatingCatBond catBond1(settlementDays, vars.faceAmount, sch,
258 index, ActualActual(ActualActual::ISMA),
259 notionalRisk,
260 ModifiedFollowing, fixingDays,
261 std::vector<Real>(), std::vector<Spread>(),
262 std::vector<Rate>(), std::vector<Rate>(),
263 false,
264 100.0, Date(30,November,2004));
265
266 ext::shared_ptr<PricingEngine> bondEngine(
267 new DiscountingBondEngine(riskFreeRate));
268 bond1.setPricingEngine(bondEngine);
269 setCouponPricer(leg: bond1.cashflows(),pricer);
270
271 ext::shared_ptr<PricingEngine> catBondEngine(new MonteCarloCatBondEngine(noCatRisk, riskFreeRate));
272 catBond1.setPricingEngine(catBondEngine);
273 setCouponPricer(leg: catBond1.cashflows(),pricer);
274
275 Real cachedPrice1 = usingAtParCoupons ? 99.874646 : 99.874645;
276
277 Real price = bond1.cleanPrice();
278 Real catPrice = catBond1.cleanPrice();
279 if (std::fabs(x: price-cachedPrice1) > tolerance || std::fabs(x: catPrice-price) > tolerance) {
280 BOOST_FAIL("failed to reproduce floating rate bond price:\n"
281 << std::fixed
282 << " floating bond: " << price << "\n"
283 << " catBond bond: " << catPrice << "\n"
284 << " expected: " << cachedPrice1 << "\n"
285 << " error: " << catPrice-price);
286 }
287
288
289
290 // different risk-free and discount curve
291
292 FloatingRateBond bond2(settlementDays, vars.faceAmount, sch,
293 index, ActualActual(ActualActual::ISMA),
294 ModifiedFollowing, fixingDays,
295 std::vector<Rate>(), std::vector<Spread>(),
296 std::vector<Rate>(), std::vector<Rate>(),
297 false,
298 100.0, Date(30,November,2004));
299
300 FloatingCatBond catBond2(settlementDays, vars.faceAmount, sch,
301 index, ActualActual(ActualActual::ISMA),
302 notionalRisk,
303 ModifiedFollowing, fixingDays,
304 std::vector<Rate>(), std::vector<Spread>(),
305 std::vector<Rate>(), std::vector<Rate>(),
306 false,
307 100.0, Date(30,November,2004));
308
309 ext::shared_ptr<PricingEngine> bondEngine2(
310 new DiscountingBondEngine(discountCurve));
311 bond2.setPricingEngine(bondEngine2);
312 setCouponPricer(leg: bond2.cashflows(),pricer);
313
314 ext::shared_ptr<PricingEngine> catBondEngine2(new MonteCarloCatBondEngine(noCatRisk, discountCurve));
315 catBond2.setPricingEngine(catBondEngine2);
316 setCouponPricer(leg: catBond2.cashflows(),pricer);
317
318 Real cachedPrice2 = 97.955904;
319
320 price = bond2.cleanPrice();
321 catPrice = catBond2.cleanPrice();
322 if (std::fabs(x: price-cachedPrice2) > tolerance || std::fabs(x: catPrice-price) > tolerance) {
323 BOOST_FAIL("failed to reproduce floating rate bond price:\n"
324 << std::fixed
325 << " floating bond: " << price << "\n"
326 << " catBond bond: " << catPrice << "\n"
327 << " expected: " << cachedPrice2 << "\n"
328 << " error: " << catPrice-price);
329 }
330
331 // varying spread
332
333 std::vector<Rate> spreads(4);
334 spreads[0] = 0.001;
335 spreads[1] = 0.0012;
336 spreads[2] = 0.0014;
337 spreads[3] = 0.0016;
338
339 FloatingRateBond bond3(settlementDays, vars.faceAmount, sch,
340 index, ActualActual(ActualActual::ISMA),
341 ModifiedFollowing, fixingDays,
342 std::vector<Real>(), spreads,
343 std::vector<Rate>(), std::vector<Rate>(),
344 false,
345 100.0, Date(30,November,2004));
346
347 FloatingCatBond catBond3(settlementDays, vars.faceAmount, sch,
348 index, ActualActual(ActualActual::ISMA),
349 notionalRisk,
350 ModifiedFollowing, fixingDays,
351 std::vector<Real>(), spreads,
352 std::vector<Rate>(), std::vector<Rate>(),
353 false,
354 100.0, Date(30,November,2004));
355
356 bond3.setPricingEngine(bondEngine2);
357 setCouponPricer(leg: bond3.cashflows(),pricer);
358
359 catBond3.setPricingEngine(catBondEngine2);
360 setCouponPricer(leg: catBond3.cashflows(),pricer);
361
362 Real cachedPrice3 = usingAtParCoupons ? 98.495459 : 98.495458;
363
364 price = bond3.cleanPrice();
365 catPrice = catBond3.cleanPrice();
366 if (std::fabs(x: price-cachedPrice3) > tolerance || std::fabs(x: catPrice-price) > tolerance) {
367 BOOST_FAIL("failed to reproduce floating rate bond price:\n"
368 << std::fixed
369 << " floating bond: " << price << "\n"
370 << " catBond bond: " << catPrice << "\n"
371 << " expected: " << cachedPrice2 << "\n"
372 << " error: " << catPrice-price);
373 }
374}
375
376
377
378void CatBondTest::testCatBondInDoomScenario() {
379 BOOST_TEST_MESSAGE("Testing floating-rate cat bond in a doom scenario (certain default)...");
380
381 using namespace catbonds_test;
382
383 CommonVars vars;
384
385 Date today(22,November,2004);
386 Settings::instance().evaluationDate() = today;
387
388 Natural settlementDays = 1;
389
390 Handle<YieldTermStructure> riskFreeRate(flatRate(today,forward: 0.025,dc: Actual360()));
391 Handle<YieldTermStructure> discountCurve(flatRate(today,forward: 0.03,dc: Actual360()));
392
393 ext::shared_ptr<IborIndex> index(new USDLibor(6*Months, riskFreeRate));
394 Natural fixingDays = 1;
395
396 Real tolerance = 1.0e-6;
397
398 ext::shared_ptr<IborCouponPricer> pricer(new
399 BlackIborCouponPricer(Handle<OptionletVolatilityStructure>()));
400
401 Schedule sch(Date(30,November,2004),
402 Date(30,November,2008),
403 Period(Semiannual),
404 UnitedStates(UnitedStates::GovernmentBond),
405 ModifiedFollowing, ModifiedFollowing,
406 DateGeneration::Backward, false);
407
408 ext::shared_ptr<std::vector<std::pair<Date, Real> > > events(new std::vector<std::pair<Date, Real> >());
409 events->emplace_back(args: Date(30,November,2004), args: 1000);
410 ext::shared_ptr<CatRisk> doomCatRisk(new EventSet(
411 events,
412 Date(30,November,2004), Date(30,November,2008)));
413
414 ext::shared_ptr<EventPaymentOffset> paymentOffset(new NoOffset());
415 ext::shared_ptr<NotionalRisk> notionalRisk(new DigitalNotionalRisk(paymentOffset, 100));
416
417 FloatingCatBond catBond(settlementDays, vars.faceAmount, sch,
418 index, ActualActual(ActualActual::ISMA),
419 notionalRisk,
420 ModifiedFollowing, fixingDays,
421 std::vector<Rate>(), std::vector<Spread>(),
422 std::vector<Rate>(), std::vector<Rate>(),
423 false,
424 100.0, Date(30,November,2004));
425
426 ext::shared_ptr<PricingEngine> catBondEngine(new MonteCarloCatBondEngine(doomCatRisk, discountCurve));
427 catBond.setPricingEngine(catBondEngine);
428 setCouponPricer(leg: catBond.cashflows(),pricer);
429
430 Real price = catBond.cleanPrice();
431 BOOST_CHECK_EQUAL(0, price);
432
433 Real lossProbability = catBond.lossProbability();
434 Real exhaustionProbability = catBond.exhaustionProbability();
435 Real expectedLoss = catBond.expectedLoss();
436
437 QL_CHECK_CLOSE(Real(1.0), lossProbability, tolerance);
438 QL_CHECK_CLOSE(Real(1.0), exhaustionProbability, tolerance);
439 QL_CHECK_CLOSE(Real(1.0), expectedLoss, tolerance);
440}
441
442
443void CatBondTest::testCatBondWithDoomOnceInTenYears() {
444 BOOST_TEST_MESSAGE("Testing floating-rate cat bond in a doom once in 10 years scenario...");
445
446 using namespace catbonds_test;
447
448 CommonVars vars;
449
450 Date today(22,November,2004);
451 Settings::instance().evaluationDate() = today;
452
453 Natural settlementDays = 1;
454
455 Handle<YieldTermStructure> riskFreeRate(flatRate(today,forward: 0.025,dc: Actual360()));
456 Handle<YieldTermStructure> discountCurve(flatRate(today,forward: 0.03,dc: Actual360()));
457
458 ext::shared_ptr<IborIndex> index(new USDLibor(6*Months, riskFreeRate));
459 Natural fixingDays = 1;
460
461 Real tolerance = 1.0e-6;
462
463 ext::shared_ptr<IborCouponPricer> pricer(new
464 BlackIborCouponPricer(Handle<OptionletVolatilityStructure>()));
465
466 Schedule sch(Date(30,November,2004),
467 Date(30,November,2008),
468 Period(Semiannual),
469 UnitedStates(UnitedStates::GovernmentBond),
470 ModifiedFollowing, ModifiedFollowing,
471 DateGeneration::Backward, false);
472
473 ext::shared_ptr<std::vector<std::pair<Date, Real> > > events(new std::vector<std::pair<Date, Real> >());
474 events->emplace_back(args: Date(30,November,2008), args: 1000);
475 ext::shared_ptr<CatRisk> doomCatRisk(new EventSet(
476 events,
477 Date(30,November,2004), Date(30,November,2044)));
478
479 ext::shared_ptr<CatRisk> noCatRisk(new EventSet(
480 ext::make_shared<std::vector<std::pair<Date, Real> > >(),
481 Date(1, Jan, 2000), Date(31, Dec, 2010)));
482
483 ext::shared_ptr<EventPaymentOffset> paymentOffset(new NoOffset());
484 ext::shared_ptr<NotionalRisk> notionalRisk(new DigitalNotionalRisk(paymentOffset, 100));
485
486 FloatingCatBond catBond(settlementDays, vars.faceAmount, sch,
487 index, ActualActual(ActualActual::ISMA),
488 notionalRisk,
489 ModifiedFollowing, fixingDays,
490 std::vector<Rate>(), std::vector<Spread>(),
491 std::vector<Rate>(), std::vector<Rate>(),
492 false,
493 100.0, Date(30,November,2004));
494
495 ext::shared_ptr<PricingEngine> catBondEngine(new MonteCarloCatBondEngine(doomCatRisk, discountCurve));
496 catBond.setPricingEngine(catBondEngine);
497 setCouponPricer(leg: catBond.cashflows(),pricer);
498
499 Real price = catBond.cleanPrice();
500 Real yield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
501 Real lossProbability = catBond.lossProbability();
502 Real exhaustionProbability = catBond.exhaustionProbability();
503 Real expectedLoss = catBond.expectedLoss();
504
505 QL_CHECK_CLOSE(Real(0.1), lossProbability, tolerance);
506 QL_CHECK_CLOSE(Real(0.1), exhaustionProbability, tolerance);
507 QL_CHECK_CLOSE(Real(0.1), expectedLoss, tolerance);
508
509 ext::shared_ptr<PricingEngine> catBondEngineRF(new MonteCarloCatBondEngine(noCatRisk, discountCurve));
510 catBond.setPricingEngine(catBondEngineRF);
511
512 Real riskFreePrice = catBond.cleanPrice();
513 Real riskFreeYield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
514 Real riskFreeLossProbability = catBond.lossProbability();
515 Real riskFreeExhaustionProbability = catBond.exhaustionProbability();
516 Real riskFreeExpectedLoss = catBond.expectedLoss();
517
518 QL_CHECK_CLOSE(Real(0.0), riskFreeLossProbability, tolerance);
519 QL_CHECK_CLOSE(Real(0.0), riskFreeExhaustionProbability, tolerance);
520 BOOST_CHECK(std::abs(riskFreeExpectedLoss) < tolerance);
521
522 QL_CHECK_CLOSE(riskFreePrice*0.9, price, tolerance);
523 BOOST_CHECK_LT(riskFreeYield, yield);
524}
525
526void CatBondTest::testCatBondWithDoomOnceInTenYearsProportional() {
527 BOOST_TEST_MESSAGE("Testing floating-rate cat bond in a doom once in 10 years scenario with proportional notional reduction...");
528
529 using namespace catbonds_test;
530
531 CommonVars vars;
532
533 Date today(22,November,2004);
534 Settings::instance().evaluationDate() = today;
535
536 Natural settlementDays = 1;
537
538 Handle<YieldTermStructure> riskFreeRate(flatRate(today,forward: 0.025,dc: Actual360()));
539 Handle<YieldTermStructure> discountCurve(flatRate(today,forward: 0.03,dc: Actual360()));
540
541 ext::shared_ptr<IborIndex> index(new USDLibor(6*Months, riskFreeRate));
542 Natural fixingDays = 1;
543
544 Real tolerance = 1.0e-6;
545
546 ext::shared_ptr<IborCouponPricer> pricer(new
547 BlackIborCouponPricer(Handle<OptionletVolatilityStructure>()));
548
549 Schedule sch(Date(30,November,2004),
550 Date(30,November,2008),
551 Period(Semiannual),
552 UnitedStates(UnitedStates::GovernmentBond),
553 ModifiedFollowing, ModifiedFollowing,
554 DateGeneration::Backward, false);
555
556 ext::shared_ptr<std::vector<std::pair<Date, Real> > > events(new std::vector<std::pair<Date, Real> >());
557 events->emplace_back(args: Date(30,November,2008), args: 1000);
558 ext::shared_ptr<CatRisk> doomCatRisk(new EventSet(
559 events,
560 Date(30,November,2004), Date(30,November,2044)));
561
562 ext::shared_ptr<CatRisk> noCatRisk(new EventSet(
563 ext::make_shared<std::vector<std::pair<Date, Real> > >(),
564 Date(1, Jan, 2000), Date(31, Dec, 2010)));
565
566 ext::shared_ptr<EventPaymentOffset> paymentOffset(new NoOffset());
567 ext::shared_ptr<NotionalRisk> notionalRisk(new ProportionalNotionalRisk(paymentOffset, 500, 1500));
568
569 FloatingCatBond catBond(settlementDays, vars.faceAmount, sch,
570 index, ActualActual(ActualActual::ISMA),
571 notionalRisk,
572 ModifiedFollowing, fixingDays,
573 std::vector<Rate>(), std::vector<Spread>(),
574 std::vector<Rate>(), std::vector<Rate>(),
575 false,
576 100.0, Date(30,November,2004));
577
578 ext::shared_ptr<PricingEngine> catBondEngine(new MonteCarloCatBondEngine(doomCatRisk, discountCurve));
579 catBond.setPricingEngine(catBondEngine);
580 setCouponPricer(leg: catBond.cashflows(),pricer);
581
582 Real price = catBond.cleanPrice();
583 Real yield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
584 Real lossProbability = catBond.lossProbability();
585 Real exhaustionProbability = catBond.exhaustionProbability();
586 Real expectedLoss = catBond.expectedLoss();
587
588 QL_CHECK_CLOSE(Real(0.1), lossProbability, tolerance);
589 QL_CHECK_CLOSE(Real(0.0), exhaustionProbability, tolerance);
590 QL_CHECK_CLOSE(Real(0.05), expectedLoss, tolerance);
591
592 ext::shared_ptr<PricingEngine> catBondEngineRF(new MonteCarloCatBondEngine(noCatRisk, discountCurve));
593 catBond.setPricingEngine(catBondEngineRF);
594
595 Real riskFreePrice = catBond.cleanPrice();
596 Real riskFreeYield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
597 Real riskFreeLossProbability = catBond.lossProbability();
598 Real riskFreeExpectedLoss = catBond.expectedLoss();
599
600 QL_CHECK_CLOSE(Real(0.0), riskFreeLossProbability, tolerance);
601 BOOST_CHECK(std::abs(riskFreeExpectedLoss) < tolerance);
602
603 QL_CHECK_CLOSE(riskFreePrice*0.95, price, tolerance);
604 BOOST_CHECK_LT(riskFreeYield, yield);
605}
606
607
608void CatBondTest::testCatBondWithGeneratedEventsProportional() {
609 BOOST_TEST_MESSAGE("Testing floating-rate cat bond in a generated scenario with proportional notional reduction...");
610
611 using namespace catbonds_test;
612
613 CommonVars vars;
614
615 Date today(22,November,2004);
616 Settings::instance().evaluationDate() = today;
617
618 Natural settlementDays = 1;
619
620 Handle<YieldTermStructure> riskFreeRate(flatRate(today,forward: 0.025,dc: Actual360()));
621 Handle<YieldTermStructure> discountCurve(flatRate(today,forward: 0.03,dc: Actual360()));
622
623 ext::shared_ptr<IborIndex> index(new USDLibor(6*Months, riskFreeRate));
624 Natural fixingDays = 1;
625
626 Real tolerance = 1.0e-6;
627
628 ext::shared_ptr<IborCouponPricer> pricer(new
629 BlackIborCouponPricer(Handle<OptionletVolatilityStructure>()));
630
631 Schedule sch(Date(30,November,2004),
632 Date(30,November,2008),
633 Period(Semiannual),
634 UnitedStates(UnitedStates::GovernmentBond),
635 ModifiedFollowing, ModifiedFollowing,
636 DateGeneration::Backward, false);
637
638 ext::shared_ptr<CatRisk> betaCatRisk(new BetaRisk(5000, 50, 500, 500));
639
640 ext::shared_ptr<CatRisk> noCatRisk(new EventSet(
641 ext::make_shared<std::vector<std::pair<Date, Real> > >(),
642 Date(1, Jan, 2000), Date(31, Dec, 2010)));
643
644 ext::shared_ptr<EventPaymentOffset> paymentOffset(new NoOffset());
645 ext::shared_ptr<NotionalRisk> notionalRisk(new ProportionalNotionalRisk(paymentOffset, 500, 1500));
646
647 FloatingCatBond catBond(settlementDays, vars.faceAmount, sch,
648 index, ActualActual(ActualActual::ISMA),
649 notionalRisk,
650 ModifiedFollowing, fixingDays,
651 std::vector<Rate>(), std::vector<Spread>(),
652 std::vector<Rate>(), std::vector<Rate>(),
653 false,
654 100.0, Date(30,November,2004));
655
656 ext::shared_ptr<PricingEngine> catBondEngine(new MonteCarloCatBondEngine(betaCatRisk, discountCurve));
657 catBond.setPricingEngine(catBondEngine);
658 setCouponPricer(leg: catBond.cashflows(),pricer);
659
660 Real price = catBond.cleanPrice();
661 Real yield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
662 Real lossProbability = catBond.lossProbability();
663 Real exhaustionProbability = catBond.exhaustionProbability();
664 Real expectedLoss = catBond.expectedLoss();
665
666 BOOST_CHECK(lossProbability<1.0 && lossProbability>0.0);
667 BOOST_CHECK(exhaustionProbability<1.0 && exhaustionProbability>0.0);
668 BOOST_CHECK(expectedLoss>0.0);
669
670 ext::shared_ptr<PricingEngine> catBondEngineRF(new MonteCarloCatBondEngine(noCatRisk, discountCurve));
671 catBond.setPricingEngine(catBondEngineRF);
672
673 Real riskFreePrice = catBond.cleanPrice();
674 Real riskFreeYield = catBond.yield(dc: ActualActual(ActualActual::ISMA), comp: Simple, freq: Annual);
675 Real riskFreeLossProbability = catBond.lossProbability();
676 Real riskFreeExpectedLoss = catBond.expectedLoss();
677
678 QL_CHECK_CLOSE(Real(0.0), riskFreeLossProbability, tolerance);
679 BOOST_CHECK(std::abs(riskFreeExpectedLoss) < tolerance);
680
681 BOOST_CHECK_GT(riskFreePrice, price);
682 BOOST_CHECK_LT(riskFreeYield, yield);
683}
684
685test_suite* CatBondTest::suite() {
686 auto* suite = BOOST_TEST_SUITE("CatBond tests");
687
688 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testEventSetForWholeYears));
689 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testEventSetForIrregularPeriods));
690 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testEventSetForNoEvents));
691 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testBetaRisk));
692 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testRiskFreeAgainstFloatingRateBond));
693 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testCatBondInDoomScenario));
694 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testCatBondWithDoomOnceInTenYears));
695 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testCatBondWithDoomOnceInTenYearsProportional));
696 suite->add(QUANTLIB_TEST_CASE(&CatBondTest::testCatBondWithGeneratedEventsProportional));
697 return suite;
698}
699

source code of quantlib/test-suite/catbonds.cpp