Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
scenariogenerator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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>
35#include <oret/toplevelfixture.hpp>
37
42#include <qle/models/lgm.hpp>
48
49#include <ql/cashflows/simplecashflow.hpp>
50#include <ql/currencies/america.hpp>
51#include <ql/currencies/europe.hpp>
52#include <ql/indexes/ibor/all.hpp>
53#include <ql/indexes/swap/euriborswap.hpp>
54#include <ql/indexes/swap/usdliborswap.hpp>
55#include <ql/instruments/cpicapfloor.hpp>
56#include <ql/instruments/makeswaption.hpp>
57#include <ql/instruments/makevanillaswap.hpp>
58#include <ql/math/statistics/incrementalstatistics.hpp>
59#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
60#include <ql/quotes/simplequote.hpp>
61#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
62#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
63#include <ql/termstructures/yield/flatforward.hpp>
64#include <ql/time/calendars/target.hpp>
65#include <ql/time/daycounters/actual360.hpp>
66#include <ql/time/daycounters/thirty360.hpp>
67#include "testmarket.hpp"
68
69#include <boost/timer/timer.hpp>
70
71using namespace QuantLib;
72using namespace QuantExt;
73using namespace boost::unit_test_framework;
74using namespace ore::analytics;
75using namespace ore::data;
76using namespace ore;
77using boost::timer::cpu_timer;
78using boost::timer::default_places;
80
81namespace {
82
83void setConventions() {
84 QuantLib::ext::shared_ptr<data::Conventions> conventions(new data::Conventions());
85
86 QuantLib::ext::shared_ptr<data::Convention> swapIndexConv(
87 new data::SwapIndexConvention("EUR-CMS-2Y", "EUR-6M-SWAP-CONVENTIONS"));
88 conventions->add(swapIndexConv);
89
90 QuantLib::ext::shared_ptr<data::Convention> swapConv(
91 new data::IRSwapConvention("EUR-6M-SWAP-CONVENTIONS", "TARGET", "Annual", "MF", "30/360", "EUR-EURIBOR-6M"));
92 conventions->add(swapConv);
93
94 InstrumentConventions::instance().setConventions(conventions);
95}
96
97struct TestData {
98 TestData() : referenceDate(30, July, 2015) {
99 Settings::instance().evaluationDate() = referenceDate;
100
101 // Build test market
102 market = QuantLib::ext::make_shared<TestMarket>(referenceDate);
103
104 // Build IR configurations
105 CalibrationType calibrationType = CalibrationType::Bootstrap;
106 LgmData::ReversionType revType = LgmData::ReversionType::HullWhite;
107 LgmData::VolatilityType volType = LgmData::VolatilityType::Hagan;
108 vector<string> swaptionExpiries = {"1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "15Y", "20Y", "30Y"};
109 vector<string> swaptionTerms = {"5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y"};
110 vector<string> swaptionStrikes(swaptionExpiries.size(), "ATM");
111 vector<Time> hTimes = {};
112 vector<Time> aTimes = {};
113
114 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
115
116 vector<Real> hValues = {0.02};
117 vector<Real> aValues = {0.08};
118 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
119 "EUR", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
120 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
121
122 hValues = {0.03};
123 aValues = {0.009};
124 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
125 "USD", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
126 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
127
128 hValues = {0.04};
129 aValues = {0.01};
130 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
131 "GBP", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
132 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
133
134 // Compile FX configurations
135 vector<string> optionExpiries = {"1Y", "2Y", "3Y", "5Y", "7Y", "10Y"};
136 vector<string> optionStrikes(optionExpiries.size(), "ATMF");
137 vector<Time> sigmaTimes = {};
138
139 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
140
141 vector<Real> sigmaValues = {0.15};
142 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>("USD", "EUR", calibrationType, true, ParamType::Piecewise,
143 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
144
145 sigmaValues = {0.15};
146 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>("GBP", "EUR", calibrationType, true, ParamType::Piecewise,
147 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
148
149 std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
150 // Inflation configurations
151 vector<QuantLib::ext::shared_ptr<InflationModelData>> infConfigs;
152 // Credit configs
153 std::vector<QuantLib::ext::shared_ptr<CrLgmData>> crLgmConfigs;
154 std::vector<QuantLib::ext::shared_ptr<CrCirData>> crCirConfigs;
155 std::vector<QuantLib::ext::shared_ptr<CommoditySchwartzData>> comConfigs;
156
157 vector<QuantLib::ext::shared_ptr<CalibrationInstrument>> instruments = {
158 QuantLib::ext::make_shared<CpiCapFloor>(CapFloor::Cap, 5 * Years, QuantLib::ext::make_shared<AbsoluteStrike>(0.0))
159 };
160 vector<CalibrationBasket> cbUkrpi = { CalibrationBasket(instruments) };
161
162 instruments = {
163 QuantLib::ext::make_shared<CpiCapFloor>(CapFloor::Floor, 5 * Years, QuantLib::ext::make_shared<AbsoluteStrike>(0.0))
164 };
165 vector<CalibrationBasket> cbEuhicpxt = { CalibrationBasket(instruments) };
166
167 ReversionParameter reversion(LgmData::ReversionType::Hagan, false,
168 ParamType::Piecewise, { 1.0 }, { 0.5, 0.5 });
169
170 VolatilityParameter volatility(LgmData::VolatilityType::Hagan, true, 0.1);
171
172 infConfigs.push_back(QuantLib::ext::make_shared<InfDkData>(CalibrationType::Bootstrap,
173 cbUkrpi, "GBP", "UKRPI", reversion, volatility));
174 infConfigs.push_back(QuantLib::ext::make_shared<InfDkData>(CalibrationType::Bootstrap,
175 cbEuhicpxt, "EUR", "EUHICPXT", reversion, volatility));
176
178 cmb.addCorrelation("IR:EUR", "IR:USD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.6)));
179 cmb.addCorrelation("IR:EUR", "IR:GBP", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
180 cmb.addCorrelation("IR:USD", "IR:GBP", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
181 cmb.addCorrelation("FX:USDEUR", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
182 cmb.addCorrelation("IR:EUR", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.2)));
183 cmb.addCorrelation("IR:EUR", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
184 cmb.addCorrelation("IR:USD", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(-0.2)));
185 cmb.addCorrelation("IR:USD", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(-0.1)));
186 cmb.addCorrelation("IR:GBP", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0)));
187 cmb.addCorrelation("IR:GBP", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
188 cmb.addCorrelation("INF:UKRPI", "IR:GBP", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
189 cmb.addCorrelation("INF:EUHICPXT", "IR:EUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
190
191 Real tolerance = 0.0001;
192 QuantLib::ext::shared_ptr<CrossAssetModelData> config(
193 QuantLib::ext::make_shared<CrossAssetModelData>(irConfigs, fxConfigs, eqConfigs, infConfigs, crLgmConfigs,
194 crCirConfigs, comConfigs, 0, cmb.correlations(), tolerance));
195
196 CrossAssetModelBuilder modelBuilder(market, config);
197 ccLgm = *modelBuilder.model();
198
199 lgm = QuantLib::ext::make_shared<QuantExt::LGM>(ccLgm->irlgm1f(0));
200 }
201
202 SavedSettings backup;
203 Date referenceDate;
204 QuantLib::ext::shared_ptr<CrossAssetModelData> config;
205 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> ccLgm;
206 QuantLib::ext::shared_ptr<QuantExt::LGM> lgm;
207 QuantLib::ext::shared_ptr<ore::data::Market> market;
208};
209
210} // anonymous namespace
211
212BOOST_FIXTURE_TEST_SUITE(OREAnalyticsTestSuite, ore::test::OreaTopLevelFixture)
213
214void test_lgm(bool sobol, bool antithetic, bool brownianBridge) {
215
216 BOOST_TEST_MESSAGE("call test_lgm with sobol=" << sobol << " antithetic=" << antithetic << " brownianBridge=" << brownianBridge);
217 TestData d;
218
219 // Simulation date grid
220 Date today = d.referenceDate;
221 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
222 DateGrid grid(tenorGrid);
223
224 // Model
225 QuantLib::ext::shared_ptr<QuantExt::LGM> model = d.lgm;
226
227 // State process
228 QuantLib::ext::shared_ptr<StochasticProcess1D> stateProcess =
229 QuantLib::ext::dynamic_pointer_cast<StochasticProcess1D>(model->stateProcess());
230
231 // Simulation market parameters, we just need the yield curve structure here
232 BOOST_TEST_MESSAGE("set up sim market parameters");
233 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
234 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
235 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
236 30 * Years, 40 * Years, 50 * Years});
237 simMarketConfig->setSimulateFXVols(false);
238 simMarketConfig->setSimulateEquityVols(false);
239 // Multi path generator: Pseudo Random
240 BigNatural seed = 42;
241 // bool antithetic = true;
242 QuantLib::ext::shared_ptr<QuantExt::MultiPathGeneratorBase> pathGen;
243 if (sobol) {
244 if (brownianBridge)
245 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobolBrownianBridge>(stateProcess, grid.timeGrid(),
246 SobolBrownianGenerator::Diagonal, seed);
247 else
248 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobol>(stateProcess, grid.timeGrid(), seed);
249 } else
250 pathGen =
251 QuantLib::ext::make_shared<MultiPathGeneratorMersenneTwister>(stateProcess, grid.timeGrid(), seed, antithetic);
252
253 // Scenario factory
254 // We assume different implementations of the scenario objects which are more or less
255 // optimized w.r.t. memory usage. Hence we use the scenario factory here to avoid
256 // switching in the scenario generator class below.
257 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory =
258 QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
259
260 // Scenario Generator
261 QuantLib::ext::shared_ptr<LgmScenarioGenerator> scenGen =
262 QuantLib::ext::make_shared<LgmScenarioGenerator>(model, pathGen, scenarioFactory, simMarketConfig, today, grid);
263
264 // Basic martingale tests
265 Size samples = 10000;
266 Real eur = 0.0, eur2 = 0.0;
267 for (Size i = 0; i < samples; i++) {
268 for (Date d : grid.dates()) {
269 QuantLib::ext::shared_ptr<Scenario> scenario = scenGen->next(d);
270 if (d == grid.dates().back()) { // in 10 years from today
271 RiskFactorKey key(RiskFactorKey::KeyType::DiscountCurve, "EUR", 8);
272 Real eur10yDiscount = scenario->get(key);
273 Real numeraire = scenario->getNumeraire();
274 eur += eur10yDiscount / numeraire;
275 eur2 += 1.0 / numeraire;
276 }
277 }
278 }
279
280 eur /= samples;
281 eur2 /= samples;
282
283 Real relTolerance = 0.01;
284 Real eurExpected = d.market->discountCurve("EUR")->discount(20.);
285 BOOST_CHECK_MESSAGE(fabs(eur - eurExpected) / eurExpected < relTolerance,
286 "EUR 20Y Discount mismatch: " << eur << " vs " << eurExpected);
287 Real eurExpected2 = d.market->discountCurve("EUR")->discount(10.);
288 BOOST_CHECK_MESSAGE(fabs(eur2 - eurExpected2) / eurExpected2 < relTolerance,
289 "EUR 10Y Discount mismatch: " << eur2 << " vs " << eurExpected2);
290
291 BOOST_TEST_MESSAGE("LGM " << (sobol ? "Sobol " : "MersenneTwister ") << (antithetic ? "Antithetic" : "")
292 << (brownianBridge ? "BrownianBridge" : ""));
293 BOOST_TEST_MESSAGE("EUR 20Y Discount: " << eur << " vs " << eurExpected);
294 BOOST_TEST_MESSAGE("EUR 10Y Discount: " << eur2 << " vs " << eurExpected2);
295}
296
297BOOST_AUTO_TEST_SUITE(ScenarioGeneratorTest)
298
299BOOST_AUTO_TEST_CASE(testLgmMersenneTwister) {
300 BOOST_TEST_MESSAGE("Testing LgmScenarioGenerator with MersenneTwister...");
302 test_lgm(false, false, false);
303}
304
305BOOST_AUTO_TEST_CASE(testLgmMersenneTwisterAntithetic) {
306 BOOST_TEST_MESSAGE("Testing LgmScenarioGenerator with MersenneTwister/Antithetic...");
308 test_lgm(false, true, false);
309}
310
311BOOST_AUTO_TEST_CASE(testLgmLowDiscrepancy) {
312 BOOST_TEST_MESSAGE("Testing LgmScenarioGenerator with LowDiscrepancy...");
314 test_lgm(true, false, false);
315}
316
317BOOST_AUTO_TEST_CASE(testLgmLowDiscrepancyBrownianBridge) {
318 BOOST_TEST_MESSAGE("Testing LgmScenarioGenerator with LowDiscrepancy/BrownianBridge...");
320 test_lgm(true, false, true);
321}
322
323void test_crossasset(bool sobol, bool antithetic, bool brownianBridge) {
324 TestData d;
325
326 // Simulation date grid
327 Date today = d.referenceDate;
328 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
329 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
330
331 // Model
332 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
333
334 // State process
335 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
336
337 // Simulation market parameters, we just need the yield curve structure here
338 BOOST_TEST_MESSAGE("set up sim market parameters");
339 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
340 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
341 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
342 30 * Years, 40 * Years, 50 * Years});
343 simMarketConfig->setSimulateFXVols(false);
344 simMarketConfig->setSimulateEquityVols(false);
345 simMarketConfig->setZeroInflationTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
346 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
347 30 * Years, 40 * Years, 50 * Years});
348
349 // Multi path generator
350 BigNatural seed = 42;
351 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(stateProcess)) {
352 tmp->resetCache(grid->timeGrid().size() - 1);
353 }
354 QuantLib::ext::shared_ptr<QuantExt::MultiPathGeneratorBase> pathGen;
355 if (sobol) {
356 if (brownianBridge)
357 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobolBrownianBridge>(stateProcess, grid->timeGrid(),
358 SobolBrownianGenerator::Diagonal, seed);
359 else
360 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobol>(stateProcess, grid->timeGrid(), seed);
361 } else
362 pathGen =
363 QuantLib::ext::make_shared<MultiPathGeneratorMersenneTwister>(stateProcess, grid->timeGrid(), seed, antithetic);
364
365 // Scenario factory
366 // We assume different implementations of the scenario objects which are more or less
367 // optimized w.r.t. memory usage. Hence we use the scenario factory here to avoid
368 // switching in the scenario generator class below.
369 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory(new SimpleScenarioFactory);
370
371 // Scenario Generator
372 QuantLib::ext::shared_ptr<CrossAssetModelScenarioGenerator> scenGen = QuantLib::ext::make_shared<CrossAssetModelScenarioGenerator>(
373 model, pathGen, scenarioFactory, simMarketConfig, today, grid, d.market);
374
375 // Basic martingale tests
376 Size samples = 10000;
377 Real eur = 0.0, usd = 0.0, gbp = 0.0, eur2 = 0.0, usd2 = 0.0, gbp2 = 0.0, eur3 = 0.0;
378
379 cpu_timer timer;
380 for (Size i = 0; i < samples; i++) {
381 for (Date d : grid->dates()) {
382 QuantLib::ext::shared_ptr<Scenario> scenario = scenGen->next(d);
383
384 if (d == grid->dates().back()) { // in 10 years from today
385 RiskFactorKey eurKey(RiskFactorKey::KeyType::DiscountCurve, "EUR", 8);
386 RiskFactorKey usdKey(RiskFactorKey::KeyType::DiscountCurve, "USD", 8);
387 RiskFactorKey gbpKey(RiskFactorKey::KeyType::DiscountCurve, "GBP", 8);
388 RiskFactorKey usdeurKey(RiskFactorKey::KeyType::FXSpot, "USDEUR");
389 RiskFactorKey gbpeurKey(RiskFactorKey::KeyType::FXSpot, "GBPEUR");
390 RiskFactorKey euhicpKey(RiskFactorKey::KeyType::CPIIndex, "EUHICPXT");
391
392 Real usdeurFX = scenario->get(usdeurKey);
393 Real gbpeurFX = scenario->get(gbpeurKey);
394 Real numeraire = scenario->getNumeraire();
395 Real eur10yDiscount = scenario->get(eurKey);
396 Real gbp10yDiscount = scenario->get(gbpKey);
397 Real usd10yDiscount = scenario->get(usdKey);
398 Real euhicp = scenario->get(euhicpKey);
399 eur += eur10yDiscount / numeraire;
400 gbp += gbp10yDiscount * gbpeurFX / numeraire;
401 usd += usd10yDiscount * usdeurFX / numeraire;
402 eur2 += 1.0 / numeraire;
403 gbp2 += gbpeurFX / numeraire;
404 usd2 += usdeurFX / numeraire;
405 eur3 += euhicp / numeraire;
406 }
407 }
408 }
409 timer.stop();
410
411 eur /= samples;
412 gbp /= samples;
413 usd /= samples;
414 eur2 /= samples;
415 gbp2 /= samples;
416 usd2 /= samples;
417 eur3 /= samples;
418
419 Real relTolerance = 0.01;
420 Real eurExpected = d.market->discountCurve("EUR")->discount(20.);
421 BOOST_CHECK_MESSAGE(fabs(eur - eurExpected) / eurExpected < relTolerance,
422 "EUR 20Y Discount mismatch: " << eur << " vs " << eurExpected);
423 Real gbpExpected = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(20.);
424 BOOST_CHECK_MESSAGE(fabs(gbp - gbpExpected) / gbpExpected < relTolerance,
425 "GBP 20Y Discount mismatch: " << gbp << " vs " << gbpExpected);
426 Real usdExpected = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(20.);
427 BOOST_CHECK_MESSAGE(fabs(usd - usdExpected) / usdExpected < relTolerance,
428 "USD 20Y Discount mismatch: " << usd << " vs " << usdExpected);
429
430 Real eurExpected2 = d.market->discountCurve("EUR")->discount(10.);
431 BOOST_CHECK_MESSAGE(fabs(eur2 - eurExpected2) / eurExpected2 < relTolerance,
432 "EUR 10Y Discount mismatch: " << eur2 << " vs " << eurExpected2);
433 Real gbpExpected2 = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(10.);
434 BOOST_CHECK_MESSAGE(fabs(gbp2 - gbpExpected2) / gbpExpected2 < relTolerance,
435 "GBP 10Y Discount mismatch: " << gbp2 << " vs " << gbpExpected2);
436 Real usdExpected2 = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(10.);
437 BOOST_CHECK_MESSAGE(fabs(usd2 - usdExpected2) / usdExpected2 < relTolerance,
438 "USD 10Y Discount mismatch: " << usd2 << " vs " << usdExpected2);
439
440 Real eurExpected3 =
441 d.market->zeroInflationIndex("EUHICPXT")
442 ->fixing(d.market->zeroInflationIndex("EUHICPXT")->zeroInflationTermStructure()->baseDate()) *
443 pow(1 + d.market->zeroInflationIndex("EUHICPXT")->zeroInflationTermStructure()->zeroRate(10.), 10) *
444 d.market->discountCurve("EUR")->discount(10.);
445 BOOST_CHECK_MESSAGE(fabs(eur3 - eurExpected3) / eurExpected3 < relTolerance,
446 "EUHICPXT CPI Rate mismatch: " << eur3 << " vs " << eurExpected3);
447
448 BOOST_TEST_MESSAGE("CrossAssetModel " << (sobol ? "Sobol " : "MersenneTwister ") << (antithetic ? "Antithetic" : "")
449 << (brownianBridge ? "BrownianBridge" : ""));
450 BOOST_TEST_MESSAGE("EUR 20Y Discount: " << eur << " vs " << eurExpected);
451 BOOST_TEST_MESSAGE("GBP 20Y Discount in EUR: " << gbp << " vs " << gbpExpected);
452 BOOST_TEST_MESSAGE("USD 20Y Discount in EUR: " << usd << " vs " << usdExpected);
453 BOOST_TEST_MESSAGE("EUR 10Y Discount: " << eur2 << " vs " << eurExpected2);
454 BOOST_TEST_MESSAGE("GBP 10Y Discount in EUR: " << gbp2 << " vs " << gbpExpected2);
455 BOOST_TEST_MESSAGE("USD 10Y Discount in EUR: " << usd2 << " vs " << usdExpected2);
456 BOOST_TEST_MESSAGE("EUHICPXT CPI: " << eur3 << " vs " << eurExpected3);
457 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w"));
458}
459
460BOOST_AUTO_TEST_CASE(testCrossAssetMersenneTwister) {
461 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator with MersenneTwister...");
463 test_crossasset(false, false, false);
464}
465
466BOOST_AUTO_TEST_CASE(testCrossAssetMersenneTwisterAntithetic) {
467 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator with MersenneTwister/Antithetic...");
469 test_crossasset(false, true, false);
470}
471
472BOOST_AUTO_TEST_CASE(testCrossAssetLowDiscrepancy) {
473 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator with LowDiscrepancy...");
475 test_crossasset(true, false, false);
476}
477
478BOOST_AUTO_TEST_CASE(testCrossAssetLowDiscrepancyBrownianBridge) {
479 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator with LowDiscrepancy/BrownianBridge...");
481 test_crossasset(true, false, true);
482}
483
484BOOST_AUTO_TEST_CASE(testCrossAssetSimMarket) {
485 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator via SimMarket (Martingale tests)...");
487
488 TestData d;
489
490 // Simulation date grid
491 Date today = d.referenceDate;
492 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
493 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
494
495 // Model
496 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
497
498 // State process
499 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
500
501 // Simulation market parameters, we just need the yield curve structure here
502 BOOST_TEST_MESSAGE("set up sim market parameters");
503 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
504 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
505 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
506 30 * Years, 40 * Years, 50 * Years});
507 simMarketConfig->setSimulateFXVols(false);
508 simMarketConfig->setSimulateEquityVols(false);
509
510 simMarketConfig->baseCcy() = "EUR";
511 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
512 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
513 simMarketConfig->interpolation() = "LogLinear";
514 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
515 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
516 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
517 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
518
519 BOOST_TEST_MESSAGE("set up scenario generator builder");
520 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
521 sgd->sequenceType() = Sobol;
522 sgd->seed() = 42;
523 sgd->setGrid(grid);
524
526 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>();
527 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
528
529 BOOST_TEST_MESSAGE("set up scenario sim market");
530 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
531 simMarket->scenarioGenerator() = sg;
532
533 // Basic Martingale tests
534 Size samples = 10000;
535 Real eur = 0.0, usd = 0.0, gbp = 0.0, eur2 = 0.0, usd2 = 0.0, gbp2 = 0.0;
536 Real eur3 = 0.0, usd3 = 0.0, gbp3 = 0.0;
537 int horizon = 10;
538
539 Date d1 = grid->dates().back();
540 Date d2 = d1 + horizon * Years;
541 Real relTolerance = 0.015;
542 Real eurExpected = d.market->discountCurve("EUR")->discount(d2);
543 Real eurExpected2 = d.market->discountCurve("EUR")->discount(d1);
544 Real gbpExpected = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(d2);
545 Real gbpExpected2 = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(d1);
546 Real usdExpected = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(d2);
547 Real usdExpected2 = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(d1);
548
549 cpu_timer timer;
550 Real updateTime = 0.0;
551 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
552 for (Size i = 0; i < samples; i++) {
553 for (Date d : grid->dates()) {
554 timer.resume();
555 simMarket->update(d);
556 timer.stop();
557 updateTime += timer.elapsed().wall * 1e-9;
558 if (d == grid->dates().back()) {
559 Real numeraire = simMarket->numeraire();
560 Real usdeurFX = simMarket->fxRate("USDEUR")->value();
561 Real gbpeurFX = simMarket->fxRate("GBPEUR")->value();
562 Real eurDiscount = simMarket->discountCurve("EUR")->discount(1.0 * horizon);
563 Real gbpDiscount = simMarket->discountCurve("GBP")->discount(1.0 * horizon);
564 ;
565 Real usdDiscount = simMarket->discountCurve("USD")->discount(1.0 * horizon);
566 ;
567 Real eurIndex =
568 simMarket->iborIndex("EUR-EURIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
569 Real gbpIndex =
570 simMarket->iborIndex("GBP-LIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
571 ;
572 Real usdIndex =
573 simMarket->iborIndex("USD-LIBOR-3M")->forwardingTermStructure()->discount(1.0 * horizon);
574 ;
575 eur += eurDiscount / numeraire;
576 gbp += gbpDiscount * gbpeurFX / numeraire;
577 usd += usdDiscount * usdeurFX / numeraire;
578 eur2 += 1.0 / numeraire;
579 gbp2 += gbpeurFX / numeraire;
580 usd2 += usdeurFX / numeraire;
581 eur3 += eurIndex / numeraire;
582 gbp3 += gbpIndex * gbpeurFX / numeraire;
583 usd3 += usdIndex * usdeurFX / numeraire;
584 }
585 }
586 }
587
588 eur /= samples;
589 gbp /= samples;
590 usd /= samples;
591 eur2 /= samples;
592 gbp2 /= samples;
593 usd2 /= samples;
594 eur3 /= samples;
595 gbp3 /= samples;
596 usd3 /= samples;
597
598 BOOST_CHECK_MESSAGE(fabs(eur - eurExpected) / eurExpected < relTolerance,
599 "EUR 20Y Discount mismatch: " << eur << " vs " << eurExpected);
600 BOOST_CHECK_MESSAGE(fabs(gbp - gbpExpected) / gbpExpected < relTolerance,
601 "GBP 20Y Discount mismatch: " << gbp << " vs " << gbpExpected);
602 BOOST_CHECK_MESSAGE(fabs(usd - usdExpected) / usdExpected < relTolerance,
603 "USD 20Y Discount mismatch: " << usd << " vs " << usdExpected);
604 BOOST_CHECK_MESSAGE(fabs(eur3 - eurExpected) / eurExpected < relTolerance,
605 "EUR 20Y Index Discount mismatch: " << eur3 << " vs " << eurExpected);
606 BOOST_CHECK_MESSAGE(fabs(gbp3 - gbpExpected) / gbpExpected < relTolerance,
607 "GBP 20Y Index Discount mismatch: " << gbp3 << " vs " << gbpExpected);
608 BOOST_CHECK_MESSAGE(fabs(usd3 - usdExpected) / usdExpected < relTolerance,
609 "USD 20Y Index Discount mismatch: " << usd3 << " vs " << usdExpected);
610 BOOST_CHECK_MESSAGE(fabs(eur2 - eurExpected2) / eurExpected2 < relTolerance,
611 "EUR 10Y Discount mismatch: " << eur2 << " vs " << eurExpected2);
612 BOOST_CHECK_MESSAGE(fabs(gbp2 - gbpExpected2) / gbpExpected2 < relTolerance,
613 "GBP 10Y Discount mismatch: " << gbp2 << " vs " << gbpExpected2);
614 BOOST_CHECK_MESSAGE(fabs(usd2 - usdExpected2) / usdExpected2 < relTolerance,
615 "USD 10Y Discount mismatch: " << usd2 << " vs " << usdExpected2);
616
617 BOOST_TEST_MESSAGE("CrossAssetModel via ScenarioSimMarket");
618 BOOST_TEST_MESSAGE("EUR " << QuantLib::io::iso_date(d2) << " Discount: " << eur << " vs " << eurExpected);
619 BOOST_TEST_MESSAGE("GBP " << QuantLib::io::iso_date(d2) << " Discount in EUR: " << gbp << " vs " << gbpExpected);
620 BOOST_TEST_MESSAGE("USD " << QuantLib::io::iso_date(d2) << " Discount in EUR: " << usd << " vs " << usdExpected);
621 BOOST_TEST_MESSAGE("EUR " << QuantLib::io::iso_date(d1) << " Discount: " << eur2 << " vs " << eurExpected2);
622 BOOST_TEST_MESSAGE("GBP " << QuantLib::io::iso_date(d1) << " Discount in EUR: " << gbp2 << " vs " << gbpExpected2);
623 BOOST_TEST_MESSAGE("USD " << QuantLib::io::iso_date(d1) << " Discount in EUR: " << usd2 << " vs " << usdExpected2);
624 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w") << ", update time " << updateTime);
625}
626
627BOOST_AUTO_TEST_CASE(testCrossAssetSimMarket2) {
628 BOOST_TEST_MESSAGE("Testing CrossAssetScenarioGenerator via SimMarket (direct test against model)...");
630 TestData d;
631
632 // Simulation date grid
633 Date today = d.referenceDate;
634 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
635 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
636
637 // Model
638 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
639
640 // State process
641 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
642
643 // Simulation market parameters, we just need the yield curve structure here
644 BOOST_TEST_MESSAGE("set up sim market parameters");
645 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
646 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
647 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
648 30 * Years, 40 * Years, 50 * Years});
649 simMarketConfig->setSimulateFXVols(false);
650 simMarketConfig->setSimulateEquityVols(false);
651
652 simMarketConfig->baseCcy() = "EUR";
653 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
654 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
655 simMarketConfig->interpolation() = "LogLinear";
656 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
657 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
658 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
659 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
660
661 BOOST_TEST_MESSAGE("set up scenario generator builder");
662 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
663 sgd->sequenceType() = Sobol;
664 sgd->directionIntegers() = SobolRsg::JoeKuoD7;
665 sgd->seed() = 42;
666 sgd->setGrid(grid);
667
669 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
670 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
671 // QuantLib::ext::shared_ptr<DateGrid> grid = sb.dateGrid();
672
673 BOOST_TEST_MESSAGE("set up scenario sim market");
674 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
675 simMarket->scenarioGenerator() = sg;
676
677 // set up model based simulation (mimicking exactly the scenario generator builder above)
678 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(stateProcess)) {
679 tmp->resetCache(grid->timeGrid().size() - 1);
680 }
681 MultiPathGeneratorSobol pathGen(stateProcess, grid->timeGrid(), 42);
682
683 Size samples = 10000;
684 double horizon = 10.0; // sample point for curves
685 Real tol0 = 1.0E-10; // for numeraire, fx spot
686 Real tol1 = 1.0E-4; // for curves (different interpolation, this is 0.1bp in zero yield)
687
688 // manual copy of the initial index curves with fixed reference date (in market, they have floating ref date)
689 Handle<YieldTermStructure> eurIndexCurve(
690 QuantLib::ext::make_shared<FlatForward>(d.referenceDate, 0.02, ActualActual(ActualActual::ISDA)));
691 Handle<YieldTermStructure> usdIndexCurve(
692 QuantLib::ext::make_shared<FlatForward>(d.referenceDate, 0.03, ActualActual(ActualActual::ISDA)));
693 Handle<YieldTermStructure> gbpIndexCurve(
694 QuantLib::ext::make_shared<FlatForward>(d.referenceDate, 0.04, ActualActual(ActualActual::ISDA)));
695
696 cpu_timer timer;
697
698 Real updateTime = 0.0;
699 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
700 for (Size i = 0; i < samples; i++) {
701 Sample<MultiPath> path = pathGen.next();
702 Size idx = 0;
703 for (Date date : grid->dates()) {
704 timer.resume();
705 simMarket->update(date);
706 timer.stop();
707 updateTime += timer.elapsed().wall * 1e-9;
708 // compare a sample of the simulated data with a parallel direct run of the model
709 // sim market
710 Real numeraire = simMarket->numeraire();
711 Real usdeurFX = simMarket->fxRate("USDEUR")->value();
712 Real gbpeurFX = simMarket->fxRate("GBPEUR")->value();
713 Real eurDiscount = simMarket->discountCurve("EUR")->discount(1.0 * horizon);
714 Real gbpDiscount = simMarket->discountCurve("GBP")->discount(1.0 * horizon);
715 ;
716 Real usdDiscount = simMarket->discountCurve("USD")->discount(1.0 * horizon);
717 ;
718 Real eurIndex = simMarket->iborIndex("EUR-EURIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
719 Real gbpIndex = simMarket->iborIndex("GBP-LIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
720 ;
721 Real usdIndex = simMarket->iborIndex("USD-LIBOR-3M")->forwardingTermStructure()->discount(1.0 * horizon);
722 ;
723 // model based values
724 idx++;
725 Real t = grid->timeGrid()[idx];
726 Real state_eur = path.value[0][idx];
727 Real numeraire_m = model->numeraire(0, t, state_eur);
728 Real usdeurFX_m = std::exp(path.value[3][idx]);
729 Real gbpeurFX_m = std::exp(path.value[4][idx]);
730 Real eurDiscount_m = model->discountBond(0, t, t + 1.0 * horizon, path.value[0][idx]);
731 Real usdDiscount_m = model->discountBond(1, t, t + 1.0 * horizon, path.value[1][idx]);
732 Real gbpDiscount_m = model->discountBond(2, t, t + 1.0 * horizon, path.value[2][idx]);
733 Real eurIndex_m = model->discountBond(0, t, t + 1.0 * horizon, path.value[0][idx], eurIndexCurve);
734 Real usdIndex_m = model->discountBond(1, t, t + 1.0 * horizon, path.value[1][idx], usdIndexCurve);
735 Real gbpIndex_m = model->discountBond(2, t, t + 1.0 * horizon, path.value[2][idx], gbpIndexCurve);
736 BOOST_CHECK_MESSAGE(fabs(numeraire - numeraire_m) < tol0,
737 "numeraire mismatch, path " << i << ", grid point " << idx << ", simmarket = "
738 << numeraire << ", model = " << numeraire_m);
739 BOOST_CHECK_MESSAGE(fabs(usdeurFX - usdeurFX_m) < tol0,
740 "usdeurFX mismatch, path " << i << ", grid point " << idx << ", simmarket = "
741 << usdeurFX << ", model = " << usdeurFX_m);
742 BOOST_CHECK_MESSAGE(fabs(gbpeurFX - gbpeurFX_m) < tol0,
743 "gbpeurFX mismatch, path " << i << ", grid point " << idx << ", simmarket = "
744 << gbpeurFX << ", model = " << gbpeurFX_m);
745 BOOST_CHECK_MESSAGE(fabs(eurDiscount - eurDiscount_m) < tol1,
746 "eurDiscount mismatch, path " << i << ", grid point " << idx << ", simmarket = "
747 << eurDiscount << ", model = " << eurDiscount_m);
748 BOOST_CHECK_MESSAGE(fabs(usdDiscount - usdDiscount_m) < tol1,
749 "usdDiscount mismatch, path " << i << ", grid point " << idx << ", simmarket = "
750 << usdDiscount << ", model = " << usdDiscount_m);
751 BOOST_CHECK_MESSAGE(fabs(gbpDiscount - gbpDiscount_m) < tol1,
752 "gbpDiscount mismatch, path " << i << ", grid point " << idx << ", simmarket = "
753 << gbpDiscount << ", model = " << gbpDiscount_m);
754 BOOST_CHECK_MESSAGE(fabs(eurIndex - eurIndex_m) < tol1,
755 "eurIndex mismatch, path " << i << ", grid point " << idx << ", simmarket = "
756 << eurIndex << ", model = " << eurIndex_m);
757 BOOST_CHECK_MESSAGE(fabs(usdIndex - usdIndex_m) < tol1,
758 "usdIndex mismatch, path " << i << ", grid point " << idx << ", simmarket = "
759 << usdIndex << ", model = " << usdIndex_m);
760 BOOST_CHECK_MESSAGE(fabs(gbpIndex - gbpIndex_m) < tol1,
761 "gbpIndex mismatch, path " << i << ", grid point " << idx << ", simmarket = "
762 << gbpIndex << ", model = " << gbpIndex_m);
763 }
764 }
765 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w") << ", update time " << updateTime);
766}
767
768BOOST_AUTO_TEST_CASE(testVanillaSwapExposure) {
769 BOOST_TEST_MESSAGE("Testing EUR and USD vanilla swap exposure profiles generated with CrossAssetScenarioGenerator");
771
772 TestData d;
773
774 // Simulation date grid
775 Date today = d.referenceDate;
776 std::vector<Period> tenorGrid;
777 for (Size i = 0; i < 20; ++i)
778 tenorGrid.push_back((i + 1) * Years);
779 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
780
781 // Model
782 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
783 model->irlgm1f(0)->shift() = 20.0;
784
785 Size samples = 5000;
786
787 // Simulation market parameters, we just need the yield curve structure here
788 BOOST_TEST_MESSAGE("set up sim market parameters");
789 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
790 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
791 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
792 30 * Years, 40 * Years, 50 * Years});
793 simMarketConfig->setSimulateFXVols(false);
794 simMarketConfig->setSimulateEquityVols(false);
795
796 simMarketConfig->baseCcy() = "EUR";
797 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
798 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
799 simMarketConfig->interpolation() = "LogLinear";
800 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
801 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
802 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
803 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
804
805 BOOST_TEST_MESSAGE("set up scenario generator builder");
806 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
807 sgd->sequenceType() = SobolBrownianBridge;
808 sgd->seed() = 42;
809 sgd->setGrid(grid);
810
812 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
813 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
814 // QuantLib::ext::shared_ptr<DateGrid> grid = sb.dateGrid();
815
816 BOOST_TEST_MESSAGE("set up scenario sim market");
817 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
818 simMarket->scenarioGenerator() = sg;
819
820 // swaps for expsoure generation
821
822 QuantLib::ext::shared_ptr<VanillaSwap> swap_eur =
823 MakeVanillaSwap(20 * Years, *simMarket->iborIndex("EUR-EURIBOR-6M"), 0.02);
824 QuantLib::ext::shared_ptr<VanillaSwap> swap_usd = MakeVanillaSwap(20 * Years, *simMarket->iborIndex("USD-LIBOR-3M"), 0.03);
825
826 // swaptions (manual inspection reveals that the expiry
827 // dates for usd are identical to eur)
828 QuantLib::ext::shared_ptr<PricingEngine> eurLgmSwaptionEngine =
829 QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(model, 0, simMarket->discountCurve("EUR"));
830 QuantLib::ext::shared_ptr<PricingEngine> usdLgmSwaptionEngine =
831 QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(model, 1, simMarket->discountCurve("USD"));
832 std::vector<Real> swaptions_eur, swaptions_usd;
833 for (Size i = 1; i <= 19; ++i) {
834 QuantLib::ext::shared_ptr<SwapIndex> swapIdx_eur = QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(
835 (20 - i) * Years, simMarket->iborIndex("EUR-EURIBOR-6M")->forwardingTermStructure(),
836 simMarket->discountCurve("EUR"));
837 QuantLib::ext::shared_ptr<SwapIndex> swapIdx_usd = QuantLib::ext::make_shared<UsdLiborSwapIsdaFixAm>(
838 (20 - i) * Years, simMarket->iborIndex("USD-LIBOR-3M")->forwardingTermStructure(),
839 simMarket->discountCurve("USD"));
840 QuantLib::ext::shared_ptr<Swaption> swaption_eur =
841 MakeSwaption(swapIdx_eur, i * Years, 0.02).withPricingEngine(eurLgmSwaptionEngine);
842 QuantLib::ext::shared_ptr<Swaption> swaption_usd =
843 MakeSwaption(swapIdx_usd, i * Years, 0.03).withPricingEngine(usdLgmSwaptionEngine);
844 swaptions_eur.push_back(swaption_eur->NPV());
845 swaptions_usd.push_back(swaption_usd->NPV() * simMarket->fxRate("USDEUR")->value());
846 }
847 swaptions_eur.push_back(0.0);
848 swaptions_usd.push_back(0.0);
849
850 // collect discounted epe
851 std::vector<Real> swap_eur_epe(grid->dates().size()), swap_usd_epe(grid->dates().size());
852
853 cpu_timer timer;
854
855 Real updateTime = 0.0;
856 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
857 for (Size i = 0; i < samples; i++) {
858 Size idx = 0;
859 for (Date date : grid->dates()) {
860 timer.resume();
861 simMarket->update(date);
862 // do not include the first payments (to be comparable with a standard swaption)
863 // i.e. set a settlement lag that kills this payment
864 Date settlementDate = date + 10;
865 QuantLib::ext::shared_ptr<PricingEngine> swapEngine_eur =
866 QuantLib::ext::make_shared<QuantExt::DiscountingSwapEngineMultiCurve>(simMarket->discountCurve("EUR"), true,
867 boost::none, settlementDate, date);
868 swap_eur->setPricingEngine(swapEngine_eur);
869 QuantLib::ext::shared_ptr<PricingEngine> swapEngine_usd =
870 QuantLib::ext::make_shared<QuantExt::DiscountingSwapEngineMultiCurve>(simMarket->discountCurve("USD"), true,
871 boost::none, settlementDate, date);
872 swap_usd->setPricingEngine(swapEngine_usd);
873 // we do not use the valuation engine, so in case updates are disabled we need to
874 // take care of the instrument update ourselves
875 swap_eur->update();
876 swap_usd->update();
877 timer.stop();
878 updateTime += timer.elapsed().wall * 1e-9;
879 Real numeraire = simMarket->numeraire();
880 Real usdeurFX = simMarket->fxRate("USDEUR")->value();
881 // swap
882 swap_eur_epe[idx] += std::max(swap_eur->NPV(), 0.0) / numeraire;
883 swap_usd_epe[idx] += std::max(swap_usd->NPV(), 0.0) * usdeurFX / numeraire;
884 idx++;
885 }
886 }
887 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w") << ", update time " << updateTime);
888
889 // compute summary statistics for swap
890 Real tol_eur = 4.0E-4, tol_usd = 13.0E-4;
891 for (Size i = 0; i < swap_eur_epe.size(); ++i) {
892 Real t = grid->timeGrid()[i + 1];
893 swap_eur_epe[i] /= samples;
894 swap_usd_epe[i] /= samples;
895 // BOOST_TEST_MESSAGE(t << " " << swap_eur_epe[i] << " "
896 // << swaptions_eur[i] << " " << swap_usd_epe[i]
897 // << " " << swaptions_usd[i]);
898 BOOST_CHECK_MESSAGE(fabs(swap_eur_epe[i] - swaptions_eur[i]) < tol_eur,
899 "discounted EUR swap epe at t=" << t << " (" << swap_eur_epe[i]
900 << ") inconsistent to analytical swaption premium ("
901 << swaptions_eur[i] << "), tolerance is " << tol_eur);
902 BOOST_CHECK_MESSAGE(fabs(swap_usd_epe[i] - swaptions_usd[i]) < tol_usd,
903 "discounted USD swap epe at t=" << t << " (" << swap_usd_epe[i]
904 << ") inconsistent to analytical swaption premium ("
905 << swaptions_usd[i] << "), tolerance is " << tol_usd);
906 }
907}
908
909BOOST_AUTO_TEST_CASE(testFxForwardExposure) {
910 BOOST_TEST_MESSAGE("Testing EUR-USD FX Forward and FX Vanilla Option exposure");
912
913 TestData d;
914
915 // Simulation date grid
916 Date today = d.referenceDate;
917 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 4 * Years, 5 * Years};
918 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
919
920 // Model
921 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
922
923 // Simulation market parameters
924 BOOST_TEST_MESSAGE("set up sim market parameters");
925 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
926 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
927 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
928 30 * Years, 40 * Years, 50 * Years});
929
930 simMarketConfig->baseCcy() = "EUR";
931 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
932 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
933 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
934 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
935 simMarketConfig->setFxVolExpiries("",
936 vector<Period>{6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
937 simMarketConfig->setFxVolDecayMode(string("ForwardVariance"));
938 simMarketConfig->setFxVolCcyPairs({"USDEUR"});
939 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
940 simMarketConfig->setSimulateFXVols(false);
941 simMarketConfig->setSimulateEquityVols(false);
942 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
943
944 BOOST_TEST_MESSAGE("set up scenario generator builder");
945 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
946 sgd->sequenceType() = SobolBrownianBridge;
947 sgd->seed() = 42;
948 sgd->setGrid(grid);
949
951 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
952 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
953 // QuantLib::ext::shared_ptr<DateGrid> grid = sb.dateGrid();
954
955 BOOST_TEST_MESSAGE("set up scenario sim market");
956 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
957 simMarket->scenarioGenerator() = sg;
958
959 Size samples = 5000;
960
961 // fx forward for expsoure generation (otm) and engine
962 QuantLib::ext::shared_ptr<FxForward> fxfwd =
963 QuantLib::ext::make_shared<FxForward>(1.0, EURCurrency(), 1.3, USDCurrency(), grid->dates().back() + 1, false);
964 QuantLib::ext::shared_ptr<PricingEngine> fxFwdEngine =
965 QuantLib::ext::make_shared<DiscountingFxForwardEngine>(EURCurrency(), simMarket->discountCurve("EUR"), USDCurrency(),
966 simMarket->discountCurve("USD"), simMarket->fxRate("USDEUR"));
967 fxfwd->setPricingEngine(fxFwdEngine);
968
969 // fx option as reference
970 QuantLib::ext::shared_ptr<VanillaOption> fxOption =
971 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, 1.0 / 1.3),
972 QuantLib::ext::make_shared<EuropeanExercise>(grid->dates().back()));
973 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> modelEngine =
974 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(model, 0);
975 fxOption->setPricingEngine(modelEngine);
976 Real refNpv = fxOption->NPV() * 1.3;
977
978 // fx option for simulation
979 QuantLib::ext::shared_ptr<VanillaOption> fxOptionSim =
980 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, 1.0 / 1.3),
981 QuantLib::ext::make_shared<EuropeanExercise>(grid->dates().back() + 1));
982 QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> simGbm =
983 QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(simMarket->fxRate("USDEUR"), simMarket->discountCurve("USD"),
984 simMarket->discountCurve("EUR"), simMarket->fxVol("USDEUR"));
985 QuantLib::ext::shared_ptr<AnalyticEuropeanEngine> fxOptionEngine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(simGbm);
986 fxOptionSim->setPricingEngine(fxOptionEngine);
987
988 // collect discounted epe
989 Real fxfwd_epe = 0.0, fxoption_epe = 0.0;
990 cpu_timer timer;
991
992 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
993 for (Size i = 0; i < samples; i++) {
994 for (Date date : grid->dates()) {
995 simMarket->update(date);
996 // we do not use the valuation engine, so in case updates are disabled we need to
997 // take care of the instrument update ourselves
998 fxfwd->update();
999 fxOptionSim->update();
1000 Real numeraire = simMarket->numeraire();
1001 if (date == grid->dates().back()) {
1002 fxfwd_epe += std::max(fxfwd->NPV(), 0.0) / numeraire; // NPV is in EUR already by engine construction
1003 fxoption_epe += fxOptionSim->NPV() * 1.3 / numeraire;
1004 }
1005 }
1006 }
1007 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w"));
1008
1009 // compute summary statistics for swap
1010 Real tol = 1.5E-4;
1011 fxfwd_epe /= samples;
1012 fxoption_epe /= samples;
1013 BOOST_TEST_MESSAGE("FxForward discounted epe = " << fxfwd_epe << " FxOption discounted epe = " << fxoption_epe
1014 << " FxOption npv = " << refNpv << " difference fwd/ref is "
1015 << (fxfwd_epe - refNpv) << " difference fxoption/ref is "
1016 << (fxoption_epe - refNpv));
1017 BOOST_CHECK_MESSAGE(fabs(fxfwd_epe - refNpv) < tol,
1018 "discounted FxForward epe (" << fxfwd_epe << ") inconsistent to analytical FxOption premium ("
1019 << refNpv << "), tolerance is " << tol);
1020 BOOST_CHECK_MESSAGE(fabs(fxoption_epe - refNpv) < tol,
1021 "discounted FxOption epe (" << fxoption_epe << ") inconsistent to analytical FxOption premium ("
1022 << refNpv << "), tolerance is " << tol);
1023}
1024
1025BOOST_AUTO_TEST_CASE(testFxForwardExposureZeroIrVol) {
1026 BOOST_TEST_MESSAGE("Testing EUR-USD FX Forward exposure (zero IR vol)");
1028
1029 TestData d;
1030
1031 // Simulation date grid
1032 Date today = d.referenceDate;
1033 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 4 * Years, 5 * Years};
1034 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
1035
1036 // Model
1037 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
1038
1039 // set ir vols to zero
1040 for (Size j = 0; j < 3; ++j) {
1041 for (Size i = 0; i < model->irlgm1f(j)->parameter(0)->size(); ++i) {
1042 model->irlgm1f(j)->parameter(0)->setParam(i, 0.0);
1043 }
1044 }
1045 model->update();
1046
1047 // Simulation market parameters
1048 BOOST_TEST_MESSAGE("set up sim market parameters");
1049 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
1050 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
1051 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
1052 30 * Years, 40 * Years, 50 * Years});
1053 simMarketConfig->setSimulateFXVols(false);
1054 simMarketConfig->setSimulateEquityVols(false);
1055
1056 simMarketConfig->baseCcy() = "EUR";
1057 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
1058 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
1059 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
1060 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
1061 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
1062 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
1063
1064 BOOST_TEST_MESSAGE("set up scenario generator builder");
1065 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
1066 sgd->sequenceType() = SobolBrownianBridge;
1067 sgd->seed() = 42;
1068 sgd->setGrid(grid);
1069
1070 ScenarioGeneratorBuilder sgb(sgd);
1071 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
1072 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
1073 // QuantLib::ext::shared_ptr<DateGrid> grid = sb.dateGrid();
1074
1075 BOOST_TEST_MESSAGE("set up scenario sim market");
1076 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
1077 simMarket->scenarioGenerator() = sg;
1078
1079 Size samples = 10000;
1080
1081 // fx forward for expsoure generation (otm) and engine
1082 Date maturity = grid->dates().back() + 1; // make sure the option is live on last grid date
1083 QuantLib::ext::shared_ptr<FxForward> fxfwd =
1084 QuantLib::ext::make_shared<FxForward>(1.0, EURCurrency(), 1.3, USDCurrency(), maturity, false);
1085 QuantLib::ext::shared_ptr<PricingEngine> fxFwdEngine =
1086 QuantLib::ext::make_shared<DiscountingFxForwardEngine>(EURCurrency(), simMarket->discountCurve("EUR"), USDCurrency(),
1087 simMarket->discountCurve("USD"), simMarket->fxRate("USDEUR"));
1088 fxfwd->setPricingEngine(fxFwdEngine);
1089
1090 // fx (forward) options as reference
1091 // note that we set the IR vols to zero, so that we can
1092 // use a simple adjustment of strike an notional
1093 std::vector<Real> refNpv;
1094 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> modelEngine =
1095 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(model, 0);
1096 for (Size i = 0; i < grid->dates().size(); ++i) {
1097 // amend strike and nominal for forward option pricing
1098 Date expiry = grid->dates()[i];
1099 Real domFwdDf =
1100 simMarket->discountCurve("EUR")->discount(maturity) / simMarket->discountCurve("EUR")->discount(expiry);
1101 Real forFwdDf =
1102 simMarket->discountCurve("USD")->discount(maturity) / simMarket->discountCurve("USD")->discount(expiry);
1103 Real strike = 1.0 / 1.3 * domFwdDf / forFwdDf;
1104 Real nominal = 1.3 * forFwdDf;
1105 QuantLib::ext::shared_ptr<VanillaOption> fxOption = QuantLib::ext::make_shared<VanillaOption>(
1106 QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, strike), QuantLib::ext::make_shared<EuropeanExercise>(expiry));
1107 fxOption->setPricingEngine(modelEngine);
1108 refNpv.push_back(fxOption->NPV() * nominal);
1109 }
1110
1111 // collect discounted epe
1112 std::vector<Real> fxfwd_epe(grid->dates().size(), 0.0);
1113 cpu_timer timer;
1114
1115 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
1116 for (Size i = 0; i < samples; i++) {
1117 Size idx = 0;
1118 for (Date date : grid->dates()) {
1119 simMarket->update(date);
1120 // we do not use the valuation engine, so in case updates are disabled we need to
1121 // take care of the instrument update ourselves
1122 fxfwd->update();
1123 Real numeraire = simMarket->numeraire();
1124 fxfwd_epe[idx++] += std::max(fxfwd->NPV(), 0.0) / numeraire; // NPV is in EUR already by engine construction
1125 }
1126 }
1127 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w"));
1128
1129 // compute summary statistics for swap
1130 Real tol = 3.0E-4;
1131 for (Size i = 0; i < fxfwd_epe.size(); ++i) {
1132 fxfwd_epe[i] /= samples;
1133 BOOST_TEST_MESSAGE("FxForward at t=" << grid->times()[i] << " depe = " << fxfwd_epe[i] << " FxOption npv = "
1134 << refNpv[i] << " difference is " << (fxfwd_epe[i] - refNpv[i]));
1135 BOOST_CHECK_MESSAGE(fabs(fxfwd_epe[i] - refNpv[i]) < tol,
1136 "discounted FxForward epe ("
1137 << fxfwd_epe[i] << ") inconsistent to analytical FxOption premium (" << refNpv[i]
1138 << "), tolerance is " << tol);
1139 }
1140}
1141
1142BOOST_AUTO_TEST_CASE(testCpiSwapExposure) {
1143 BOOST_TEST_MESSAGE("Testing CPI Swap exposure");
1145
1146 TestData d;
1147
1148 // Simulation date grid
1149 Date today = d.referenceDate;
1150 std::vector<Period> tenorGrid = {5 * Years};
1151 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
1152
1153 // Model
1154 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
1155
1156 // set ir vols to zero
1157 for (Size j = 0; j < 3; ++j) {
1158 for (Size i = 0; i < model->irlgm1f(j)->parameter(0)->size(); ++i) {
1159 model->irlgm1f(j)->parameter(0)->setParam(i, 0.0);
1160 }
1161 }
1162 for (Size k = 0; k < 2; ++k) {
1163 for (Size i = 0; i < model->infdk(k)->parameter(0)->size(); ++i) {
1164 model->infdk(k)->parameter(0)->setParam(i, 0.0);
1165 }
1166 }
1167
1168 model->update();
1169
1170 // Simulation market parameters
1171 BOOST_TEST_MESSAGE("set up sim market parameters");
1172 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
1173 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
1174 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
1175 30 * Years, 40 * Years, 50 * Years});
1176 simMarketConfig->setSimulateFXVols(false);
1177 simMarketConfig->setSimulateEquityVols(false);
1178 simMarketConfig->baseCcy() = "EUR";
1179 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
1180 simMarketConfig->setIndices({"EUR-EURIBOR-6M"});
1181 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
1182 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
1183 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
1184 simMarketConfig->setZeroInflationIndices({"UKRPI", "EUHICPXT"});
1185 simMarketConfig->setZeroInflationTenors("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years, 5 * Years,
1186 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years});
1187 simMarketConfig->setCpiIndices({"UKRPI", "EUHICPXT"});
1188
1189 BOOST_TEST_MESSAGE("set up scenario generator builder");
1190 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
1191 sgd->sequenceType() = SobolBrownianBridge;
1192 sgd->seed() = 42;
1193 sgd->setGrid(grid);
1194
1195 ScenarioGeneratorBuilder sgb(sgd);
1196 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
1197 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
1198
1199 BOOST_TEST_MESSAGE("set up scenario sim market");
1200 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
1201 simMarket->scenarioGenerator() = sg;
1202
1203 Size samples = 5000;
1204
1205 Date maturity = grid->dates().back() + 1; // make sure the option is live on last grid date
1206
1207 Handle<ZeroInflationIndex> infIndex = simMarket->zeroInflationIndex("EUHICPXT");
1208 Real baseCPI = infIndex->fixing(infIndex->zeroInflationTermStructure()->baseDate());
1209
1210 Schedule cpiSchedule(vector<Date>{maturity});
1211 Leg cpiLeg = QuantLib::CPILeg(cpiSchedule, infIndex.currentLink(), baseCPI, 2 * Months)
1212 .withFixedRates(1)
1213 .withNotionals(1)
1214 .withObservationInterpolation(CPI::Flat)
1215 .withPaymentDayCounter(ActualActual(ActualActual::ISDA))
1216 .withPaymentAdjustment(Following)
1217 .withSubtractInflationNominal(true);
1218
1219 QuantLib::ext::shared_ptr<Portfolio> portfolio(new Portfolio());
1220
1221 QuantLib::ext::shared_ptr<QuantLib::Swap> cpiSwap =
1222 QuantLib::ext::make_shared<QuantLib::Swap>(vector<Leg>{cpiLeg}, vector<bool>{false});
1223 auto dscEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(simMarket->discountCurve("EUR"));
1224 cpiSwap->setPricingEngine(dscEngine);
1225
1226 // cpi floor options as reference
1227 // note that we set the IR vols to zero, so that we can
1228 // use a simple adjustment of strike an notional
1229 QuantLib::ext::shared_ptr<AnalyticDkCpiCapFloorEngine> modelEngine =
1230 QuantLib::ext::make_shared<AnalyticDkCpiCapFloorEngine>(model, 1, baseCPI);
1231
1232 QuantLib::ext::shared_ptr<CPICapFloor> cap = QuantLib::ext::make_shared<CPICapFloor>(
1233 Option::Type::Call, 1.0, today, baseCPI, maturity, infIndex->fixingCalendar(), ModifiedFollowing,
1234 infIndex->fixingCalendar(), ModifiedFollowing, 0.0, infIndex, 2 * Months, CPI::Flat);
1235 cap->setPricingEngine(modelEngine);
1236 Real capNpv = cap->NPV();
1237
1238 // collect discounted epe
1239 Real cpiSwap_epe = 0.0;
1240 cpu_timer timer;
1241 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
1242 for (Size i = 0; i < samples; i++) {
1243 simMarket->update(grid->dates().back());
1244 // we do not use the valuation engine, so in case updates are disabled we need to
1245 // take care of the instrument update ourselves
1246 cpiSwap->update();
1247 Real numeraire = simMarket->numeraire();
1248 cpiSwap_epe += std::max(cpiSwap->NPV(), 0.0) / numeraire;
1249
1250 simMarket->fixingManager()->reset();
1251 }
1252 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w"));
1253
1254 // compute summary statistics for swap
1255 Real tol = 3.0E-4;
1256 cpiSwap_epe /= samples;
1257 BOOST_TEST_MESSAGE("CPI Swap at t=" << grid->dates().back() << " epe = " << cpiSwap_epe
1258 << " CPI Cap epe = " << capNpv << " difference is " << (cpiSwap_epe - capNpv));
1259 BOOST_CHECK_MESSAGE(fabs(cpiSwap_epe - capNpv) < tol,
1260 "discounted CPI Swap epe (" << cpiSwap_epe << ") inconsistent to analytical CPI Cap premium ("
1261 << capNpv << "), tolerance is " << tol);
1262}
1263
1264BOOST_AUTO_TEST_SUITE_END()
1265
1266BOOST_AUTO_TEST_SUITE_END()
const Sample< MultiPath > & next() const override
Data types stored in the scenario class.
Definition: scenario.hpp:48
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.
Factory class for building simple scenario objects.
void addCorrelation(const std::string &factor1, const std::string &factor2, QuantLib::Real correlation)
const std::map< CorrelationKey, QuantLib::Handle< QuantLib::Quote > > & correlations()
const QuantLib::TimeGrid & timeGrid() const
const std::vector< QuantLib::Date > & dates() const
OREAnalytics Top level fixture.
Simple flat market setup to be used in the test suite.
Definition: testmarket.hpp:64
Scenario generation using cross asset model paths.
Scenario generation using LGM paths.
Time maturity
Date referenceDate
Sobol
SobolBrownianBridge
RandomVariable pow(RandomVariable x, const RandomVariable &y)
CalibrationType
Size size(const ValueType &v)
void setConventions()
Fixture that can be used at top level of OREAnalytics test suites.
BOOST_AUTO_TEST_CASE(testLgmMersenneTwister)
void test_crossasset(bool sobol, bool antithetic, bool brownianBridge)
void test_lgm(bool sobol, bool antithetic, bool brownianBridge)
Build a scenariogenerator.
A Market class that can be updated by Scenarios.
Simple scenario class.
factory classes for simple scenarios