Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
amcbermudanswaption.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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// clang-format off
20#include <boost/test/unit_test.hpp>
21#include <boost/test/data/test_case.hpp>
22// clang-format on
23
25
34
42
44
51#include <qle/models/lgm.hpp>
57
59
60#include <ql/currencies/america.hpp>
61#include <ql/currencies/europe.hpp>
62#include <ql/indexes/ibor/all.hpp>
63#include <ql/indexes/swap/euriborswap.hpp>
64#include <ql/indexes/swap/usdliborswap.hpp>
65#include <ql/instruments/makeswaption.hpp>
66#include <ql/instruments/makevanillaswap.hpp>
67#include <ql/math/statistics/incrementalstatistics.hpp>
68#include <ql/pricingengines/swap/discountingswapengine.hpp>
69#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
70#include <ql/quotes/simplequote.hpp>
71#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
72#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
73#include <ql/termstructures/yield/flatforward.hpp>
74#include <ql/time/calendars/jointcalendar.hpp>
75#include <ql/time/calendars/target.hpp>
76#include <ql/time/daycounters/actual360.hpp>
77#include <ql/time/daycounters/thirty360.hpp>
78
79#include <boost/timer/timer.hpp>
80
81#include "testmarket.hpp"
82
83using namespace QuantLib;
84using namespace QuantExt;
85using namespace boost::unit_test_framework;
86using namespace ore::analytics;
87using namespace ore::data;
88
89struct TestData : ore::test::OreaTopLevelFixture {
90 TestData() : referenceDate(30, July, 2015) {
91 ObservationMode::instance().setMode(ObservationMode::Mode::None);
92 Settings::instance().evaluationDate() = referenceDate;
93
94 // Build test market
95 market = QuantLib::ext::make_shared<testsuite::TestMarket>(referenceDate);
96
97 // Build IR configurations
98 CalibrationType calibrationType = CalibrationType::None;
99 LgmData::ReversionType revType = LgmData::ReversionType::HullWhite;
100 LgmData::VolatilityType volType = LgmData::VolatilityType::HullWhite;
101 vector<string> swaptionExpiries = {"1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "15Y", "20Y", "30Y"};
102 vector<string> swaptionTerms = {"5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y"};
103 vector<string> swaptionStrikes(swaptionExpiries.size(), "ATM");
104 vector<Time> hTimes = {};
105 vector<Time> aTimes = {};
106
107 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
108
109 vector<Real> hValues = {0.02}; // reversion
110 vector<Real> aValues = {0.01}; // HW vol
111 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
112 "EUR", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
113 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
114
115 hValues = {0.012}; // reversion
116 aValues = {0.0075}; // HW vol
117 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
118 "USD", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
119 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
120
121 // Compile FX configurations
122 vector<string> optionExpiries = {};
123 vector<string> optionStrikes = {};
124 vector<Time> sigmaTimes = {};
125
126 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
127 vector<Real> sigmaValues = {0.15};
128 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>("USD", "EUR", calibrationType, false, ParamType::Constant,
129 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
130
132 cmb.addCorrelation("IR:EUR", "IR:USD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.5)));
133 cmb.addCorrelation("IR:EUR", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.6)));
134 cmb.addCorrelation("IR:USD", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.7)));
135
136 // CAM configuration
137 QuantLib::ext::shared_ptr<CrossAssetModelData> config(
138 QuantLib::ext::make_shared<CrossAssetModelData>(irConfigs, fxConfigs, cmb.correlations()));
139
140 // build CAM and marginal LGM models
141 CrossAssetModelBuilder modelBuilder(market, config);
142 ccLgm = *modelBuilder.model();
143 lgm_eur = QuantLib::ext::make_shared<QuantExt::LGM>(ccLgm->irlgm1f(0));
144 lgm_usd = QuantLib::ext::make_shared<QuantExt::LGM>(ccLgm->irlgm1f(1));
145 }
146
147 Date referenceDate;
148 QuantLib::ext::shared_ptr<ore::data::Conventions> conventions_;
149 QuantLib::ext::shared_ptr<CrossAssetModelData> config;
150 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> ccLgm;
151 QuantLib::ext::shared_ptr<QuantExt::LGM> lgm_eur, lgm_usd;
152 QuantLib::ext::shared_ptr<ore::data::Market> market;
153};
154
155struct TestCase {
156 const char* label; // label of test case
157 Real tolerance; // tolerance requirement
158 bool isPhysical; // physical settlement (otherwise it is cash settled)
159 Size gridEvalEachNth; // evaluate grid engine each nth sim point
160 bool fineGrid; // monthly grid instead of default semiannually?
161 bool inBaseCcy; // if true simulate deal in EUR = base ccy, otherwise in USD
162 Size numExercises; // number of exercises, yearly, starting at 10y
163 Size swapLen; // swap len in years, starting at 10y
164 bool isAmortising; // is swap amortising?
165 Size simYears; // number of years to cover in simulation
166 Real horizonShift; // horizon shift to be applied to CAM, AMC LGM and Grid LGM models
167 Size samples; // EPE simulation samples
168 Size trainingPaths; // training paths
169 Real sx; // numerical lgm engine number of std devs
170 Size nx; // nuermical lgm engine number of points per std dev
171 std::vector<std::vector<Real>> cachedResults; // results from grid engine run
172};
173
174// Needed for BOOST_DATA_TEST_CASE below as it writes out TestCase
175std::ostream& operator<<(std::ostream& os, const TestCase& testCase) { return os << testCase.label; }
176
177TestCase testCaseData[] = {
178 {"Physical Settled Swaption EUR 10y10y",
179 25E-4,
180 true,
181 1,
182 false,
183 true,
184 10,
185 10,
186 false,
187 21,
188 0.0,
189 10000,
190 10000,
191 4.0,
192 10,
193 {{0.509357, 0.091875}, {1.00662, 0.0918901}, {1.50411, 0.0918698}, {2.00274, 0.0918969}, {2.50411, 0.0919471},
194 {3, 0.0919541}, {3.50411, 0.0919187}, {4, 0.091858}, {4.50389, 0.0917798}, {5.00116, 0.0918077},
195 {5.50959, 0.0917394}, {6, 0.0917051}, {6.50685, 0.0915082}, {7.00548, 0.0914719}, {7.50411, 0.0916239},
196 {8.00274, 0.0916299}, {8.50389, 0.0915738}, {9.00116, 0.0917204}, {9.50411, 0.0920092}, {10, 0.0918714},
197 {10.5041, 0.0917764}, {11, 0.0844412}, {11.5096, 0.0831162}, {12, 0.0758481}, {12.5066, 0.0743106},
198 {13.0039, 0.0670203}, {13.5041, 0.0661777}, {14, 0.0583992}, {14.5041, 0.0571024}, {15, 0.0494685},
199 {15.5041, 0.0481926}, {16, 0.0403637}, {16.5039, 0.0389221}, {17.0012, 0.0309469}, {17.5068, 0.0294415},
200 {18.0055, 0.0214747}, {18.5041, 0.0199978}, {19.0027, 0.0118743}, {19.5041, 0.0098358}, {20, 0.00239165},
201 {20.5039, 0}, {21.0012, 0}}},
202 {"Cash Settled Swaption EUR 10y10y",
203 20E-4,
204 false,
205 1,
206 false,
207 true,
208 10,
209 10,
210 false,
211 21,
212 0.0,
213 10000,
214 10000,
215 4.0,
216 10,
217 {{0.509357, 0.091875}, {1.00662, 0.0918901},
218 {1.50411, 0.0918698}, {2.00274, 0.0918969},
219 {2.50411, 0.0919471}, {3, 0.0919541},
220 {3.50411, 0.0919187}, {4, 0.091858},
221 {4.50389, 0.0917798}, {5.00116, 0.0918077},
222 {5.50959, 0.0917394}, {6, 0.0917051},
223 {6.50685, 0.0915082}, {7.00548, 0.0914719},
224 {7.50411, 0.0916239}, {8.00274, 0.0916299},
225 {8.50389, 0.0915738}, {9.00116, 0.0917204},
226 {9.50411, 0.0920092}, {10, 0.0918714},
227 {10.5041, 0.0216995}, {11, 0.0216537},
228 {11.5096, 0.0165814}, {12, 0.0165204},
229 {12.5066, 0.0114238}, {13.0039, 0.0112071},
230 {13.5041, 0.00810083}, {14, 0.00810626},
231 {14.5041, 0.00498351}, {15, 0.00494857},
232 {15.5041, 0.00302471}, {16, 0.00300223},
233 {16.5039, 0.00161475}, {17.0012, 0.00150577},
234 {17.5068, 0.000872231}, {18.0055, 0.000691536},
235 {18.5041, 0.000427726}, {19.0027, 0.000259685},
236 {19.5041, 0}, {20, 0},
237 {20.5039, 0}, {21.0012, 0}}},
238 {"Physical Settled Swaption USD 10y10y",
239 40E-4,
240 true,
241 1,
242 false,
243 false,
244 10,
245 10,
246 false,
247 21,
248 0.0,
249 10000,
250 10000,
251 4.0,
252 10,
253 {{0.509357, 0.05351}, {1.00662, 0.0534781}, {1.50411, 0.0533827}, {2.00274, 0.0532503}, {2.50411, 0.0533026},
254 {3, 0.053185}, {3.50411, 0.0530595}, {4, 0.0529746}, {4.50389, 0.0530751}, {5.00116, 0.0532191},
255 {5.50959, 0.0529909}, {6, 0.0530138}, {6.50685, 0.0529023}, {7.00548, 0.0529725}, {7.50411, 0.0530392},
256 {8.00274, 0.0525997}, {8.50389, 0.052524}, {9.00116, 0.052617}, {9.50411, 0.0528042}, {10, 0.0527962},
257 {10.5041, 0.0501823}, {11, 0.043772}, {11.5096, 0.0440506}, {12, 0.0384473}, {12.5066, 0.0392294},
258 {13.0039, 0.0338302}, {13.5041, 0.0339911}, {14, 0.0287289}, {14.5041, 0.0292609}, {15, 0.0235833},
259 {15.5041, 0.0240571}, {16, 0.0184883}, {16.5039, 0.0189846}, {17.0012, 0.0133277}, {17.5068, 0.0134281},
260 {18.0055, 0.00808448}, {18.5041, 0.00806168}, {19.0027, 0.00289948}, {19.5041, 0.00248467}, {20, 1.91824e-06},
261 {20.5039, 0}, {21.0012, 0}}},
262 // FIXME, this test case fails; the AMC profile looks more reasonable than the reference results though...? to be
263 // checked.
264 /*{"Cash Settled Swaption USD 10y10y",
265 20E-4,
266 false,
267 1,
268 false,
269 false,
270 10,
271 10,
272 false,
273 21,
274 0.0,
275 10000,
276 10000,
277 4.0,
278 10,
279 {{0.509357, 0.0535937}, {1.00662, 0.0535972},
280 {1.50411, 0.053576}, {2.00274, 0.0535674},
281 {2.50411, 0.0535863}, {3, 0.0535605},
282 {3.50411, 0.0535707}, {4, 0.0535609},
283 {4.50389, 0.0535324}, {5.00116, 0.0535029},
284 {5.50959, 0.0534719}, {6, 0.0534826},
285 {6.50685, 0.0534808}, {7.00548, 0.0534828},
286 {7.50411, 0.0534604}, {8.00274, 0.0534544},
287 {8.50389, 0.0534457}, {9.00116, 0.0534749},
288 {9.50411, 0.0534384}, {10, 0.0533359},
289 {10.5041, 0.0115775}, {11, 0.0112601},
290 {11.5096, 0.0107997}, {12, 0.0102093},
291 {12.5066, 0.00892546}, {13.0039, 0.00837041},
292 {13.5041, 0.00697047}, {14, 0.0063756},
293 {14.5041, 0.00537863}, {15, 0.00480582},
294 {15.5041, 0.00387191}, {16, 0.00326433},
295 {16.5039, 0.00265382}, {17.0012, 0.00205139},
296 {17.5068, 0.00157963}, {18.0055, 0.000919889},
297 {18.5041, 0.000675715}, {19.0027, 5.29768e-05},
298 {19.5041, 0}, {20, 0},
299 {20.5039, 0}, {21.0012, 0}}},*/
300 {"Physical Settled Swaption EUR 10y50y (Long Term Simulation)",
301 200E-4,
302 true,
303 4,
304 false,
305 true,
306 50,
307 50,
308 false,
309 61,
310 50.0,
311 10000,
312 10000,
313 4.0,
314 10,
315 {{0.509357, 0}, {1.00662, 0}, {1.50411, 0}, {2.00274, 0.36692},
316 {2.50411, 0}, {3, 0}, {3.50411, 0}, {4, 0.367243},
317 {4.50389, 0}, {5.00116, 0}, {5.50959, 0}, {6, 0.366503},
318 {6.50685, 0}, {7.00548, 0}, {7.50411, 0}, {8.00274, 0.365889},
319 {8.50389, 0}, {9.00116, 0}, {9.50411, 0}, {10, 0.363011},
320 {10.5041, 0}, {11, 0}, {11.5096, 0}, {12, 0.352021},
321 {12.5066, 0}, {13.0039, 0}, {13.5041, 0}, {14, 0.337706},
322 {14.5041, 0}, {15, 0}, {15.5041, 0}, {16, 0.320619},
323 {16.5039, 0}, {17.0012, 0}, {17.5068, 0}, {18.0055, 0.304939},
324 {18.5041, 0}, {19.0027, 0}, {19.5041, 0}, {20, 0.287801},
325 {20.5039, 0}, {21.0012, 0}, {21.5041, 0}, {22, 0.271108},
326 {22.5096, 0}, {23, 0}, {23.5068, 0}, {24.0055, 0.254674},
327 {24.5039, 0}, {25.0012, 0}, {25.5041, 0}, {26, 0.237545},
328 {26.5041, 0}, {27, 0}, {27.5041, 0}, {28, 0.221542},
329 {28.5094, 0}, {29.0066, 0}, {29.5041, 0}, {30.0027, 0.204903},
330 {30.5041, 0}, {31, 0}, {31.5041, 0}, {32, 0.189248},
331 {32.5039, 0}, {33.0012, 0}, {33.5096, 0}, {34, 0.173405},
332 {34.5068, 0}, {35.0055, 0}, {35.5041, 0}, {36.0027, 0.158597},
333 {36.5039, 0}, {37.0012, 0}, {37.5041, 0}, {38, 0.144585},
334 {38.5041, 0}, {39, 0}, {39.5096, 0}, {40, 0.129709},
335 {40.5066, 0}, {41.0039, 0}, {41.5041, 0}, {42, 0.115654},
336 {42.5041, 0}, {43, 0}, {43.5041, 0}, {44, 0.101223},
337 {44.5039, 0}, {45.0012, 0}, {45.5068, 0}, {46.0055, 0.0882498},
338 {46.5041, 0}, {47.0027, 0}, {47.5041, 0}, {48, 0.0751091},
339 {48.5039, 0}, {49.0012, 0}, {49.5041, 0}, {50, 0.0619377},
340 {50.5096, 0}, {51, 0}, {51.5068, 0}, {52.0055, 0.0496666},
341 {52.5039, 0}, {53.0012, 0}, {53.5041, 0}, {54, 0.0373868},
342 {54.5041, 0}, {55, 0}, {55.5041, 0}, {56, 0.0249966},
343 {56.5094, 0}, {57.0066, 0}, {57.5041, 0}, {58.0027, 0.0131594},
344 {58.5041, 0}, {59, 0}, {59.5041, 0}, {60, 0.00169942},
345 {60.5039, 0}, {61.0012, 0}}},
346 {"Physical Settled Amortising Swaption EUR 10y10y",
347 20E-4,
348 true,
349 1,
350 false,
351 true,
352 10,
353 10,
354 true,
355 21,
356 0.0,
357 10000,
358 10000,
359 4.0,
360 10,
361 {{0.509357, 0.0465975}, {1.00662, 0.046597}, {1.50411, 0.0465705},
362 {2.00274, 0.0465701}, {2.50411, 0.0465922}, {3, 0.0465888},
363 {3.50411, 0.0465547}, {4, 0.0465135}, {4.50389, 0.0464943},
364 {5.00116, 0.0465701}, {5.50959, 0.0464974}, {6, 0.0464543},
365 {6.50685, 0.0463007}, {7.00548, 0.0462769}, {7.50411, 0.0464078},
366 {8.00274, 0.0464715}, {8.50389, 0.0464315}, {9.00116, 0.0464997},
367 {9.50411, 0.0467324}, {10, 0.0466733}, {10.5041, 0.0465448},
368 {11, 0.0389981}, {11.5096, 0.0380827}, {12, 0.0312491},
369 {12.5066, 0.0304138}, {13.0039, 0.0243758}, {13.5041, 0.0237934},
370 {14, 0.0183401}, {14.5041, 0.0177131}, {15, 0.0131241},
371 {15.5041, 0.0125303}, {16, 0.00865102}, {16.5039, 0.00818706},
372 {17.0012, 0.00505281}, {17.5068, 0.00468286}, {18.0055, 0.00240449},
373 {18.5041, 0.00216216}, {19.0027, 0.000691815}, {19.5041, 0.00058673},
374 {20, 1.52066e-05}, {20.5039, 0}, {21.0012, 0}}}};
375
376BOOST_FIXTURE_TEST_SUITE(OreAmcTestSuite, ore::test::TopLevelFixture)
377
378BOOST_FIXTURE_TEST_SUITE(AmcBermudanSwaptionTest, TestData)
379
380BOOST_DATA_TEST_CASE(testBermudanSwaptionExposure, boost::unit_test::data::make(testCaseData), testCase) {
381
382 // if true, only output results (e.g. for plotting), do no checks
383 const bool outputResults = false;
384
385 // if true, cached results are used for the reference values computed with the grid engine
386 // if false, the grid engine is used for the computation, which is slow
387 // note that the cached results are for sx=4, nx=10 and gridEvalEachNth=1 (except for Long Term Simulation case,
388 // where it is 4) if these parameters change, the cached results should be refreshed
389 const bool useCachedResults = true;
390
391 BOOST_TEST_MESSAGE("Testing Bermudan swaption exposure profile");
392
393 // Simulation date grid
394 Date today = referenceDate;
395 std::vector<Period> tenorGrid;
396
397 if (!testCase.fineGrid) {
398 // coarse grid 6m spacing
399 for (Size i = 0; i < 2 * testCase.simYears; ++i) {
400 tenorGrid.push_back(((i + 1) * 6) * Months);
401 }
402 } else {
403 // fine grid 1m spacing
404 for (Size i = 0; i < 12 * testCase.simYears; ++i) {
405 tenorGrid.push_back(((i + 1)) * Months);
406 }
407 }
408
409 Calendar cal;
410 if (testCase.inBaseCcy)
411 cal = TARGET();
412 else
413 cal = JointCalendar(UnitedStates(UnitedStates::Settlement), UnitedKingdom());
414
415 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid, cal, ActualActual(ActualActual::ISDA));
416
417 // Model
418 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = ccLgm;
419
420 // Simulation market parameters, we just need the yield curve structure here
421 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
422 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
423 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
424 30 * Years, 40 * Years, 50 * Years});
425 simMarketConfig->setSimulateFXVols(false);
426
427 simMarketConfig->baseCcy() = "EUR";
428 simMarketConfig->setDiscountCurveNames({"EUR", "USD"});
429 simMarketConfig->ccys() = {"EUR", "USD"};
430 std::vector<std::string> tmp;
431 for (auto c : simMarketConfig->ccys()) {
432 if (c != simMarketConfig->baseCcy())
433 tmp.push_back(c + simMarketConfig->baseCcy());
434 }
435 simMarketConfig->setFxCcyPairs(tmp);
436
437 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M"});
438 simMarketConfig->interpolation() = "LogLinear";
439 simMarketConfig->setSwapVolExpiries("EUR", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
440 simMarketConfig->setSwapVolTerms("EUR", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
441
442 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
443 sgd->sequenceType() = SobolBrownianBridge;
444 sgd->seed() = 42;
445 sgd->setGrid(grid);
446
448 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
449 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, market);
450
451 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(market, simMarketConfig);
452 simMarket->scenarioGenerator() = sg;
453
454 // Bermudan swaption for exposure generation
455 Date startDate = cal.advance(today, 2 * Days);
456 Date fwdStartDate = cal.advance(startDate, 10 * Years);
457 Date endDate = cal.advance(fwdStartDate, testCase.swapLen * Years);
458 Schedule fixedSchedule(fwdStartDate, endDate, 1 * Years, cal, Following, Following, DateGeneration::Forward, false);
459 Schedule floatingSchedule(fwdStartDate, endDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following, Following,
460 DateGeneration::Forward, false);
461 Schedule fixedScheduleSwap(startDate, endDate, 1 * Years, cal, Following, Following, DateGeneration::Forward,
462 false);
463 Schedule floatingScheduleSwap(startDate, endDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following, Following,
464 DateGeneration::Forward, false);
465 Schedule fixedScheduleSwapShort(startDate, fwdStartDate, 1 * Years, cal, Following, Following,
466 DateGeneration::Forward, false);
467 Schedule floatingScheduleSwapShort(startDate, fwdStartDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following,
468 Following, DateGeneration::Forward, false);
469 QuantLib::ext::shared_ptr<VanillaSwap> underlying, vanillaswap, vanillaswapshort;
470 QuantLib::ext::shared_ptr<NonstandardSwap> underlyingNs, vanillaswapNs, vanillaswapshortNs;
471 if (!testCase.isAmortising) {
472 // Standard Vanilla Swap
473 if (testCase.inBaseCcy) {
474 underlying = QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap::Payer, 1.0, fixedSchedule, 0.02, Thirty360(Thirty360::BondBasis),
475 floatingSchedule, *simMarket->iborIndex("EUR-EURIBOR-6M"), 0.0,
476 Actual360());
477 } else {
478 underlying = QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap::Payer, 1.0, fixedSchedule, 0.03, Thirty360(Thirty360::BondBasis),
479 floatingSchedule, *simMarket->iborIndex("USD-LIBOR-3M"), 0.0,
480 Actual360());
481 }
482 } else {
483 // Nonstandard Swap
484 std::vector<Real> fixNotionals(fixedSchedule.size() - 1), floatNotionals(floatingSchedule.size() - 1);
485 std::vector<Real> fixNotionalsSwap(fixedScheduleSwap.size() - 1),
486 floatNotionalsSwap(floatingScheduleSwap.size() - 1);
487 std::vector<Real> fixNotionalsShort(fixedScheduleSwapShort.size() - 1),
488 floatNotionalsShort(floatingScheduleSwapShort.size() - 1);
489 for (Size i = 0; i < fixNotionals.size(); ++i)
490 fixNotionals[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionals.size());
491 for (Size i = 0; i < floatNotionals.size(); ++i)
492 floatNotionals[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionals.size());
493 for (Size i = 0; i < fixNotionalsSwap.size(); ++i)
494 fixNotionalsSwap[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionalsSwap.size());
495 for (Size i = 0; i < floatNotionalsSwap.size(); ++i)
496 floatNotionalsSwap[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionalsSwap.size());
497 for (Size i = 0; i < fixNotionalsShort.size(); ++i)
498 fixNotionalsShort[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionalsShort.size());
499 for (Size i = 0; i < floatNotionalsShort.size(); ++i)
500 floatNotionalsShort[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionalsShort.size());
501
502 if (testCase.inBaseCcy) {
503 underlyingNs = QuantLib::ext::make_shared<NonstandardSwap>(
504 VanillaSwap::Payer, fixNotionals, floatNotionals, fixedSchedule,
505 std::vector<Real>(fixNotionals.size(), 0.02), Thirty360(Thirty360::BondBasis), floatingSchedule,
506 *simMarket->iborIndex("EUR-EURIBOR-6M"), 1.0, 0.0, Actual360());
507 } else {
508 underlyingNs = QuantLib::ext::make_shared<NonstandardSwap>(
509 VanillaSwap::Payer, fixNotionals, floatNotionals, fixedSchedule,
510 std::vector<Real>(fixNotionals.size(), 0.03), Thirty360(Thirty360::BondBasis), floatingSchedule,
511 *simMarket->iborIndex("USD-LIBOR-3M"), 1.0, 0.0, Actual360());
512 }
513 }
514
515 // needed for physical exercise in the option wrapper
516 QuantLib::ext::shared_ptr<PricingEngine> underlyingEngine = QuantLib::ext::make_shared<QuantLib::DiscountingSwapEngine>(
517 testCase.inBaseCcy ? simMarket->discountCurve("EUR") : simMarket->discountCurve("USD"));
518 if (underlying)
519 underlying->setPricingEngine(underlyingEngine);
520 if (underlyingNs)
521 underlyingNs->setPricingEngine(underlyingEngine);
522 if (vanillaswap) {
523 vanillaswap->setPricingEngine(underlyingEngine);
524 vanillaswapshort->setPricingEngine(underlyingEngine);
525 }
526 if (vanillaswapNs) {
527 vanillaswapNs->setPricingEngine(underlyingEngine);
528 vanillaswapshortNs->setPricingEngine(underlyingEngine);
529 }
530
531 // collect fixing dates
532 std::vector<Date> fixingDates;
533 if (vanillaswap) {
534 for (Size i = 0; i < vanillaswap->leg(1).size(); ++i) {
535 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(vanillaswap->leg(1)[i]);
536 fixingDates.push_back(c->fixingDate());
537 }
538 } else if (vanillaswapNs) {
539 for (Size i = 0; i < vanillaswapNs->leg(1).size(); ++i) {
540 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(vanillaswapNs->leg(1)[i]);
541 fixingDates.push_back(c->fixingDate());
542 }
543 } else if (underlying) {
544 for (Size i = 0; i < underlying->leg(1).size(); ++i) {
545 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(underlying->leg(1)[i]);
546 fixingDates.push_back(c->fixingDate());
547 }
548 } else if (underlyingNs) {
549 for (Size i = 0; i < underlyingNs->leg(1).size(); ++i) {
550 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(underlyingNs->leg(1)[i]);
551 fixingDates.push_back(c->fixingDate());
552 }
553 }
554
555 // exercise dates
556 std::vector<Date> exerciseDates;
557 for (Size i = 0; i < testCase.numExercises; ++i) {
558 if (!testCase.fineGrid)
559 // coarse grid
560 exerciseDates.push_back(grid->dates()[19 + 2 * i]);
561 else
562 // find grid
563 exerciseDates.push_back(grid->dates()[119 + 12 * i]);
564 }
565
566 QuantLib::ext::shared_ptr<QuantLib::Instrument> tmpUnd;
567 if (testCase.isAmortising)
568 tmpUnd = underlyingNs;
569 else
570 tmpUnd = underlying;
571 std::vector<QuantLib::ext::shared_ptr<QuantLib::Instrument>> undInst(exerciseDates.size(), tmpUnd);
572
573 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<BermudanExercise>(exerciseDates);
574 QuantLib::ext::shared_ptr<Instrument> swaption;
575 Settlement::Type settlementType = testCase.isPhysical ? Settlement::Physical : Settlement::Cash;
576 Settlement::Method settlementMethod =
577 testCase.isPhysical ? Settlement::PhysicalOTC : Settlement::CollateralizedCashPrice;
578 if (testCase.isAmortising)
579 swaption = QuantLib::ext::make_shared<NonstandardSwaption>(underlyingNs, exercise, settlementType, settlementMethod);
580 else
581 swaption = QuantLib::ext::make_shared<Swaption>(underlying, exercise, settlementType, settlementMethod);
582
583 QuantLib::ext::shared_ptr<IrLgm1fParametrization> param;
584 // vol and rev must be consistent to CAM's LGM models in TestData
585 Array emptyTimes;
586 Array alphaEur(1), kappaEur(1), alphaUsd(1), kappaUsd(1);
587 alphaEur[0] = lgm_eur->parametrization()->hullWhiteSigma(1.0);
588 kappaEur[0] = lgm_eur->parametrization()->kappa(1.0);
589 alphaUsd[0] = lgm_usd->parametrization()->hullWhiteSigma(1.0);
590 kappaUsd[0] = lgm_usd->parametrization()->kappa(1.0);
591 if (testCase.inBaseCcy)
592 param = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
593 EURCurrency(), simMarket->discountCurve("EUR"), emptyTimes, alphaEur, emptyTimes, kappaEur);
594 else
595 param = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
596 USDCurrency(), simMarket->discountCurve("USD"), emptyTimes, alphaUsd, emptyTimes, kappaUsd);
597 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> bermmodel = QuantLib::ext::make_shared<LinearGaussMarkovModel>(param);
598
599 // apply horizon shift
600 // for grid engine
601 param->shift() = -param->H(testCase.horizonShift);
602 // for CAM and EUR AMC engine
603 QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_eur->parametrization())->shift() =
604 -QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_eur->parametrization())->H(testCase.horizonShift);
605 // for USD AMC engine (in CAM no effect)
606 QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_usd->parametrization())->shift() =
607 -QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_usd->parametrization())->H(testCase.horizonShift);
608
609 // grid engine
610
611 QuantLib::ext::shared_ptr<PricingEngine> engineGrid;
612 if (testCase.isAmortising)
613 engineGrid = QuantLib::ext::make_shared<NumericLgmNonstandardSwaptionEngine>(bermmodel, Real(testCase.sx), testCase.nx,
614 Real(testCase.sx), testCase.nx);
615 else
616 engineGrid = QuantLib::ext::make_shared<NumericLgmSwaptionEngine>(bermmodel, Real(testCase.sx), testCase.nx,
617 Real(testCase.sx), testCase.nx);
618
619 // mc engine
620 std::vector<Size> externalModelIndices = testCase.inBaseCcy ? std::vector<Size>{0} : std::vector<Size>{1};
621 QuantLib::ext::shared_ptr<PricingEngine> engineMc;
622 if (testCase.isAmortising) {
623 engineMc = QuantLib::ext::make_shared<McLgmNonstandardSwaptionEngine>(
624 testCase.inBaseCcy ? lgm_eur : lgm_usd, MersenneTwisterAntithetic, SobolBrownianBridge,
625 testCase.trainingPaths, 0, 4711, 4712, 6, LsmBasisSystem::Monomial, SobolBrownianGenerator::Steps,
626 SobolRsg::JoeKuoD7, Handle<YieldTermStructure>(), grid->dates(), externalModelIndices);
627 swaption->setPricingEngine(engineMc);
628 } else {
629 engineMc = QuantLib::ext::make_shared<McLgmSwaptionEngine>(
630 testCase.inBaseCcy ? lgm_eur : lgm_usd, MersenneTwisterAntithetic, SobolBrownianBridge,
631 testCase.trainingPaths, 0, 4711, 4712, 6, LsmBasisSystem::Monomial, SobolBrownianGenerator::Steps,
632 SobolRsg::JoeKuoD7, Handle<YieldTermStructure>(), grid->dates(), externalModelIndices);
633 swaption->setPricingEngine(engineMc);
634 }
635
636 // wrapper (long option)
637
638 QuantLib::ext::shared_ptr<InstrumentWrapper> wrapperGrid = QuantLib::ext::make_shared<BermudanOptionWrapper>(
639 swaption, vanillaswap ? false : true, exerciseDates, testCase.isPhysical, undInst);
640 wrapperGrid->initialise(grid->dates());
641
642 // collect discounted epe
643 std::vector<Real> swaption_epe_grid(grid->dates().size(), 0.0);
644 std::vector<Real> swaption_epe_amc(grid->dates().size(), 0.0);
645
646 // amc valuation
647 swaption->setPricingEngine(engineMc);
648 class TestTrade : public Trade {
649 public:
650 TestTrade(const string& tradeType, const string& curr, const QuantLib::ext::shared_ptr<InstrumentWrapper>& inst)
651 : Trade(tradeType) {
652 instrument_ = inst;
653 npvCurrency_ = curr;
654 }
655 void build(const QuantLib::ext::shared_ptr<EngineFactory>&) override {}
656 };
657 AMCValuationEngine amcValEngine(model, sgd, QuantLib::ext::shared_ptr<Market>(), std::vector<string>(),
658 std::vector<string>(), 0);
659 auto trade = QuantLib::ext::make_shared<TestTrade>("BermudanSwaption", testCase.inBaseCcy ? "EUR" : "USD",
660 QuantLib::ext::make_shared<VanillaInstrument>(swaption));
661 trade->id() = "DummyTradeId";
662 auto portfolio = QuantLib::ext::make_shared<Portfolio>();
663 portfolio->add(trade);
664 QuantLib::ext::shared_ptr<NPVCube> outputCube = QuantLib::ext::make_shared<DoublePrecisionInMemoryCube>(
665 referenceDate, std::set<string>{"DummyTradeId"}, grid->dates(), testCase.samples);
666 boost::timer::cpu_timer timer;
667 amcValEngine.buildCube(portfolio, outputCube);
668 timer.stop();
669 Real amcTime = timer.elapsed().wall * 1e-9;
670
671 // epe computation (this is divided by the number of samples below)
672 for (Size j = 0; j < grid->dates().size(); ++j) {
673 for (Size i = 0; i < testCase.samples; ++i) {
674 swaption_epe_amc[j] += std::max(outputCube->get(0, j, i, 0), 0.0);
675 }
676 }
677
678 Real fx = 1.0;
679 if (!testCase.inBaseCcy)
680 fx = simMarket->fxRate("USDEUR")->value();
681
682 Real amcNPV = outputCube->getT0(0, 0) / fx; // convert back to USD, cube contains base ccy values
683
684 timer.start();
685 swaption->setPricingEngine(engineGrid);
686 Real gridNPV = swaption->NPV();
687 timer.stop();
688 Real gridTime0 = timer.elapsed().wall * 1e-9;
689
690 BOOST_CHECK_MESSAGE((std::abs(gridNPV - amcNPV) <= testCase.tolerance),
691 "Can not verify gridNPV (" << gridNPV << ") and amcNPV (" << amcNPV << ")"
692 << ", difference is " << gridNPV - amcNPV << ", tolerance is "
693 << testCase.tolerance);
694
695 // grid engine simulation (only if not cached, this takes a long time)
696 if (useCachedResults) {
697 for (Size t = 0; t < grid->dates().size(); ++t) {
698 swaption_epe_grid.at(t) = testCase.cachedResults.at(t).at(1) * static_cast<Real>(testCase.samples);
699 }
700 } else {
701 Real updateTime = 0.0;
702 Real gridTime = 0.0;
703 BOOST_TEST_MESSAGE("running " << testCase.samples << " samples simulation over " << grid->dates().size()
704 << " time steps");
705 QuantLib::ext::shared_ptr<IborIndex> index =
706 *(testCase.inBaseCcy ? simMarket->iborIndex("EUR-EURIBOR-6M") : simMarket->iborIndex("USD-LIBOR-3M"));
707 for (Size i = 0; i < testCase.samples; ++i) {
708 if (i % 100 == 0)
709 BOOST_TEST_MESSAGE("Sample " << i);
710 Size idx = 0, fixIdx = 0;
711 Size gridCnt = testCase.gridEvalEachNth;
712 for (Date date : grid->dates()) {
713 timer.start();
714 // if we use cached results, we do not need the sim market
715 simMarket->update(date);
716 // set fixings
717 Real v = index->fixing(date);
718 while (fixIdx < fixingDates.size() && fixingDates[fixIdx] <= date) {
719 index->addFixing(fixingDates[fixIdx], v);
720 ++fixIdx;
721 }
722 // we do not use the valuation engine, so in case updates are disabled we need to
723 // take care of the instrument update ourselves
724 // this is only relevant for the discrete sim market, not the model sim market
725 swaption->update();
726 if (underlying)
727 underlying->update();
728 if (underlyingNs)
729 underlyingNs->update();
730 if (vanillaswap) {
731 vanillaswap->update();
732 vanillaswapshort->update();
733 }
734 if (vanillaswapNs) {
735 vanillaswapNs->update();
736 vanillaswapshortNs->update();
737 }
738 timer.stop();
739 updateTime += timer.elapsed().wall * 1e-9;
740 Real numeraire = simMarket->numeraire();
741 Real fx = 1.0;
742 if (!testCase.inBaseCcy)
743 fx = simMarket->fxRate("USDEUR")->value();
744 // swaption epe accumulation
745 if (--gridCnt == 0) {
746 timer.start();
747 swaption_epe_grid[idx] += std::max(wrapperGrid->NPV() * fx, 0.0) / numeraire;
748 timer.stop();
749 gridTime += timer.elapsed().wall * 1e-9;
750 gridCnt = testCase.gridEvalEachNth;
751 }
752 idx++;
753 }
754 wrapperGrid->reset();
755 index->clearFixings();
756 }
757 BOOST_TEST_MESSAGE("Simulation time, grid " << gridTime << ", updates " << updateTime);
758 }
759
760 // compute summary statistics for swaption and check results
761 if (outputResults) {
762 std::clog << "time swaption_epe_grid swaption_epe_amc" << std::endl;
763 }
764 Size gridCnt = testCase.gridEvalEachNth;
765 Real maxSwaptionErr = 0.0;
766 for (Size i = 0; i < swaption_epe_grid.size(); ++i) {
767 Real t = grid->timeGrid()[i + 1];
768 swaption_epe_grid[i] /= testCase.samples;
769 swaption_epe_amc[i] /= testCase.samples;
770 if (outputResults) {
771 if (useCachedResults) {
772 // output all results
773 std::clog << t << " " << swaption_epe_grid[i] << " " << swaption_epe_amc[i] << " " << std::endl;
774 } else {
775 // output results in a format that makes it easy to insert them as cached results in the code above
776 std::clog << "{" << t << ", " << swaption_epe_grid[i] << std::endl;
777 }
778 }
779 if (--gridCnt == 0) {
780 if (!outputResults) {
781 BOOST_CHECK_MESSAGE((std::abs(swaption_epe_grid[i] - swaption_epe_amc[i]) <= testCase.tolerance),
782 "Can not verify swaption epe at grid point t="
783 << t << ", grid = " << swaption_epe_grid[i] << ", amc = " << swaption_epe_amc[i]
784 << ", difference " << (swaption_epe_grid[i] - swaption_epe_amc[i])
785 << ", tolerance " << testCase.tolerance);
786 }
787 maxSwaptionErr = std::max(maxSwaptionErr, std::abs(swaption_epe_grid[i] - swaption_epe_amc[i]));
788 gridCnt = testCase.gridEvalEachNth;
789 }
790 }
791 BOOST_TEST_MESSAGE("AMC simulation time = " << amcTime << "s, T0 NPV (AMC) = " << amcNPV
792 << ", T0 NPV (Grid) = " << gridNPV << " (" << gridTime0 * 1000.0
793 << " ms), Max Error Swaption = " << maxSwaptionErr);
794
795} // testBermudanSwaptionExposure
796
797BOOST_AUTO_TEST_SUITE_END()
798
799BOOST_AUTO_TEST_SUITE_END()
BOOST_DATA_TEST_CASE(testBermudanSwaptionExposure, boost::unit_test::data::make(testCaseData), testCase)
TestCase testCaseData[]
std::ostream & operator<<(std::ostream &os, const TestCase &testCase)
valuation engine for amc
void buildCube(const QuantLib::ext::shared_ptr< ore::data::Portfolio > &portfolio, QuantLib::ext::shared_ptr< ore::analytics::NPVCube > &outputCube)
build cube in single threaded run
QuantLib::ext::shared_ptr< ScenarioGenerator > build(QuantLib::ext::shared_ptr< QuantExt::CrossAssetModel > model, QuantLib::ext::shared_ptr< ScenarioFactory > sf, QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > marketConfig, Date asof, QuantLib::ext::shared_ptr< ore::data::Market > initMarket, const std::string &configuration=ore::data::Market::defaultConfiguration, const QuantLib::ext::shared_ptr< PathGeneratorFactory > &pf=QuantLib::ext::make_shared< MultiPathGeneratorFactory >())
Build function.
void addCorrelation(const std::string &factor1, const std::string &factor2, QuantLib::Real correlation)
const std::map< CorrelationKey, QuantLib::Handle< QuantLib::Quote > > & correlations()
virtual void build(const QuantLib::ext::shared_ptr< EngineFactory > &)=0
OREAnalytics Top level fixture.
Scenario generation using cross asset model paths.
A cube implementation that stores the cube in memory.
Scenario generation using LGM paths.
Date referenceDate
MersenneTwisterAntithetic
SobolBrownianBridge
CalibrationType
Size size(const ValueType &v)
Singleton class to hold global Observation Mode.
Fixture that can be used at top level of OREAnalytics test suites.
Build a scenariogenerator.
A Market class that can be updated by Scenarios.
Simple scenario class.
factory classes for simple scenarios