Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
lgmbgsflexiswapengine.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 "toplevelfixture.hpp"
20
22#include <qle/models/lgm.hpp>
24
25#include <ql/cashflows/floatingratecoupon.hpp>
26#include <ql/currencies/europe.hpp>
27#include <ql/indexes/ibor/euribor.hpp>
28#include <ql/models/shortrate/onefactormodels/gsr.hpp>
29#include <ql/pricingengines/swap/discountingswapengine.hpp>
30#include <ql/pricingengines/swaption/gaussian1dswaptionengine.hpp>
31#include <ql/quotes/simplequote.hpp>
32#include <ql/termstructures/yield/flatforward.hpp>
33#include <ql/time/all.hpp>
34
35#include <boost/assign/std/vector.hpp>
36#include <boost/test/unit_test.hpp>
37
38#include <algorithm>
39
40using namespace boost::assign;
41using namespace QuantLib;
42using namespace QuantExt;
43using namespace boost::unit_test_framework;
44using namespace std;
45
46namespace {
47
48struct TestData : qle::test::TopLevelFixture {
49 TestData() : qle::test::TopLevelFixture() {
50 cal = TARGET();
51 evalDate = Date(5, February, 2016);
52 Settings::instance().evaluationDate() = evalDate;
53 effectiveDate = Date(cal.advance(evalDate, 2 * Days));
54 maturityDate = Date(cal.advance(effectiveDate, 10 * Years));
55 fixedSchedule = Schedule(effectiveDate, maturityDate, 1 * Years, cal, ModifiedFollowing, ModifiedFollowing,
56 DateGeneration::Forward, false);
57 floatingSchedule = Schedule(effectiveDate, maturityDate, 6 * Months, cal, ModifiedFollowing, ModifiedFollowing,
58 DateGeneration::Forward, false);
59 fixedScheduleSeasoned = Schedule(effectiveDate - 2 * Years, maturityDate - 2 * Years, 1 * Years, cal,
60 ModifiedFollowing, ModifiedFollowing, DateGeneration::Forward, false);
61 floatingScheduleSeasoned = Schedule(effectiveDate - 2 * Years, maturityDate - 2 * Years, 6 * Months, cal,
62 ModifiedFollowing, ModifiedFollowing, DateGeneration::Forward, false);
63 rateLevel = 0.02;
64 strike = 0.025;
65 nominal = 1000.0;
66 yts = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(evalDate, rateLevel, Actual365Fixed()));
67 euribor6m = QuantLib::ext::make_shared<Euribor>(6 * Months, yts);
68 vanillaSwap = QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap::Receiver, nominal, fixedSchedule, strike,
69 Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, 0.0, Actual360());
70 for (Size i = 0; i < vanillaSwap->floatingLeg().size(); ++i) {
71 auto cpn = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(vanillaSwap->floatingLeg()[i]);
72 BOOST_REQUIRE(cpn != nullptr);
73 if (cpn->fixingDate() > evalDate && i % 2 == 0)
74 exerciseDates.push_back(cpn->fixingDate());
75 }
76 stepDates = std::vector<Date>(exerciseDates.begin(), exerciseDates.end() - 1);
77 stepTimes = Array(stepDates.size());
78 for (Size i = 0; i < stepDates.size(); ++i) {
79 stepTimes[i] = yts->timeFromReference(stepDates[i]);
80 }
81 sigmas = Array(stepDates.size() + 1);
82 for (Size i = 0; i < sigmas.size(); ++i) {
83 sigmas[i] = 0.0050 + (0.0080 - 0.0050) * std::exp(-0.2 * static_cast<double>(i));
84 }
85 reversion = 0.03;
86 lgmParam = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
87 EURCurrency(), yts, stepTimes, Array(sigmas.begin(), sigmas.end()), stepTimes,
88 Array(sigmas.size(), reversion));
89 lgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgmParam);
90 dscSwapEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(yts);
91 vanillaSwap->setPricingEngine(dscSwapEngine);
92 }
93
94 Calendar cal;
95 Date evalDate, effectiveDate, maturityDate;
96 Schedule fixedSchedule, floatingSchedule;
97 Schedule fixedScheduleSeasoned, floatingScheduleSeasoned;
98 Real rateLevel, strike, nominal;
99 Handle<YieldTermStructure> yts;
100 QuantLib::ext::shared_ptr<IborIndex> euribor6m;
101 QuantLib::ext::shared_ptr<VanillaSwap> vanillaSwap;
102 std::vector<Date> exerciseDates, stepDates;
103 Array stepTimes, sigmas;
104 Real reversion;
105 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgmParam;
106 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm;
107 QuantLib::ext::shared_ptr<DiscountingSwapEngine> dscSwapEngine;
108};
109
110} // namespace
111
112BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
113
114BOOST_FIXTURE_TEST_SUITE(LgmBgsFlexiSwapEngineTest, TestData)
115
116BOOST_AUTO_TEST_CASE(testConsistencyWithFlexiSwapPricing) {
117
118 BOOST_TEST_MESSAGE("Testing LGM BGS Flexi-Swap engine against LGM Flexi-Swap engine...");
119
120 // balance guaranteed swap
121
122 Size nFixed = fixedSchedule.size() - 1, nFloat = floatingSchedule.size() - 1;
123
124 std::vector<std::vector<Real>> trancheNominals{
125 {1000.0, 900.0, 800.0, 700.0, 600.0, 500.0, 400.0, 300.0, 200.0, 100.0},
126 {300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0}};
127
128 // tranche 0
129 QuantLib::ext::shared_ptr<BalanceGuaranteedSwap> bgs0 = QuantLib::ext::make_shared<BalanceGuaranteedSwap>(
130 VanillaSwap::Payer, trancheNominals, fixedSchedule, 0, fixedSchedule, std::vector<Real>(nFixed, strike),
131 Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0),
132 std::vector<Real>(nFloat, Null<Real>()), std::vector<Real>(nFloat, Null<Real>()), Actual360());
133
134 // tranche 1
135 QuantLib::ext::shared_ptr<BalanceGuaranteedSwap> bgs1 = QuantLib::ext::make_shared<BalanceGuaranteedSwap>(
136 VanillaSwap::Payer, trancheNominals, fixedSchedule, 1, fixedSchedule, std::vector<Real>(nFixed, strike),
137 Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0),
138 std::vector<Real>(nFloat, Null<Real>()), std::vector<Real>(nFloat, Null<Real>()), Actual360());
139
140 Handle<Quote> minCpr(QuantLib::ext::make_shared<SimpleQuote>(0.05));
141 Handle<Quote> maxCpr(QuantLib::ext::make_shared<SimpleQuote>(0.25));
142
143 auto bgsEngine = QuantLib::ext::make_shared<NumericLgmBgsFlexiSwapEngine>(
144 lgm, 7.0, 16, 7.0, 32, minCpr, maxCpr, yts, QuantExt::NumericLgmBgsFlexiSwapEngine::Method::SingleSwaptions);
145
146 bgs0->setPricingEngine(bgsEngine);
147 Real bgs0Npv = bgs0->NPV();
148 BOOST_TEST_MESSAGE("BGS Npv (tranche 0) = " << bgs0Npv);
149
150 bgs0->setPricingEngine(dscSwapEngine);
151 Real bgs0DscNpv = bgs0->NPV();
152 BOOST_TEST_MESSAGE("BGS discounting engine Npv (tranche 0) = " << bgs0DscNpv);
153
154 bgs1->setPricingEngine(bgsEngine);
155 Real bgs1Npv = bgs1->NPV();
156 BOOST_TEST_MESSAGE("BGS Npv (tranche 1) = " << bgs1Npv);
157
158 bgs1->setPricingEngine(dscSwapEngine);
159 Real bgs1DscNpv = bgs1->NPV();
160 BOOST_TEST_MESSAGE("BGS discounting engine Npv (tranche 1) = " << bgs1DscNpv);
161
162 // flexi swap
163
164 // from manual calculation in Excel
165
166 // tranche 0
167 std::vector<Real> fixedNotionals0{1000.0, 835.0, 683.6666667, 545.0590909, 418.3002273,
168 302.5740795, 197.1236156, 101.2497755, 14.31232412, 0.0};
169 std::vector<Real> floatNotionals0(2 * fixedNotionals0.size(), 0.0);
170 Size i = 0; // remove in C++14 and instead write [i=0, &fixedNotionals] in the capture below
171 std::generate(floatNotionals0.begin(), floatNotionals0.end(),
172 [i, &fixedNotionals0]() mutable { return fixedNotionals0[i++ / 2]; });
173 std::vector<Real> lowerNotionals0{1000.0, 575.0, 283.3333333, 84.46969697, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
174
175 // tranche 1
176 std::vector<Real> fixedNotionals1{300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 235.7342431};
177 std::vector<Real> floatNotionals1(2 * fixedNotionals1.size(), 0.0);
178 i = 0; // see above
179 std::generate(floatNotionals1.begin(), floatNotionals1.end(),
180 [i, &fixedNotionals1]() mutable { return fixedNotionals1[i++ / 2]; });
181 std::vector<Real> lowerNotionals1{300.0, 300.0, 300.0, 300.0, 249.905303,
182 159.6617214, 99.78857586, 60.58592106, 35.34178728, 19.43798301};
183
184 // upper bound for zero CPR
185
186 // tranche 0
187 std::vector<Real> fixedNotionals0ZeroCpr{1000.0, 900.0, 800.0, 700.0, 600.0, 500.0, 400.0, 300.0, 200.0, 100.0};
188 std::vector<Real> floatNotionals0ZeroCpr(2 * fixedNotionals0ZeroCpr.size(), 0.0);
189 i = 0; // see above
190 std::generate(floatNotionals0ZeroCpr.begin(), floatNotionals0ZeroCpr.end(),
191 [i, &fixedNotionals0ZeroCpr]() mutable { return fixedNotionals0ZeroCpr[i++ / 2]; });
192 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap0 = QuantLib::ext::make_shared<FlexiSwap>(
193 VanillaSwap::Payer, fixedNotionals0, floatNotionals0, fixedSchedule, std::vector<Real>(nFixed, strike),
194 Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0),
195 std::vector<Real>(nFloat, Null<Real>()), std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals0,
196 Position::Long);
197 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap0MinCpr0 = QuantLib::ext::make_shared<FlexiSwap>(
198 VanillaSwap::Payer, fixedNotionals0ZeroCpr, floatNotionals0ZeroCpr, fixedSchedule,
199 std::vector<Real>(nFixed, strike), Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0),
200 std::vector<Real>(nFloat, 0.0), std::vector<Real>(nFloat, Null<Real>()),
201 std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals1, Position::Long);
202
203 // tranche 1
204 std::vector<Real> fixedNotionals1ZeroCpr(10, 300.0);
205 std::vector<Real> floatNotionals1ZeroCpr(20, 300.0);
206 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap1 = QuantLib::ext::make_shared<FlexiSwap>(
207 VanillaSwap::Payer, fixedNotionals1, floatNotionals1, fixedSchedule, std::vector<Real>(nFixed, strike),
208 Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0),
209 std::vector<Real>(nFloat, Null<Real>()), std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals1,
210 Position::Long);
211 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap1MinCpr0 = QuantLib::ext::make_shared<FlexiSwap>(
212 VanillaSwap::Payer, fixedNotionals1ZeroCpr, floatNotionals1ZeroCpr, fixedSchedule,
213 std::vector<Real>(nFixed, strike), Thirty360(Thirty360::BondBasis), floatingSchedule, euribor6m, std::vector<Real>(nFloat, 1.0),
214 std::vector<Real>(nFloat, 0.0), std::vector<Real>(nFloat, Null<Real>()),
215 std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals1, Position::Long);
216
217 auto flexiEngine = QuantLib::ext::make_shared<NumericLgmFlexiSwapEngine>(
219
220 flexiSwap0->setPricingEngine(flexiEngine);
221 Real flexi0Npv = flexiSwap0->NPV();
222 BOOST_TEST_MESSAGE("Flexi-Swap Npv (tranche 0) = " << flexi0Npv);
223
224 flexiSwap0MinCpr0->setPricingEngine(dscSwapEngine);
225 Real flexi0DscNpv0 = flexiSwap0MinCpr0->NPV();
226 BOOST_TEST_MESSAGE("Flexi-Swap (tranche 0, minCPR=0), discounting engine Npv = " << flexi0DscNpv0);
227
228 flexiSwap1->setPricingEngine(flexiEngine);
229 Real flexi1Npv = flexiSwap1->NPV();
230 BOOST_TEST_MESSAGE("Flexi-Swap Npv (tranche 1) = " << flexi1Npv);
231
232 flexiSwap1MinCpr0->setPricingEngine(dscSwapEngine);
233 Real flexi1DscNpv0 = flexiSwap1MinCpr0->NPV();
234 BOOST_TEST_MESSAGE("Flexi-Swap (tranche 1, minCPR=0), discounting engine Npv = " << flexi1DscNpv0);
235
236 BOOST_CHECK_CLOSE(bgs0Npv, flexi0Npv, 1E-8);
237 BOOST_CHECK_CLOSE(bgs0DscNpv, flexi0DscNpv0, 1E-8);
238 BOOST_CHECK_CLOSE(bgs1Npv, flexi1Npv, 1E-8);
239 BOOST_CHECK_CLOSE(bgs1DscNpv, flexi1DscNpv0, 1E-8);
240
241} // testConsistencyWithFlexiSwap
242
243BOOST_AUTO_TEST_CASE(testConsistencyWithFlexiSwapPricingSeasonedDeal) {
244
245 BOOST_TEST_MESSAGE("Testing LGM BGS Flexi-Swap engine against LGM Flexi-Swap engine (seasoned deal)...");
246
247 euribor6m->addFixing(Date(6, August, 2015), 0.01); // need for pricing
248
249 // balance guaranteed swap
250
251 Size nFixed = fixedScheduleSeasoned.size() - 1, nFloat = floatingScheduleSeasoned.size() - 1;
252
253 std::vector<std::vector<Real>> trancheNominals{
254 {1000.0, 900.0, 800.0, 700.0, 600.0, 500.0, 400.0, 300.0, 200.0, 100.0},
255 {300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0, 300.0}};
256
257 // tranche 0
258 QuantLib::ext::shared_ptr<BalanceGuaranteedSwap> bgs0 = QuantLib::ext::make_shared<BalanceGuaranteedSwap>(
259 VanillaSwap::Payer, trancheNominals, fixedScheduleSeasoned, 0, fixedScheduleSeasoned,
260 std::vector<Real>(nFixed, strike), Thirty360(Thirty360::BondBasis), floatingScheduleSeasoned, euribor6m,
261 std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0), std::vector<Real>(nFloat, Null<Real>()),
262 std::vector<Real>(nFloat, Null<Real>()), Actual360());
263
264 Handle<Quote> minCpr(QuantLib::ext::make_shared<SimpleQuote>(0.05));
265 Handle<Quote> maxCpr(QuantLib::ext::make_shared<SimpleQuote>(0.25));
266
267 auto bgsEngine = QuantLib::ext::make_shared<NumericLgmBgsFlexiSwapEngine>(
268 lgm, 7.0, 16, 7.0, 32, minCpr, maxCpr, yts, QuantExt::NumericLgmBgsFlexiSwapEngine::Method::SingleSwaptions);
269
270 bgs0->setPricingEngine(bgsEngine);
271 Real bgs0Npv = bgs0->NPV();
272 BOOST_TEST_MESSAGE("BGS Npv (tranche 0) = " << bgs0Npv);
273
274 bgs0->setPricingEngine(dscSwapEngine);
275 Real bgs0DscNpv = bgs0->NPV();
276 BOOST_TEST_MESSAGE("BGS discounting engine Npv (tranche 0) = " << bgs0DscNpv);
277
278 // flexi swap
279
280 // from manual calculation in Excel;
281 // the prepayments start in the 3rd fixed period (this is the first period with a future start date)
282
283 // tranche 0
284 std::vector<Real> fixedNotionals0{1000.0, 900, 740.0, 593.4545455, 459.4363636,
285 337.0827273, 225.59325, 124.2288375, 32.31258938, 0.0};
286 std::vector<Real> floatNotionals0(2 * fixedNotionals0.size(), 0.0);
287 Size i = 0; // remove in C++14 and instead write [i=0, &fixedNotionals] in the capture below
288 std::generate(floatNotionals0.begin(), floatNotionals0.end(),
289 [i, &fixedNotionals0]() mutable { return fixedNotionals0[i++ / 2]; });
290 std::vector<Real> lowerNotionals0{1000.0, 900.0, 500.0, 227.2727273, 42.72727273, 0.0, 0.0, 0.0, 0.0, 0.0};
291
292 // upper bound for zero CPR
293
294 // tranche 0
295 std::vector<Real> fixedNotionals0ZeroCpr{1000.0, 900.0, 800.0, 700.0, 600.0, 500.0, 400.0, 300.0, 200.0, 100.0};
296 std::vector<Real> floatNotionals0ZeroCpr(2 * fixedNotionals0ZeroCpr.size(), 0.0);
297 i = 0; // see above
298 std::generate(floatNotionals0ZeroCpr.begin(), floatNotionals0ZeroCpr.end(),
299 [i, &fixedNotionals0ZeroCpr]() mutable { return fixedNotionals0ZeroCpr[i++ / 2]; });
300 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap0 = QuantLib::ext::make_shared<FlexiSwap>(
301 VanillaSwap::Payer, fixedNotionals0, floatNotionals0, fixedScheduleSeasoned, std::vector<Real>(nFixed, strike),
302 Thirty360(Thirty360::BondBasis), floatingScheduleSeasoned, euribor6m, std::vector<Real>(nFloat, 1.0),
303 std::vector<Real>(nFloat, 0.0), std::vector<Real>(nFloat, Null<Real>()),
304 std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals0, Position::Long);
305 QuantLib::ext::shared_ptr<FlexiSwap> flexiSwap0MinCpr0 = QuantLib::ext::make_shared<FlexiSwap>(
306 VanillaSwap::Payer, fixedNotionals0ZeroCpr, floatNotionals0ZeroCpr, fixedScheduleSeasoned,
307 std::vector<Real>(nFixed, strike), Thirty360(Thirty360::BondBasis), floatingScheduleSeasoned, euribor6m,
308 std::vector<Real>(nFloat, 1.0), std::vector<Real>(nFloat, 0.0), std::vector<Real>(nFloat, Null<Real>()),
309 std::vector<Real>(nFloat, Null<Real>()), Actual360(), lowerNotionals0, Position::Long);
310
311 auto flexiEngine = QuantLib::ext::make_shared<NumericLgmFlexiSwapEngine>(
313
314 flexiSwap0->setPricingEngine(flexiEngine);
315 Real flexi0Npv = flexiSwap0->NPV();
316 BOOST_TEST_MESSAGE("Flexi-Swap Npv (tranche 0) = " << flexi0Npv);
317
318 flexiSwap0MinCpr0->setPricingEngine(dscSwapEngine);
319 Real flexi0DscNpv0 = flexiSwap0MinCpr0->NPV();
320 BOOST_TEST_MESSAGE("Flexi-Swap (tranche 0, minCPR=0), discounting engine Npv = " << flexi0DscNpv0);
321
322 BOOST_CHECK_CLOSE(bgs0Npv, flexi0Npv, 1E-8);
323 BOOST_CHECK_CLOSE(bgs0DscNpv, flexi0DscNpv0, 1E-8);
324} // testConsistencyWithFlexiSwap
325
326BOOST_AUTO_TEST_SUITE_END()
327
328BOOST_AUTO_TEST_SUITE_END()
adaptor to emulate piecewise constant Hull White parameters
lgm model class
BOOST_AUTO_TEST_CASE(testConsistencyWithFlexiSwapPricing)
numeric engine for balance guaranteed swaps using a flexi swap proxy in the LGM model
Fixture that can be used at top level.