19#include <boost/test/unit_test.hpp>
35#include <oret/toplevelfixture.hpp>
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>
69#include <boost/timer/timer.hpp>
73using namespace boost::unit_test_framework;
77using boost::timer::cpu_timer;
78using boost::timer::default_places;
84 QuantLib::ext::shared_ptr<data::Conventions> conventions(
new data::Conventions());
86 QuantLib::ext::shared_ptr<data::Convention> swapIndexConv(
88 conventions->add(swapIndexConv);
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);
94 InstrumentConventions::instance().setConventions(conventions);
102 market = QuantLib::ext::make_shared<TestMarket>(referenceDate);
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 = {};
114 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
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));
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));
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));
135 vector<string> optionExpiries = {
"1Y",
"2Y",
"3Y",
"5Y",
"7Y",
"10Y"};
136 vector<string> optionStrikes(optionExpiries.size(),
"ATMF");
137 vector<Time> sigmaTimes = {};
139 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
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));
145 sigmaValues = {0.15};
146 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>(
"GBP",
"EUR", calibrationType,
true, ParamType::Piecewise,
147 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
149 std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
151 vector<QuantLib::ext::shared_ptr<InflationModelData>> infConfigs;
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;
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))
163 QuantLib::ext::make_shared<CpiCapFloor>(CapFloor::Floor, 5 * Years, QuantLib::ext::make_shared<AbsoluteStrike>(0.0))
168 ParamType::Piecewise, { 1.0 }, { 0.5, 0.5 });
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));
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)));
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));
197 ccLgm = *modelBuilder.model();
199 lgm = QuantLib::ext::make_shared<QuantExt::LGM>(ccLgm->irlgm1f(0));
202 SavedSettings backup;
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;
214void
test_lgm(
bool sobol,
bool antithetic,
bool brownianBridge) {
216 BOOST_TEST_MESSAGE(
"call test_lgm with sobol=" << sobol <<
" antithetic=" << antithetic <<
" brownianBridge=" << brownianBridge);
220 Date today = d.referenceDate;
221 std::vector<Period> tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
225 QuantLib::ext::shared_ptr<QuantExt::LGM> model = d.lgm;
228 QuantLib::ext::shared_ptr<StochasticProcess1D> stateProcess =
229 QuantLib::ext::dynamic_pointer_cast<StochasticProcess1D>(model->stateProcess());
232 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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);
240 BigNatural seed = 42;
242 QuantLib::ext::shared_ptr<QuantExt::MultiPathGeneratorBase> pathGen;
245 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobolBrownianBridge>(stateProcess, grid.
timeGrid(),
246 SobolBrownianGenerator::Diagonal, seed);
248 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobol>(stateProcess, grid.
timeGrid(), seed);
251 QuantLib::ext::make_shared<MultiPathGeneratorMersenneTwister>(stateProcess, grid.
timeGrid(), seed, antithetic);
257 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory =
258 QuantLib::ext::make_shared<SimpleScenarioFactory>(
true);
261 QuantLib::ext::shared_ptr<LgmScenarioGenerator> scenGen =
262 QuantLib::ext::make_shared<LgmScenarioGenerator>(model, pathGen, scenarioFactory, simMarketConfig, today, grid);
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()) {
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;
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);
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);
297BOOST_AUTO_TEST_SUITE(ScenarioGeneratorTest)
300 BOOST_TEST_MESSAGE(
"Testing LgmScenarioGenerator with MersenneTwister...");
306 BOOST_TEST_MESSAGE(
"Testing LgmScenarioGenerator with MersenneTwister/Antithetic...");
312 BOOST_TEST_MESSAGE(
"Testing LgmScenarioGenerator with LowDiscrepancy...");
318 BOOST_TEST_MESSAGE(
"Testing LgmScenarioGenerator with LowDiscrepancy/BrownianBridge...");
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);
332 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
335 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
338 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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});
350 BigNatural seed = 42;
351 if (
auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(stateProcess)) {
352 tmp->resetCache(grid->timeGrid().size() - 1);
354 QuantLib::ext::shared_ptr<QuantExt::MultiPathGeneratorBase> pathGen;
357 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobolBrownianBridge>(stateProcess, grid->timeGrid(),
358 SobolBrownianGenerator::Diagonal, seed);
360 pathGen = QuantLib::ext::make_shared<MultiPathGeneratorSobol>(stateProcess, grid->timeGrid(), seed);
363 QuantLib::ext::make_shared<MultiPathGeneratorMersenneTwister>(stateProcess, grid->timeGrid(), seed, antithetic);
372 QuantLib::ext::shared_ptr<CrossAssetModelScenarioGenerator> scenGen = QuantLib::ext::make_shared<CrossAssetModelScenarioGenerator>(
373 model, pathGen, scenarioFactory, simMarketConfig, today, grid, d.market);
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;
380 for (Size i = 0; i < samples; i++) {
381 for (Date d : grid->dates()) {
382 QuantLib::ext::shared_ptr<Scenario> scenario = scenGen->next(d);
384 if (d == grid->dates().back()) {
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");
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;
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);
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);
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);
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"));
461 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator with MersenneTwister...");
467 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator with MersenneTwister/Antithetic...");
473 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator with LowDiscrepancy...");
479 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator with LowDiscrepancy/BrownianBridge...");
485 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator via SimMarket (Martingale tests)...");
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);
496 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
499 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
502 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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);
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"});
519 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
521 sgd->sequenceType() =
Sobol;
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);
529 BOOST_TEST_MESSAGE(
"set up scenario sim market");
530 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
531 simMarket->scenarioGenerator() = sg;
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;
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);
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()) {
555 simMarket->update(d);
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);
565 Real usdDiscount = simMarket->discountCurve(
"USD")->discount(1.0 * horizon);
568 simMarket->iborIndex(
"EUR-EURIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
570 simMarket->iborIndex(
"GBP-LIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
573 simMarket->iborIndex(
"USD-LIBOR-3M")->forwardingTermStructure()->discount(1.0 * horizon);
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;
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);
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);
628 BOOST_TEST_MESSAGE(
"Testing CrossAssetScenarioGenerator via SimMarket (direct test against model)...");
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);
638 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
641 QuantLib::ext::shared_ptr<StochasticProcess> stateProcess = model->stateProcess();
644 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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);
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"});
661 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
663 sgd->sequenceType() =
Sobol;
664 sgd->directionIntegers() = SobolRsg::JoeKuoD7;
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);
673 BOOST_TEST_MESSAGE(
"set up scenario sim market");
674 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
675 simMarket->scenarioGenerator() = sg;
678 if (
auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(stateProcess)) {
679 tmp->resetCache(grid->timeGrid().size() - 1);
683 Size samples = 10000;
684 double horizon = 10.0;
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)));
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();
703 for (Date date : grid->dates()) {
705 simMarket->update(date);
707 updateTime += timer.elapsed().wall * 1e-9;
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);
716 Real usdDiscount = simMarket->discountCurve(
"USD")->discount(1.0 * horizon);
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);
721 Real usdIndex = simMarket->iborIndex(
"USD-LIBOR-3M")->forwardingTermStructure()->discount(1.0 * horizon);
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);
765 BOOST_TEST_MESSAGE(
"Simulation time " << timer.format(default_places,
"%w") <<
", update time " << updateTime);
769 BOOST_TEST_MESSAGE(
"Testing EUR and USD vanilla swap exposure profiles generated with CrossAssetScenarioGenerator");
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);
782 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
783 model->irlgm1f(0)->shift() = 20.0;
788 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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);
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"});
805 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
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);
816 BOOST_TEST_MESSAGE(
"set up scenario sim market");
817 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
818 simMarket->scenarioGenerator() = sg;
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);
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());
847 swaptions_eur.push_back(0.0);
848 swaptions_usd.push_back(0.0);
851 std::vector<Real> swap_eur_epe(grid->dates().size()), swap_usd_epe(grid->dates().size());
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++) {
859 for (Date date : grid->dates()) {
861 simMarket->update(date);
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);
878 updateTime += timer.elapsed().wall * 1e-9;
879 Real numeraire = simMarket->numeraire();
880 Real usdeurFX = simMarket->fxRate(
"USDEUR")->value();
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;
887 BOOST_TEST_MESSAGE(
"Simulation time " << timer.format(default_places,
"%w") <<
", update time " << updateTime);
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;
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);
910 BOOST_TEST_MESSAGE(
"Testing EUR-USD FX Forward and FX Vanilla Option exposure");
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);
921 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
924 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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});
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"});
944 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
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);
955 BOOST_TEST_MESSAGE(
"set up scenario sim market");
956 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
957 simMarket->scenarioGenerator() = sg;
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);
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;
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);
989 Real fxfwd_epe = 0.0, fxoption_epe = 0.0;
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);
999 fxOptionSim->update();
1000 Real numeraire = simMarket->numeraire();
1001 if (date == grid->dates().back()) {
1002 fxfwd_epe += std::max(fxfwd->NPV(), 0.0) / numeraire;
1003 fxoption_epe += fxOptionSim->NPV() * 1.3 / numeraire;
1007 BOOST_TEST_MESSAGE(
"Simulation time " << timer.format(default_places,
"%w"));
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);
1026 BOOST_TEST_MESSAGE(
"Testing EUR-USD FX Forward exposure (zero IR vol)");
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);
1037 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
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);
1048 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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);
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"});
1064 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
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);
1075 BOOST_TEST_MESSAGE(
"set up scenario sim market");
1076 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
1077 simMarket->scenarioGenerator() = sg;
1079 Size samples = 10000;
1082 Date
maturity = grid->dates().back() + 1;
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);
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) {
1098 Date expiry = grid->dates()[i];
1100 simMarket->discountCurve(
"EUR")->discount(
maturity) / simMarket->discountCurve(
"EUR")->discount(expiry);
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);
1112 std::vector<Real> fxfwd_epe(grid->dates().size(), 0.0);
1115 BOOST_TEST_MESSAGE(
"running " << samples <<
" samples simulation over " << grid->dates().size() <<
" time steps");
1116 for (Size i = 0; i < samples; i++) {
1118 for (Date date : grid->dates()) {
1119 simMarket->update(date);
1123 Real numeraire = simMarket->numeraire();
1124 fxfwd_epe[idx++] += std::max(fxfwd->NPV(), 0.0) / numeraire;
1127 BOOST_TEST_MESSAGE(
"Simulation time " << timer.format(default_places,
"%w"));
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);
1143 BOOST_TEST_MESSAGE(
"Testing CPI Swap exposure");
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);
1154 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = d.ccLgm;
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);
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);
1171 BOOST_TEST_MESSAGE(
"set up sim market parameters");
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"});
1189 BOOST_TEST_MESSAGE(
"set up scenario generator builder");
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);
1199 BOOST_TEST_MESSAGE(
"set up scenario sim market");
1200 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
1201 simMarket->scenarioGenerator() = sg;
1203 Size samples = 5000;
1205 Date
maturity = grid->dates().back() + 1;
1207 Handle<ZeroInflationIndex> infIndex = simMarket->zeroInflationIndex(
"EUHICPXT");
1208 Real baseCPI = infIndex->fixing(infIndex->zeroInflationTermStructure()->baseDate());
1210 Schedule cpiSchedule(vector<Date>{
maturity});
1211 Leg cpiLeg = QuantLib::CPILeg(cpiSchedule, infIndex.currentLink(), baseCPI, 2 * Months)
1214 .withObservationInterpolation(CPI::Flat)
1215 .withPaymentDayCounter(ActualActual(ActualActual::ISDA))
1216 .withPaymentAdjustment(Following)
1217 .withSubtractInflationNominal(
true);
1219 QuantLib::ext::shared_ptr<Portfolio> portfolio(
new Portfolio());
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);
1229 QuantLib::ext::shared_ptr<AnalyticDkCpiCapFloorEngine> modelEngine =
1230 QuantLib::ext::make_shared<AnalyticDkCpiCapFloorEngine>(model, 1, baseCPI);
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();
1239 Real cpiSwap_epe = 0.0;
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());
1247 Real numeraire = simMarket->numeraire();
1248 cpiSwap_epe += std::max(cpiSwap->NPV(), 0.0) / numeraire;
1250 simMarket->fixingManager()->reset();
1252 BOOST_TEST_MESSAGE(
"Simulation time " << timer.format(default_places,
"%w"));
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);
1264BOOST_AUTO_TEST_SUITE_END()
1266BOOST_AUTO_TEST_SUITE_END()
const Sample< MultiPath > & next() const override
Data types stored in the scenario class.
Build a ScenarioGenerator.
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.
Scenario Generator description.
ScenarioSimMarket description.
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.
Scenario generation using cross asset model paths.
Scenario generation using LGM paths.
RandomVariable pow(RandomVariable x, const RandomVariable &y)
Size size(const ValueType &v)
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.
factory classes for simple scenarios