Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
riskparticipationagreement.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <boost/test/unit_test.hpp>
20
21#include <oret/toplevelfixture.hpp>
22
25
28#include <qle/models/lgm.hpp>
31
32#include <ql/cashflows/couponpricer.hpp>
33#include <ql/cashflows/iborcoupon.hpp>
34#include <ql/currencies/europe.hpp>
35#include <ql/exercise.hpp>
36#include <ql/experimental/coupons/strippedcapflooredcoupon.hpp>
37#include <ql/indexes/ibor/euribor.hpp>
38#include <ql/indexes/iborindex.hpp>
39#include <ql/indexes/swap/euriborswap.hpp>
40#include <ql/instruments/makevanillaswap.hpp>
41#include <ql/instruments/nonstandardswap.hpp>
42#include <ql/math/optimization/levenbergmarquardt.hpp>
43#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
44#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
45#include <ql/termstructures/credit/flathazardrate.hpp>
46#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
47#include <ql/termstructures/yield/flatforward.hpp>
48#include <ql/time/calendars/target.hpp>
49#include <ql/time/daycounters/actualactual.hpp>
50#include <ql/timegrid.hpp>
51
52#include <boost/accumulators/accumulators.hpp>
53#include <boost/accumulators/statistics/mean.hpp>
54#include <boost/accumulators/statistics/stats.hpp>
55
56#include <boost/timer/timer.hpp>
57
58using namespace QuantLib;
59using namespace QuantExt;
60using namespace ore::data;
61using namespace boost::accumulators;
62
63using namespace boost::unit_test_framework;
64using std::vector;
65
66BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
67
68BOOST_AUTO_TEST_SUITE(RiskParticipationAgreementTests)
69
70namespace {
71void runTest(const std::vector<Real>& nominals, const bool isPayer, const Real errorTol,
72 const std::vector<Real> cachedSimResults = {}) {
73
74 Date today(6, Jun, 2019);
75 Settings::instance().evaluationDate() = today;
76
77 auto dsc = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(today, 0.01, ActualActual(ActualActual::ISDA)));
78 auto fwd = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(today, 0.02, ActualActual(ActualActual::ISDA)));
79 auto def =
80 Handle<DefaultProbabilityTermStructure>(QuantLib::ext::make_shared<FlatHazardRate>(today, 0.0050, ActualActual(ActualActual::ISDA)));
81 auto blackVol = Handle<SwaptionVolatilityStructure>(
82 QuantLib::ext::make_shared<ConstantSwaptionVolatility>(today, TARGET(), Following, 0.0050, ActualActual(ActualActual::ISDA), Normal));
83
84 auto swapIndexBase = QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(10 * Years, fwd, dsc);
85
86 // create swap
87
88 std::vector<Real> fixedNominals(nominals);
89 fixedNominals.resize(20, nominals.back());
90 std::vector<Real> fixedRates(20, 0.03);
91 std::vector<Real> floatNominals;
92 for (auto const& n : fixedNominals) {
93 floatNominals.push_back(n);
94 floatNominals.push_back(n);
95 }
96
97 QuantLib::ext::shared_ptr<VanillaSwap> tmp = MakeVanillaSwap(20 * Years, swapIndexBase->iborIndex(), 0.03);
98 QuantLib::ext::shared_ptr<Swap> underlying = QuantLib::ext::make_shared<NonstandardSwap>(
99 isPayer ? VanillaSwap::Payer : VanillaSwap::Receiver, fixedNominals, floatNominals, tmp->fixedSchedule(),
100 fixedRates, tmp->fixedDayCount(), tmp->floatingSchedule(), tmp->iborIndex(), std::vector<Real>(40, 1.0),
101 std::vector<Real>(40, 0.0), tmp->floatingDayCount());
102
103 underlying->setPricingEngine(QuantLib::ext::make_shared<DiscountingSwapEngine>(dsc));
104 BOOST_TEST_MESSAGE("Underlying NPV = " << underlying->NPV());
105
106 // create RPA contract
107
108 Real participationRate = 0.8;
109 Real recoveryRate = 0.2;
110 Date feePayDate = today + 20;
111 Leg fee;
112 fee.push_back(
113 QuantLib::ext::make_shared<FixedRateCoupon>(feePayDate, nominals.front(), 0.02, ActualActual(ActualActual::ISDA), today, feePayDate));
114
115 QuantLib::ext::shared_ptr<RiskParticipationAgreement> rpa = QuantLib::ext::make_shared<RiskParticipationAgreement>(
116 std::vector<Leg>{underlying->leg(0), underlying->leg(1)}, std::vector<bool>{isPayer, !isPayer},
117 std::vector<std::string>{"EUR", "EUR"}, std::vector<Leg>{fee}, true, std::vector<std::string>{"EUR"},
118 participationRate, today, underlying->maturityDate(), true, recoveryRate);
119
120 // create LGM model and RPA LGM pricing engine
121
122 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper>> basket;
123 std::vector<Date> expiryDates;
124 for (Size i = 1; i < 20; ++i) {
125 QuantLib::ext::shared_ptr<BlackCalibrationHelper> helper = QuantLib::ext::make_shared<SwaptionHelper>(
126 i * Years, (20 - i) * Years, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0050)),
127 swapIndexBase->iborIndex(), 1 * Years, swapIndexBase->dayCounter(),
128 swapIndexBase->iborIndex()->dayCounter(), dsc, BlackCalibrationHelper::RelativePriceError,
129 fixedRates.front(), 1.0, Normal);
130 basket.push_back(helper);
131 expiryDates.push_back(
132 QuantLib::ext::static_pointer_cast<SwaptionHelper>(helper)->swaption()->exercise()->dates().back());
133 }
134 std::vector<Date> stepDates(expiryDates.begin(), expiryDates.end() - 1);
135 Array stepTimes(stepDates.size());
136 for (Size i = 0; i < stepDates.size(); ++i) {
137 stepTimes[i] = dsc->timeFromReference(stepDates[i]);
138 }
139 auto lgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
140 EURCurrency(), dsc, stepTimes, Array(stepTimes.size() + 1, 0.0050), stepTimes,
141 Array(stepTimes.size() + 1, 0.0));
142 lgm_p->shift() = -lgm_p->H(20.0);
143 auto lgm = QuantLib::ext::make_shared<LGM>(lgm_p);
144
145 auto rpaEngine = QuantLib::ext::make_shared<NumericLgmRiskParticipationAgreementEngine>(
146 "EUR", std::map<std::string, Handle<YieldTermStructure>>{{"EUR", dsc}}, std::map<std::string, Handle<Quote>>(),
147 lgm, 3.0, 10, 3.0, 10, def, Handle<Quote>());
148
149 // set engine
150
151 rpa->setPricingEngine(rpaEngine);
152
153 // extract results from RPA pricing engine
154
155 boost::timer::cpu_timer timer;
156 std::vector<Date> gridDates = rpa->result<std::vector<Date>>("GridDates");
157 std::vector<Real> epe_engine = rpa->result<std::vector<Real>>("OptionNpvs");
158 timer.stop();
159 BOOST_TEST_MESSAGE("EPE calculation in numeric lgm engine took " << timer.elapsed().wall * 1e-6 << "ms");
160
161 // generate eval dates from grid dates (midpoint rule) and check them vs. cached results
162
163 std::vector<Date> evalDates;
164 for (Size i = 0; i < gridDates.size() - 1; ++i) {
165 evalDates.push_back(gridDates[i] + (gridDates[i + 1] - gridDates[i]) / 2);
166 }
167
168 std::vector<Date> expectedEvalDates{
169 Date(7, September, 2019), Date(10, March, 2020), Date(9, September, 2020), Date(11, March, 2021),
170 Date(9, September, 2021), Date(11, March, 2022), Date(10, September, 2022), Date(13, March, 2023),
171 Date(11, September, 2023), Date(11, March, 2024), Date(9, September, 2024), Date(11, March, 2025),
172 Date(9, September, 2025), Date(11, March, 2026), Date(9, September, 2026), Date(11, March, 2027),
173 Date(9, September, 2027), Date(11, March, 2028), Date(11, September, 2028), Date(12, March, 2029),
174 Date(10, September, 2029), Date(11, March, 2030), Date(9, September, 2030), Date(11, March, 2031),
175 Date(9, September, 2031), Date(10, March, 2032), Date(9, September, 2032), Date(11, March, 2033),
176 Date(10, September, 2033), Date(13, March, 2034), Date(11, September, 2034), Date(12, March, 2035),
177 Date(10, September, 2035), Date(10, March, 2036), Date(9, September, 2036), Date(11, March, 2037),
178 Date(9, September, 2037), Date(11, March, 2038), Date(9, September, 2038), Date(11, March, 2039)};
179
180 BOOST_REQUIRE(evalDates.size() == expectedEvalDates.size());
181 for (Size i = 0; i < evalDates.size(); ++i)
182 BOOST_REQUIRE_EQUAL(evalDates[i], expectedEvalDates[i]);
183
184 // generate EPE with full simulation
185
186 std::vector<Real> evalTimes;
187 for (Size i = 0; i < evalDates.size(); ++i) {
188 evalTimes.push_back(dsc->timeFromReference(evalDates[i]));
189 }
190 Size nTimes = evalDates.size();
191 std::vector<Real> epe_sim(nTimes);
192
193 if (cachedSimResults.empty()) {
194 Size nPaths = 10000;
195 TimeGrid grid(evalTimes.begin(), evalTimes.end());
196
197 auto swaptionEngineLgm = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(lgm);
198 for (Size i = 0; i < basket.size(); ++i) {
199 basket[i]->setPricingEngine(swaptionEngineLgm);
200 }
201 LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
202 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
203 lgm->calibrateVolatilitiesIterative(basket, lm, ec);
204
205 MultiPathGeneratorSobolBrownianBridge pgen(lgm->stateProcess(), grid);
206
207 auto lgm_dsc = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(lgm, dsc);
208 auto lgm_fwd = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(lgm, fwd);
209
210 auto lgmEuribor = swapIndexBase->iborIndex()->clone(Handle<YieldTermStructure>(lgm_fwd));
211
212 std::vector<Leg> lgmLinkedUnderlying(2);
213 std::set<Date> requiredFixings;
214 for (Size i = 0; i < 2; ++i) {
215 for (auto const& c : underlying->leg(i)) {
216 auto f = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(c);
217 if (f) {
218 requiredFixings.insert(f->fixingDate());
219 auto ic = QuantLib::ext::make_shared<IborCoupon>(
220 f->date(), f->nominal(), f->accrualStartDate(), f->accrualEndDate(), f->fixingDays(),
221 lgmEuribor, f->gearing(), f->spread(), f->referencePeriodStart(), f->referencePeriodEnd(),
222 f->dayCounter(), f->isInArrears());
223 ic->setPricer(QuantLib::ext::make_shared<BlackIborCouponPricer>());
224 lgmLinkedUnderlying[i].push_back(ic);
225 } else
226 lgmLinkedUnderlying[i].push_back(c);
227 }
228 }
229
230 Swap lgmUnderlying(lgmLinkedUnderlying, {isPayer, !isPayer});
231
232 timer.start();
233 auto lgmSwapEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(Handle<YieldTermStructure>(lgm_dsc));
234 lgmUnderlying.setPricingEngine(lgmSwapEngine);
235 std::vector<accumulator_set<double, stats<tag::mean>>> acc(nTimes);
236 for (Size p = 0; p < nPaths; ++p) {
237 auto currentFixing = requiredFixings.begin();
238 MultiPath path = pgen.next().value;
239 for (Size i = 0; i < nTimes; ++i) {
240 lgm_dsc->move(evalDates[i], path[0][i + 1]);
241 lgm_fwd->move(evalDates[i], path[0][i + 1]);
242 Settings::instance().evaluationDate() = evalDates[i];
243 while (currentFixing != requiredFixings.end() && evalDates[i] >= *currentFixing) {
244 Date evalDateAdj =
245 lgmEuribor->fixingCalendar().adjust(evalDates[i]); // for today's fixing generation
246 lgmEuribor->addFixing(*currentFixing, lgmEuribor->fixing(evalDateAdj), false);
247 ++currentFixing;
248 }
249 acc[i](std::max(lgmUnderlying.NPV(), 0.0) / lgm->numeraire(evalTimes[i], path[0][i + 1]));
250 }
251 Settings::instance().evaluationDate() = today;
252 IndexManager::instance().clearHistory(lgmEuribor->name());
253 }
254 for (Size i = 0; i < nTimes; ++i) {
255 epe_sim[i] = mean(acc[i]);
256 BOOST_TEST_MESSAGE(epe_sim[i] << (i < nTimes - 1 ? "," : ""));
257 }
258 timer.stop();
259 BOOST_TEST_MESSAGE("EPE calculation via full simulation took " << timer.elapsed().wall * 1e-6 << "ms");
260 } else {
261 BOOST_REQUIRE(cachedSimResults.size() == nTimes);
262 for (Size i = 0; i < nTimes; ++i)
263 epe_sim[i] = cachedSimResults[i];
264 }
265
266 // compare results
267
268 Real maxError = 0.0;
269 BOOST_TEST_MESSAGE("date t EPE_engine EPE_sim");
270 for (Size i = 0; i < nTimes; ++i) {
271 BOOST_TEST_MESSAGE(QuantLib::io::iso_date(evalDates[i])
272 << " " << evalTimes[i] << " " << epe_engine[i] << " " << epe_sim[i]);
273 BOOST_CHECK_SMALL(epe_engine[i] - epe_sim[i], errorTol);
274 maxError = std::max(maxError, std::abs(epe_sim[i] - epe_engine[i]));
275 }
276 BOOST_TEST_MESSAGE("max error = " << maxError);
277
278 // check total npv of rpa
279
280 Real npv = rpa->NPV();
281 BOOST_TEST_MESSAGE("RPA total NPV (LGM engine) = " << npv);
282
283 Real protNpv = 0.0;
284 for (Size i = 0; i < gridDates.size() - 1; ++i) {
285 protNpv += epe_engine[i] * def->defaultProbability(gridDates[i], gridDates[i + 1]);
286 }
287 protNpv *= participationRate * (1.0 - recoveryRate);
288
289 Real feeNpv = 0.0;
290 for (auto const& c : fee) {
291 // fee contribution
292 feeNpv += c->amount() * dsc->discount(c->date()) * def->survivalProbability(c->date());
293 // fee accruals
294 auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c);
295 if (cpn) {
296 Date start = std::max(cpn->accrualStartDate(), today);
297 Date end = cpn->accrualEndDate();
298 if (start < end) {
299 Date mid = start + (end - start) / 2;
300 feeNpv += cpn->accruedAmount(mid) * dsc->discount(mid) * def->defaultProbability(start, end);
301 }
302 }
303 }
304
305 BOOST_TEST_MESSAGE("Expected NPV = " << protNpv - feeNpv);
306 BOOST_CHECK_CLOSE(npv, protNpv - feeNpv, 1E-8);
307
308 // check npv in black engine
309
310 auto blackEngine = QuantLib::ext::make_shared<AnalyticBlackRiskParticipationAgreementEngine>(
311 "EUR", std::map<std::string, Handle<YieldTermStructure>>{{"EUR", dsc}}, std::map<std::string, Handle<Quote>>(),
312 def, Handle<Quote>(), blackVol, swapIndexBase, false, 0.0, false);
313 rpa->setPricingEngine(blackEngine);
314 Real blackNpv = rpa->NPV();
315
316 BOOST_TEST_MESSAGE("Black NPV = " << blackNpv);
317 BOOST_CHECK_CLOSE(npv, blackNpv, 5.0);
318}
319} // namespace
320
321BOOST_AUTO_TEST_CASE(testStandardPayerSwap) {
322 runTest({10000.0}, true, 10.0,
323 {0, 1.94889, 15.7715, 24.0619, 54.8255, 55.7279, 95.6096, 91.7498, 133.741, 123.373,
324 164.17, 147.664, 189.878, 166.109, 207.458, 179.875, 220.102, 187.612, 226.417, 190.731,
325 227.738, 188.212, 222.535, 180.287, 214.038, 169.203, 200.895, 154.238, 183.085, 133.985,
326 161.588, 111.699, 135.997, 85.3633, 107.052, 56.2545, 74.7086, 25.7101, 38.9068, 1.31757});
327}
328
329BOOST_AUTO_TEST_CASE(testStandardReceiverSwap) {
330 runTest({10000.0}, false, 10.0,
331 {1776.41, 1878.45, 1694.92, 1802.31, 1637.38, 1736.49, 1582.74, 1677.57, 1526.22, 1611.41,
332 1463.59, 1542.34, 1396.47, 1467.08, 1322.19, 1388.18, 1244.17, 1303.92, 1159.84, 1214.88,
333 1072.06, 1122.67, 978.803, 1026.29, 882.843, 926.86, 783.296, 824.455, 679.52, 718.399,
334 572.469, 608.334, 463.113, 497.353, 351.281, 384.799, 236.4, 271.088, 119.076, 164.356});
335}
336
337BOOST_AUTO_TEST_CASE(testAmortisingPayerSwap) {
338 runTest({10000.0, 9500.0, 9000.0, 8500.0, 8000.0, 7500.0, 7000.0, 6500.0, 6000.0, 5500.0, 5000.0,
339 4500.0, 4000.0, 3500.0, 3000.0, 2500.0, 2000.0, 1500.0, 1000.0, 500.0, 0.0},
340 true, 5.0, {0, 0.58703, 8.11856, 8.93971, 26.7995, 20.7611, 44.169, 33.6139, 58.2056, 43.9656,
341 67.223, 49.9792, 72.8682, 52.7195, 74.1339, 53.0533, 72.9152, 51.5704, 69.0406, 47.9078,
342 63.6394, 42.9462, 56.4375, 36.8842, 48.762, 30.607, 40.6079, 24.2613, 32.3475, 17.8779,
343 24.4077, 12.3198, 17.092, 7.4161, 10.7417, 3.56567, 5.61611, 1.08903, 1.94534, 0.0658784});
344}
345
346BOOST_AUTO_TEST_CASE(testAmortisingReceiverSwap) {
347 runTest({10000.0, 9500.0, 9000.0, 8500.0, 8000.0, 7500.0, 7000.0, 6500.0, 6000.0, 5500.0, 5000.0,
348 4500.0, 4000.0, 3500.0, 3000.0, 2500.0, 2000.0, 1500.0, 1000.0, 500.0, 0.0},
349 false, 50.0, {962.21, 1062.92, 873.019, 967.955, 799.857, 882.248, 731.282, 804.481, 664.708, 726.901,
350 599.229, 653.365, 535.172, 581.061, 472.101, 511.799, 411.877, 445.908, 353.651, 382.434,
351 299.225, 323.595, 247.998, 268.821, 200.983, 218.353, 158.261, 172.636, 119.932, 131.857,
352 86.3659, 95.7154, 58.1326, 65.4286, 35.2337, 40.7063, 17.7519, 21.5932, 5.95378, 8.21779});
353}
354
355BOOST_AUTO_TEST_CASE(testAccretingPayerSwap) {
356 runTest({10000.0, 11000.0, 12000.0, 13000.0, 14000.0, 15000.0, 16000.0, 17000.0, 18000.0, 19000.0,
357 20000.0, 21000.0, 22000.0, 23000.0, 24000.0, 25000.0, 26000.0, 27000.0, 28000.0, 29000.0},
358 true, 20.0, {0, 5.03316, 31.0906, 56.0286, 110.937, 128.441, 198.589, 210.596, 284.946, 284.878,
359 358.21, 345.452, 424.04, 395.573, 474.253, 435.555, 514.612, 461.857, 541.256, 478.229,
360 556.038, 480.233, 554.782, 468.493, 544.637, 447.635, 521.492, 415.265, 484.579, 367.228,
361 435.953, 311.242, 373.81, 241.924, 299.673, 162.093, 212.894, 75.2118, 112.83, 3.82094});
362}
363
364BOOST_AUTO_TEST_CASE(testAccretingReceiverSwap) {
365 runTest({10000.0, 11000.0, 12000.0, 13000.0, 14000.0, 15000.0, 16000.0, 17000.0, 18000.0, 19000.0,
366 20000.0, 21000.0, 22000.0, 23000.0, 24000.0, 25000.0, 26000.0, 27000.0, 28000.0, 29000.0},
367 false, 20.0, {3404.82, 3509.87, 3338.75, 3472.73, 3312.48, 3447.74, 3285.74, 3426.31, 3249.38, 3383.13,
368 3192.44, 3322.71, 3119.21, 3241.8, 3022.51, 3142.98, 2908.89, 3022.1, 2772.3, 2881.64,
369 2617.82, 2722.29, 2440.46, 2542.63, 2246.61, 2345.11, 2033.39, 2129.17, 1798.72, 1892.51,
370 1544.68, 1634.35, 1273.08, 1361.87, 983.376, 1073.45, 673.695, 770.339, 345.319, 476.632});
371}
372
373namespace {
374Real computeUnderlyingNpv(const bool underlyingIsPayer, const Real cap, const Real floor, const bool nakedOption) {
375 Date today(6, Jun, 2019);
376 Settings::instance().evaluationDate() = today;
377
378 auto dsc = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(today, 0.01, ActualActual(ActualActual::ISDA)));
379 auto fwd = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(today, 0.02, ActualActual(ActualActual::ISDA)));
380 auto def =
381 Handle<DefaultProbabilityTermStructure>(QuantLib::ext::make_shared<FlatHazardRate>(today, 0.0050, ActualActual(ActualActual::ISDA)));
382
383 auto iborIndex = QuantLib::ext::make_shared<Euribor>(6 * Months, fwd);
384
385 // create rpas
386
387 Schedule schedule(today + 2 * Days, (today + 2 * Days) + 10 * Years, 6 * Months, NullCalendar(), Unadjusted,
388 Unadjusted, DateGeneration::Forward, false);
389 IborLeg l(schedule, iborIndex);
390 l.withNotionals(10000.0);
391
392 if (cap != Null<Real>())
393 l.withCaps(cap);
394 if (floor != Null<Real>())
395 l.withFloors(floor);
396
397 Leg leg;
398 if (nakedOption)
399 leg = StrippedCappedFlooredCouponLeg(l);
400 else
401 leg = l;
402
403 auto rpa = QuantLib::ext::make_shared<RiskParticipationAgreement>(
404 std::vector<Leg>{leg}, std::vector<bool>{underlyingIsPayer}, std::vector<std::string>{"EUR"},
405 std::vector<Leg>{}, false, std::vector<std::string>{}, 0.0, schedule.dates().front(), schedule.dates().back(),
406 true, 0.0);
407
408 // create lgm engine
409
410 auto lgm =
411 QuantLib::ext::make_shared<LGM>(QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), dsc, 0.0040, 0.01));
412 auto engine = QuantLib::ext::make_shared<NumericLgmRiskParticipationAgreementEngine>(
413 "EUR", std::map<std::string, Handle<YieldTermStructure>>{{"EUR", dsc}}, std::map<std::string, Handle<Quote>>(),
414 lgm, 3.0, 10, 3.0, 10, def, Handle<Quote>());
415
416 // compute underlying npv and return it
417
418 rpa->setPricingEngine(engine);
419 return rpa->result<Real>("UnderlyingNpv");
420}
421} // namespace
422
423BOOST_AUTO_TEST_CASE(testCapFloors) {
424
425 constexpr Real tol = 1E-10;
426
427 // underlying is receiver
428
429 // no cap/floor
430 Real plain = computeUnderlyingNpv(false, Null<Real>(), Null<Real>(), false);
431
432 // capped/floored coupon
433 Real capped = computeUnderlyingNpv(false, 0.03, Null<Real>(), false);
434 Real floored = computeUnderlyingNpv(false, Null<Real>(), 0.01, false);
435 Real collared = computeUnderlyingNpv(false, 0.03, 0.01, false);
436
437 // the embedded option
438 Real cap = computeUnderlyingNpv(false, 0.03, Null<Real>(), true);
439 Real floor = computeUnderlyingNpv(false, Null<Real>(), 0.01, true);
440 Real collar = computeUnderlyingNpv(false, 0.03, 0.01, true);
441
442 BOOST_CHECK_CLOSE(capped + cap, plain, tol);
443 BOOST_CHECK_CLOSE(floored - floor, plain, tol);
444 BOOST_CHECK_CLOSE(collared - collar, plain, tol);
445
446 // underlying is payer
447
448 // no cap/floor
449 Real plain2 = computeUnderlyingNpv(true, Null<Real>(), Null<Real>(), false);
450
451 // capped/floored coupon
452 Real capped2 = computeUnderlyingNpv(true, 0.03, Null<Real>(), false);
453 Real floored2 = computeUnderlyingNpv(true, Null<Real>(), 0.01, false);
454 Real collared2 = computeUnderlyingNpv(true, 0.03, 0.01, false);
455
456 // the embedded option
457 Real cap2 = computeUnderlyingNpv(true, 0.03, Null<Real>(), true);
458 Real floor2 = computeUnderlyingNpv(true, Null<Real>(), 0.01, true);
459 Real collar2 = computeUnderlyingNpv(true, 0.03, 0.01, true);
460
461 BOOST_CHECK_CLOSE(capped2 + cap2, plain2, tol);
462 BOOST_CHECK_CLOSE(floored2 - floor2, plain2, tol);
463 BOOST_CHECK_CLOSE(collared2 - collar2, plain2, tol);
464
465 // check sign changes between underlying receiver and payer
466 BOOST_CHECK_CLOSE(plain, -plain2, tol);
467 BOOST_CHECK_CLOSE(capped, -capped2, tol);
468 BOOST_CHECK_CLOSE(floored, -floored2, tol);
469 BOOST_CHECK_CLOSE(collared, -collared2, tol);
470 BOOST_CHECK_CLOSE(cap, -cap2, tol);
471 BOOST_CHECK_CLOSE(floor, -floor2, tol);
472 BOOST_CHECK_CLOSE(collar, -collar2, tol);
473}
474
475BOOST_AUTO_TEST_SUITE_END()
476
477BOOST_AUTO_TEST_SUITE_END()
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
BOOST_AUTO_TEST_CASE(testStandardPayerSwap)