Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
crossassetmodel.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 "toplevelfixture.hpp"
20#include "utilities.hpp"
21// clang-format off
22#include <boost/test/unit_test.hpp>
23#include <boost/test/data/test_case.hpp>
24// clang-format on
54#include <qle/models/lgm.hpp>
82
83#include <ql/currencies/america.hpp>
84#include <ql/currencies/europe.hpp>
85#include <ql/indexes/ibor/euribor.hpp>
86#include <ql/indexes/ibor/gbplibor.hpp>
87#include <ql/indexes/ibor/usdlibor.hpp>
88#include <ql/indexes/inflation/euhicp.hpp>
89#include <ql/indexes/inflation/ukrpi.hpp>
90#include <ql/instruments/cpicapfloor.hpp>
91#include <ql/instruments/vanillaoption.hpp>
92#include <ql/math/optimization/levenbergmarquardt.hpp>
93#include <ql/math/randomnumbers/rngtraits.hpp>
94#include <ql/math/statistics/incrementalstatistics.hpp>
95#include <ql/methods/montecarlo/multipathgenerator.hpp>
96#include <ql/methods/montecarlo/pathgenerator.hpp>
97#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
98#include <ql/models/shortrate/onefactormodels/gsr.hpp>
99#include <ql/pricingengines/swaption/gaussian1dswaptionengine.hpp>
100#include <ql/pricingengines/credit/midpointcdsengine.hpp>
101#include <ql/quotes/simplequote.hpp>
102#include <ql/termstructures/credit/flathazardrate.hpp>
103#include <ql/termstructures/inflation/interpolatedzeroinflationcurve.hpp>
104#include <ql/termstructures/inflationtermstructure.hpp>
105#include <ql/termstructures/yield/flatforward.hpp>
106#include <ql/time/calendars/target.hpp>
107#include <ql/time/daycounters/actual360.hpp>
108#include <ql/time/daycounters/thirty360.hpp>
109
110#include <boost/make_shared.hpp>
111// fix for boost 1.64, see https://lists.boost.org/Archives/boost/2016/11/231756.php
112#if BOOST_VERSION >= 106400
113#include <boost/serialization/array_wrapper.hpp>
114#endif
115#include <boost/accumulators/accumulators.hpp>
116#include <boost/accumulators/statistics/covariance.hpp>
117#include <boost/accumulators/statistics/error_of_mean.hpp>
118#include <boost/accumulators/statistics/mean.hpp>
119#include <boost/accumulators/statistics/stats.hpp>
120#include <boost/accumulators/statistics/variates/covariate.hpp>
121#include <boost/make_shared.hpp>
122
123using namespace QuantLib;
124using namespace QuantExt;
125
126using boost::unit_test_framework::test_suite;
127using namespace boost::accumulators;
128namespace bdata = boost::unit_test::data;
129using std::vector;
130
131namespace {
132
133struct BermudanTestData {
134 BermudanTestData()
135 : evalDate(12, January, 2015), yts(QuantLib::ext::make_shared<FlatForward>(evalDate, 0.02, Actual365Fixed())),
136 euribor6m(QuantLib::ext::make_shared<Euribor>(6 * Months, yts)), effectiveDate(TARGET().advance(evalDate, 2 * Days)),
137 startDate(TARGET().advance(effectiveDate, 1 * Years)), maturityDate(TARGET().advance(startDate, 9 * Years)),
138 fixedSchedule(startDate, maturityDate, 1 * Years, TARGET(), ModifiedFollowing, ModifiedFollowing,
139 DateGeneration::Forward, false),
140 floatingSchedule(startDate, maturityDate, 6 * Months, TARGET(), ModifiedFollowing, ModifiedFollowing,
141 DateGeneration::Forward, false),
142 underlying(
143 QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap(VanillaSwap::Payer, 1.0, fixedSchedule, 0.02, Thirty360(Thirty360::BondBasis),
144 floatingSchedule, euribor6m, 0.0, Actual360()))),
145 reversion(0.03) {
146 Settings::instance().evaluationDate() = evalDate;
147 for (Size i = 0; i < 9; ++i) {
148 exerciseDates.push_back(TARGET().advance(fixedSchedule[i], -2 * Days));
149 }
150 exercise = QuantLib::ext::make_shared<BermudanExercise>(exerciseDates, false);
151
152 swaption = QuantLib::ext::make_shared<Swaption>(underlying, exercise);
153 stepDates = std::vector<Date>(exerciseDates.begin(), exerciseDates.end() - 1);
154 sigmas = std::vector<Real>(stepDates.size() + 1);
155 for (Size i = 0; i < sigmas.size(); ++i) {
156 sigmas[i] = 0.0050 + (0.0080 - 0.0050) * std::exp(-0.2 * static_cast<double>(i));
157 }
158 stepTimes_a = Array(stepDates.size());
159 for (Size i = 0; i < stepDates.size(); ++i) {
160 stepTimes_a[i] = yts->timeFromReference(stepDates[i]);
161 }
162 sigmas_a = Array(sigmas.begin(), sigmas.end());
163 kappas_a = Array(sigmas_a.size(), reversion);
164 }
165 SavedSettings backup;
166 Date evalDate;
167 Handle<YieldTermStructure> yts;
168 QuantLib::ext::shared_ptr<IborIndex> euribor6m;
169 Date effectiveDate, startDate, maturityDate;
170 Schedule fixedSchedule, floatingSchedule;
171 QuantLib::ext::shared_ptr<VanillaSwap> underlying;
172 std::vector<Date> exerciseDates, stepDates;
173 std::vector<Real> sigmas;
174 QuantLib::ext::shared_ptr<Exercise> exercise;
175 QuantLib::ext::shared_ptr<Swaption> swaption;
176 Array stepTimes_a, sigmas_a, kappas_a;
177 Real reversion;
178}; // BermudanTestData
179
180} // anonymous namespace
181
182BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
183
184BOOST_AUTO_TEST_SUITE(CrossAssetModelTest)
185
186BOOST_AUTO_TEST_CASE(testBermudanLgm1fGsr) {
187
188 BOOST_TEST_MESSAGE("Testing consistency of Bermudan swaption pricing in "
189 "LGM 1F and GSR models...");
190
191 BermudanTestData d;
192
193 // we use the Hull White adaptor for the LGM parametrization
194 // which should lead to equal Bermudan swaption prices
195 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
196 EURCurrency(), d.yts, d.stepTimes_a, d.sigmas_a, d.stepTimes_a, d.kappas_a);
197
198 // fix any T forward measure
199 QuantLib::ext::shared_ptr<Gsr> gsr = QuantLib::ext::make_shared<Gsr>(d.yts, d.stepDates, d.sigmas, d.reversion, 50.0);
200
201 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm_p);
202
203 QuantLib::ext::shared_ptr<Gaussian1dModel> lgm_g1d = QuantLib::ext::make_shared<Gaussian1dCrossAssetAdaptor>(lgm);
204
205 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineGsr =
206 QuantLib::ext::make_shared<Gaussian1dSwaptionEngine>(gsr, 64, 7.0, true, false);
207
208 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineLgm =
209 QuantLib::ext::make_shared<Gaussian1dSwaptionEngine>(lgm_g1d, 64, 7.0, true, false);
210
211 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineLgm2 =
212 QuantLib::ext::make_shared<NumericLgmSwaptionEngine>(lgm, 7.0, 16, 7.0, 32);
213
214 d.swaption->setPricingEngine(swaptionEngineGsr);
215 Real npvGsr = d.swaption->NPV();
216 d.swaption->setPricingEngine(swaptionEngineLgm);
217 Real npvLgm = d.swaption->NPV();
218 d.swaption->setPricingEngine(swaptionEngineLgm2);
219 Real npvLgm2 = d.swaption->NPV();
220
221 Real tol = 0.2E-4; // basis point tolerance
222
223 if (std::fabs(npvGsr - npvLgm) > tol)
224 BOOST_ERROR("Failed to verify consistency of Bermudan swaption price "
225 "in IrLgm1f / Gaussian1d adaptor engine ("
226 << npvLgm << ") and Gsr (" << npvGsr << ") models, tolerance is " << tol);
227
228 if (std::fabs(npvGsr - npvLgm2) > tol)
229 BOOST_ERROR("Failed to verify consistency of Bermudan swaption price "
230 "in IrLgm1f / Numeric LGM engine ("
231 << npvLgm2 << ") and Gsr (" << npvGsr << ") models, tolerance is " << tol);
232}
233
234BOOST_AUTO_TEST_CASE(testBermudanLgmInvariances) {
235
236 BOOST_TEST_MESSAGE("Testing LGM model invariances for Bermudan "
237 "swaption pricing...");
238
239 BermudanTestData d;
240
241 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p2 = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
242 EURCurrency(), d.yts, d.stepTimes_a, d.sigmas_a, d.stepTimes_a, d.kappas_a);
243
244 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm2 = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm_p2);
245
246 QuantLib::ext::shared_ptr<Gaussian1dModel> lgm_g1d2 = QuantLib::ext::make_shared<Gaussian1dCrossAssetAdaptor>(lgm2);
247
248 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineLgm2 =
249 QuantLib::ext::make_shared<Gaussian1dSwaptionEngine>(lgm_g1d2, 64, 7.0, true, false);
250
251 d.swaption->setPricingEngine(swaptionEngineLgm2);
252 Real npvLgm = d.swaption->NPV();
253
254 lgm_p2->shift() = -5.0;
255 lgm_p2->scaling() = 3.0;
256
257 // parametrizations are not observed, so we have to call update ourselves
258 lgm2->update();
259
260 Real npvLgm2 = d.swaption->NPV();
261
262 Real tol = 1.0E-5;
263
264 if (std::fabs(npvLgm - npvLgm2) > tol)
265 BOOST_ERROR("Failed to verify consistency of Bermudan swaption price "
266 "under LGM model invariances, difference is "
267 << (npvLgm - npvLgm2));
268
269} // testBermudanLgm1fGsr
270
271BOOST_AUTO_TEST_CASE(testNonstandardBermudanSwaption) {
272
273 BOOST_TEST_MESSAGE("Testing numeric LGM swaption engine for non-standard swaption...");
274
275 BermudanTestData d;
276
277 QuantLib::ext::shared_ptr<NonstandardSwaption> ns_swaption = QuantLib::ext::make_shared<NonstandardSwaption>(*d.swaption);
278
279 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
280 EURCurrency(), d.yts, d.stepTimes_a, d.sigmas_a, d.stepTimes_a, d.kappas_a);
281
282 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm_p);
283
284 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<NumericLgmSwaptionEngine>(lgm, 7.0, 16, 7.0, 32);
285 QuantLib::ext::shared_ptr<PricingEngine> ns_engine =
286 QuantLib::ext::make_shared<NumericLgmNonstandardSwaptionEngine>(lgm, 7.0, 16, 7.0, 32);
287
288 d.swaption->setPricingEngine(engine);
289 ns_swaption->setPricingEngine(ns_engine);
290
291 Real npv = d.swaption->NPV();
292 Real ns_npv = d.swaption->NPV();
293
294 Real tol = 1.0E-12;
295 if (std::fabs(npv - ns_npv) >= tol)
296 BOOST_ERROR("Failed to verify consistency of Bermudan swaption price ("
297 << npv << ") and Bermudan nonstandard swaption price (" << ns_npv << "), difference is "
298 << (npv - ns_npv) << ", tolerance is " << tol);
299} // testNonstandardBermudanSwaption
300
301BOOST_AUTO_TEST_CASE(testLgm1fCalibration) {
302
303 BOOST_TEST_MESSAGE("Testing calibration of LGM 1F model (analytic engine) "
304 "against GSR parameters...");
305
306 // for fixed kappa != 0.0 we calibrate sigma via
307 // the Hull White Adaptor
308
309 SavedSettings backup;
310
311 Date evalDate(12, January, 2015);
312 Settings::instance().evaluationDate() = evalDate;
313 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(evalDate, 0.02, Actual365Fixed()));
314 QuantLib::ext::shared_ptr<IborIndex> euribor6m = QuantLib::ext::make_shared<Euribor>(6 * Months, yts);
315
316 // coterminal basket 1y-9y, 2y-8y, ... 9y-1y
317
318 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > basket;
319 Real impliedVols[] = { 0.4, 0.39, 0.38, 0.35, 0.35, 0.34, 0.33, 0.32, 0.31 };
320 std::vector<Date> expiryDates;
321
322 for (Size i = 0; i < 9; ++i) {
323 QuantLib::ext::shared_ptr<BlackCalibrationHelper> helper = QuantLib::ext::make_shared<SwaptionHelper>(
324 (i + 1) * Years, (9 - i) * Years, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(impliedVols[i])), euribor6m,
325 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(), yts);
326 basket.push_back(helper);
327 expiryDates.push_back(
328 QuantLib::ext::static_pointer_cast<SwaptionHelper>(helper)->swaption()->exercise()->dates().back());
329 }
330
331 std::vector<Date> stepDates(expiryDates.begin(), expiryDates.end() - 1);
332
333 Array stepTimes_a(stepDates.size());
334 for (Size i = 0; i < stepDates.size(); ++i) {
335 stepTimes_a[i] = yts->timeFromReference(stepDates[i]);
336 }
337
338 Real kappa = 0.05;
339
340 std::vector<Real> gsrInitialSigmas(stepDates.size() + 1, 0.0050);
341 std::vector<Real> lgmInitialSigmas2(stepDates.size() + 1, 0.0050);
342
343 Array lgmInitialSigmas2_a(lgmInitialSigmas2.begin(), lgmInitialSigmas2.end());
344 Array kappas_a(lgmInitialSigmas2_a.size(), kappa);
345
346 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
347 EURCurrency(), yts, stepTimes_a, lgmInitialSigmas2_a, stepTimes_a, kappas_a);
348
349 // fix any T forward measure
350 QuantLib::ext::shared_ptr<Gsr> gsr = QuantLib::ext::make_shared<Gsr>(yts, stepDates, gsrInitialSigmas, kappa, 50.0);
351
352 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm_p);
353
354 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineGsr =
355 QuantLib::ext::make_shared<Gaussian1dSwaptionEngine>(gsr, 64, 7.0, true, false);
356
357 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineLgm = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(lgm);
358
359 // calibrate GSR
360
361 LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
362 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
363
364 for (Size i = 0; i < basket.size(); ++i) {
365 basket[i]->setPricingEngine(swaptionEngineGsr);
366 }
367
368 gsr->calibrateVolatilitiesIterative(basket, lm, ec);
369
370 Array gsrSigmas = gsr->volatility();
371
372 // calibrate LGM
373
374 for (Size i = 0; i < basket.size(); ++i) {
375 basket[i]->setPricingEngine(swaptionEngineLgm);
376 }
377
378 lgm->calibrateVolatilitiesIterative(basket, lm, ec);
379
380 Array lgmSigmas = lgm->parametrization()->parameterValues(0);
381
382 Real tol0 = 1E-8;
383 Real tol = 2E-5;
384
385 for (Size i = 0; i < gsrSigmas.size(); ++i) {
386 // check calibration itself, we should match the market prices
387 // rather exactly (note that this tests the lgm calibration,
388 // not the gsr calibration)
389 if (std::fabs(basket[i]->modelValue() - basket[i]->marketValue()) > tol0)
390 BOOST_ERROR("Failed to calibrate to market swaption #"
391 << i << ", market price is " << basket[i]->marketValue() << " while model price is "
392 << basket[i]->modelValue());
393 // compare calibrated model parameters
394 if (std::fabs(gsrSigmas[i] - lgmSigmas[i]) > tol)
395 BOOST_ERROR("Failed to verify LGM's sigma from Hull White adaptor (#"
396 << i << "), which is " << lgmSigmas[i] << " while GSR's sigma is " << gsrSigmas[i] << ")");
397 }
398
399 // calibrate LGM as component of CrossAssetModel
400
401 // create a second set of parametrization ...
402 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p21 = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
403 USDCurrency(), yts, stepTimes_a, lgmInitialSigmas2_a, stepTimes_a, kappas_a);
404 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p22 = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
405 EURCurrency(), yts, stepTimes_a, lgmInitialSigmas2_a, stepTimes_a, kappas_a);
406
407 // ... and a fx parametrization ...
408 Array notimes_a(0);
409 Array sigma_a(1, 0.10);
410 QuantLib::ext::shared_ptr<FxBsParametrization> fx_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
411 EURCurrency(), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.00)), notimes_a, sigma_a);
412
413 // ... and set up an crossasset model with USD as domestic currency ...
414 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
415 parametrizations.push_back(lgm_p21);
416 parametrizations.push_back(lgm_p22);
417 parametrizations.push_back(fx_p);
418 Matrix rho(3, 3, 0.0);
419 rho[0][0] = rho[1][1] = rho[2][2] = 1.0;
420 QuantLib::ext::shared_ptr<CrossAssetModel> xmodel =
421 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, rho, SalvagingAlgorithm::None);
422
423 // .. whose EUR component we calibrate as before and compare the
424 // result against the 1d case and as well check that the USD
425 // component was not touched by the calibration.
426
427 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngineLgm2 = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(xmodel, 1);
428
429 for (Size i = 0; i < basket.size(); ++i) {
430 basket[i]->setPricingEngine(swaptionEngineLgm2);
431 }
432
433 xmodel->calibrateIrLgm1fVolatilitiesIterative(1, basket, lm, ec);
434
435 Array lgmSigmas2eur = xmodel->irlgm1f(1)->parameterValues(0);
436 Array lgmSigmas2usd = xmodel->irlgm1f(0)->parameterValues(0);
437
438 for (Size i = 0; i < gsrSigmas.size(); ++i) {
439 // compare calibrated model parameters against 1d calibration before
440 if (!close_enough(lgmSigmas2eur[i], lgmSigmas[i]))
441 BOOST_ERROR("Failed to verify crossasset LGM1F component calibration "
442 "at parameter #"
443 << i << " against 1d calibration, which is " << lgmSigmas2eur[i] << " while 1d calibration was "
444 << lgmSigmas[i] << ")");
445 // compare usd component against start values (since it was not
446 // calibrated, so should not have changed)
447 if (!close_enough(lgmSigmas2usd[i], lgmInitialSigmas2[i]))
448 BOOST_ERROR("Non calibrated crossasset LGM1F component was changed by "
449 "other's component calibration at #"
450 << i << ", the new value is " << lgmSigmas2usd[i] << " while the initial value was "
451 << lgmInitialSigmas2[i]);
452 }
453
454} // testLgm1fCalibration
455
456BOOST_AUTO_TEST_CASE(testCcyLgm3fForeignPayouts) {
457
458 BOOST_TEST_MESSAGE("Testing pricing of foreign payouts under domestic "
459 "measure in Ccy LGM 3F model...");
460
461 SavedSettings backup;
462
463 Date referenceDate(30, July, 2015);
464
465 Settings::instance().evaluationDate() = referenceDate;
466
467 Handle<YieldTermStructure> eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed()));
468
469 Handle<YieldTermStructure> usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed()));
470
471 // use different grids for the EUR and USD models and the FX volatility
472 // process to test the piecewise numerical integration ...
473
474 std::vector<Date> volstepdatesEur, volstepdatesUsd, volstepdatesFx;
475
476 volstepdatesEur.push_back(Date(15, July, 2016));
477 volstepdatesEur.push_back(Date(15, July, 2017));
478 volstepdatesEur.push_back(Date(15, July, 2018));
479 volstepdatesEur.push_back(Date(15, July, 2019));
480 volstepdatesEur.push_back(Date(15, July, 2020));
481
482 volstepdatesUsd.push_back(Date(13, April, 2016));
483 volstepdatesUsd.push_back(Date(13, September, 2016));
484 volstepdatesUsd.push_back(Date(13, April, 2017));
485 volstepdatesUsd.push_back(Date(13, September, 2017));
486 volstepdatesUsd.push_back(Date(13, April, 2018));
487 volstepdatesUsd.push_back(Date(15, July, 2018)); // shared with EUR
488 volstepdatesUsd.push_back(Date(13, April, 2019));
489 volstepdatesUsd.push_back(Date(13, September, 2019));
490
491 volstepdatesFx.push_back(Date(15, July, 2016)); // shared with EUR
492 volstepdatesFx.push_back(Date(15, October, 2016));
493 volstepdatesFx.push_back(Date(15, May, 2017));
494 volstepdatesFx.push_back(Date(13, September, 2017)); // shared with USD
495 volstepdatesFx.push_back(Date(15, July, 2018)); // shared with EUR and USD
496
497 std::vector<Real> eurVols, usdVols, fxVols;
498
499 for (Size i = 0; i < volstepdatesEur.size() + 1; ++i) {
500 eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
501 }
502 for (Size i = 0; i < volstepdatesUsd.size() + 1; ++i) {
503 usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
504 }
505 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
506 fxVols.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
507 }
508
509 Array alphaTimesEur(volstepdatesEur.size()), alphaEur(eurVols.begin(), eurVols.end()), kappaTimesEur(0),
510 kappaEur(1, 0.02);
511 Array alphaTimesUsd(volstepdatesUsd.size()), alphaUsd(usdVols.begin(), usdVols.end()), kappaTimesUsd(0),
512 kappaUsd(1, 0.04);
513 Array fxTimes(volstepdatesFx.size()), fxSigmas(fxVols.begin(), fxVols.end());
514
515 for (Size i = 0; i < alphaTimesEur.size(); ++i) {
516 alphaTimesEur[i] = eurYts->timeFromReference(volstepdatesEur[i]);
517 }
518 for (Size i = 0; i < alphaTimesUsd.size(); ++i) {
519 alphaTimesUsd[i] = eurYts->timeFromReference(volstepdatesUsd[i]);
520 }
521 for (Size i = 0; i < fxTimes.size(); ++i) {
522 fxTimes[i] = eurYts->timeFromReference(volstepdatesFx[i]);
523 }
524
525 QuantLib::ext::shared_ptr<IrLgm1fParametrization> eurLgmParam = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(
526 EURCurrency(), eurYts, alphaTimesEur, alphaEur, kappaTimesEur, kappaEur);
527
528 QuantLib::ext::shared_ptr<IrLgm1fParametrization> usdLgmParam = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(
529 USDCurrency(), usdYts, alphaTimesUsd, alphaUsd, kappaTimesUsd, kappaUsd);
530
531 // USD per EUR (foreign per domestic)
532 Handle<Quote> usdEurSpotToday(QuantLib::ext::make_shared<SimpleQuote>(0.90));
533
534 QuantLib::ext::shared_ptr<FxBsParametrization> fxUsdEurBsParam =
535 QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), usdEurSpotToday, fxTimes, fxSigmas);
536
537 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
538 singleModels.push_back(eurLgmParam);
539 singleModels.push_back(usdLgmParam);
540 singleModels.push_back(fxUsdEurBsParam);
541
542 QuantLib::ext::shared_ptr<CrossAssetModel> ccLgm = QuantLib::ext::make_shared<CrossAssetModel>(singleModels);
543
544 Size eurIdx = ccLgm->ccyIndex(EURCurrency());
545 Size usdIdx = ccLgm->ccyIndex(USDCurrency());
546 Size eurUsdIdx = usdIdx - 1;
547
548 ccLgm->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::IR, usdIdx, -0.2);
549 ccLgm->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, 0.8);
550 ccLgm->setCorrelation(CrossAssetModel::AssetType::IR, usdIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, -0.5);
551
552 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> eurLgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(eurLgmParam);
553 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> usdLgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(usdLgmParam);
554
555 QuantLib::ext::shared_ptr<StochasticProcess> process = ccLgm->stateProcess();
556 QuantLib::ext::shared_ptr<StochasticProcess> usdProcess = usdLgm->stateProcess();
557
558 // path generation
559
560 Size n = 500000; // number of paths
561 Size seed = 121; // seed
562 // maturity of test payoffs
563 Time T = 5.0;
564 // take large steps, but not only one (since we are testing)
565 Size steps = static_cast<Size>(T * 2.0);
566 TimeGrid grid(T, steps);
567 PseudoRandom::rsg_type sg2 = PseudoRandom::make_sequence_generator(steps, seed);
568
569 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
570 tmp->resetCache(grid.size() - 1);
571 }
572 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(usdProcess)) {
573 tmp->resetCache(grid.size() - 1);
574 }
575 MultiPathGeneratorMersenneTwister pg(process, grid, seed, false);
576 PathGenerator<PseudoRandom::rsg_type> pg2(usdProcess, grid, sg2, false);
577
578 // test
579 // 1 deterministic USD cashflow under EUR numeraire vs. price on USD curve
580 // 2 zero bond option USD under EUR numeraire vs. USD numeraire
581 // 3 fx option USD-EUR under EUR numeraire vs. analytical price
582
583 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > stat1, stat2a, stat2b, stat3;
584
585 // same for paths2 since shared time grid
586 for (Size j = 0; j < n; ++j) {
587 Sample<MultiPath> path = pg.next();
588 Sample<Path> path2 = pg2.next();
589 Size l = path.value[0].length() - 1;
590 Real fx = std::exp(path.value[2][l]);
591 Real zeur = path.value[0][l];
592 Real zusd = path.value[1][l];
593 Real zusd2 = path2.value[l];
594
595 // 1 USD paid at T deflated with EUR numeraire
596 stat1(1.0 * fx / eurLgm->numeraire(T, zeur));
597
598 // 2 USD zero bond option at T on P(T,T+10) strike 0.5 ...
599 // ... under EUR numeraire ...
600 Real zbOpt = std::max(usdLgm->discountBond(T, T + 10.0, zusd) - 0.5, 0.0);
601 stat2a(zbOpt * fx / eurLgm->numeraire(T, zeur));
602 // ... and under USD numeraire ...
603 Real zbOpt2 = std::max(usdLgm->discountBond(T, T + 10.0, zusd2) - 0.5, 0.0);
604 stat2b(zbOpt2 / usdLgm->numeraire(T, zusd2));
605
606 // 3 USD-EUR fx option @0.9
607 stat3(std::max(fx - 0.9, 0.0) / eurLgm->numeraire(T, zeur));
608 }
609
610 QuantLib::ext::shared_ptr<VanillaOption> fxOption =
611 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, 0.9),
612 QuantLib::ext::make_shared<EuropeanExercise>(referenceDate + 5 * 365));
613
614 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> ccLgmFxOptionEngine =
615 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(ccLgm, 0);
616
617 ccLgmFxOptionEngine->cache();
618
619 fxOption->setPricingEngine(ccLgmFxOptionEngine);
620
621 Real npv1 = mean(stat1);
622 Real error1 = error_of<tag::mean>(stat1);
623 Real expected1 = usdYts->discount(5.0) * usdEurSpotToday->value();
624 Real npv2a = mean(stat2a);
625 Real error2a = error_of<tag::mean>(stat2a);
626 Real npv2b = mean(stat2b) * usdEurSpotToday->value();
627 ;
628 Real error2b = error_of<tag::mean>(stat2b) * usdEurSpotToday->value();
629 Real npv3 = mean(stat3);
630 Real error3 = error_of<tag::mean>(stat3);
631
632 // accept this relative difference in error estimates
633 Real tolError = 0.2;
634 // accept tolErrEst*errorEstimate as absolute difference
635 Real tolErrEst = 1.0;
636
637 if (std::fabs((error1 - 4E-4) / 4E-4) > tolError)
638 BOOST_ERROR("error estimate deterministic "
639 "cashflow pricing can not be "
640 "reproduced, is "
641 << error1 << ", expected 4E-4, relative tolerance " << tolError);
642 if (std::fabs((error2a - 1E-4) / 1E-4) > tolError)
643 BOOST_ERROR("error estimate zero bond "
644 "option pricing (foreign measure) can "
645 "not be reproduced, is "
646 << error2a << ", expected 1E-4, relative tolerance " << tolError);
647 if (std::fabs((error2b - 7E-5) / 7E-5) > tolError)
648 BOOST_ERROR("error estimate zero bond "
649 "option pricing (domestic measure) can "
650 "not be reproduced, is "
651 << error2b << ", expected 7E-5, relative tolerance " << tolError);
652 if (std::fabs((error3 - 2.7E-4) / 2.7E-4) > tolError)
653 BOOST_ERROR("error estimate fx option pricing can not be reproduced, is "
654 << error3 << ", expected 2.7E-4, relative tolerance " << tolError);
655
656 if (std::fabs(npv1 - expected1) > tolErrEst * error1)
657 BOOST_ERROR("can no reproduce deterministic cashflow pricing, is "
658 << npv1 << ", expected " << expected1 << ", tolerance " << tolErrEst << "*" << error1);
659
660 if (std::fabs(npv2a - npv2b) > tolErrEst * std::sqrt(error2a * error2a + error2b * error2b))
661 BOOST_ERROR("can no reproduce zero bond option pricing, domestic "
662 "measure result is "
663 << npv2a << ", foreign measure result is " << npv2b << ", tolerance " << tolErrEst << "*"
664 << std::sqrt(error2a * error2a + error2b * error2b));
665
666 if (std::fabs(npv3 - fxOption->NPV()) > tolErrEst * error3)
667 BOOST_ERROR("can no reproduce fx option pricing, monte carlo result is "
668 << npv3 << ", analytical pricing result is " << fxOption->NPV() << ", tolerance is " << tolErrEst
669 << "*" << error3);
670
671} // testCcyLgm3fForeignPayouts
672
673namespace {
674
675struct Lgm5fTestData {
676 Lgm5fTestData()
677 : referenceDate(30, July, 2015), eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed())),
678 usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed())),
679 gbpYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.04, Actual365Fixed())),
680 fxEurUsd(QuantLib::ext::make_shared<SimpleQuote>(0.90)), fxEurGbp(QuantLib::ext::make_shared<SimpleQuote>(1.35)), c(5, 5) {
681
682 Settings::instance().evaluationDate() = referenceDate;
683 volstepdates.push_back(Date(15, July, 2016));
684 volstepdates.push_back(Date(15, July, 2017));
685 volstepdates.push_back(Date(15, July, 2018));
686 volstepdates.push_back(Date(15, July, 2019));
687 volstepdates.push_back(Date(15, July, 2020));
688
689 volstepdatesFx.push_back(Date(15, July, 2016));
690 volstepdatesFx.push_back(Date(15, October, 2016));
691 volstepdatesFx.push_back(Date(15, May, 2017));
692 volstepdatesFx.push_back(Date(13, September, 2017));
693 volstepdatesFx.push_back(Date(15, July, 2018));
694
695 volsteptimes_a = Array(volstepdates.size());
696 volsteptimesFx_a = Array(volstepdatesFx.size());
697 for (Size i = 0; i < volstepdates.size(); ++i) {
698 volsteptimes_a[i] = eurYts->timeFromReference(volstepdates[i]);
699 }
700 for (Size i = 0; i < volstepdatesFx.size(); ++i) {
701 volsteptimesFx_a[i] = eurYts->timeFromReference(volstepdatesFx[i]);
702 }
703
704 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
705 eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
706 }
707 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
708 usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
709 }
710 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
711 gbpVols.push_back(0.0070 + (0.0095 - 0.0070) * std::exp(-0.3 * static_cast<double>(i)));
712 }
713 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
714 fxSigmasUsd.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
715 }
716 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
717 fxSigmasGbp.push_back(0.10 + (0.15 - 0.10) * std::exp(-0.3 * static_cast<double>(i)));
718 }
719 eurVols_a = Array(eurVols.begin(), eurVols.end());
720 usdVols_a = Array(usdVols.begin(), usdVols.end());
721 gbpVols_a = Array(gbpVols.begin(), gbpVols.end());
722 fxSigmasUsd_a = Array(fxSigmasUsd.begin(), fxSigmasUsd.end());
723 fxSigmasGbp_a = Array(fxSigmasGbp.begin(), fxSigmasGbp.end());
724
725 notimes_a = Array(0);
726 eurKappa_a = Array(1, 0.02);
727 usdKappa_a = Array(1, 0.03);
728 gbpKappa_a = Array(1, 0.04);
729
730 eurLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), eurYts, volsteptimes_a,
731 eurVols_a, notimes_a, eurKappa_a);
732 usdLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(USDCurrency(), usdYts, volsteptimes_a,
733 usdVols_a, notimes_a, usdKappa_a);
734 gbpLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(GBPCurrency(), gbpYts, volsteptimes_a,
735 gbpVols_a, notimes_a, gbpKappa_a);
736
737 fxUsd_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), fxEurUsd, volsteptimesFx_a,
738 fxSigmasUsd_a);
739 fxGbp_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(GBPCurrency(), fxEurGbp, volsteptimesFx_a,
740 fxSigmasGbp_a);
741
742 singleModels.push_back(eurLgm_p);
743 singleModels.push_back(usdLgm_p);
744 singleModels.push_back(gbpLgm_p);
745 singleModels.push_back(fxUsd_p);
746 singleModels.push_back(fxGbp_p);
747
748 // clang-format off
749 // EUR USD GBP FX USD-EUR FX GBP-EUR
750 c[0][0] = 1.0; c[0][1] = 0.6; c[0][2] = 0.3; c[0][3] = 0.2; c[0][4] = 0.3; // EUR
751 c[1][0] = 0.6; c[1][1] = 1.0; c[1][2] = 0.1; c[1][3] = -0.2; c[1][4] = -0.1; // USD
752 c[2][0] = 0.3; c[2][1] = 0.1; c[2][2] = 1.0; c[2][3] = 0.0; c[2][4] = 0.1; // GBP
753 c[3][0] = 0.2; c[3][1] = -0.2; c[3][2] = 0.0; c[3][3] = 1.0; c[3][4] = 0.3; // FX USD-EUR
754 c[4][0] = 0.3; c[4][1] = -0.1; c[4][2] = 0.1; c[4][3] = 0.3; c[4][4] = 1.0; // FX GBP-EUR
755 // clang-format on
756
757 ccLgmExact = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
758 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
759 ccLgmEuler = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
760 IrModel::Measure::LGM, CrossAssetModel::Discretization::Euler);
761 }
762
763 SavedSettings backup;
764 Date referenceDate;
765 Handle<YieldTermStructure> eurYts, usdYts, gbpYts;
766 std::vector<Date> volstepdates, volstepdatesFx;
767 Array volsteptimes_a, volsteptimesFx_a;
768 std::vector<Real> eurVols, usdVols, gbpVols, fxSigmasUsd, fxSigmasGbp;
769 Handle<Quote> fxEurUsd, fxEurGbp;
770 Array eurVols_a, usdVols_a, gbpVols_a, fxSigmasUsd_a, fxSigmasGbp_a;
771 Array notimes_a, eurKappa_a, usdKappa_a, gbpKappa_a;
772 QuantLib::ext::shared_ptr<IrLgm1fParametrization> eurLgm_p, usdLgm_p, gbpLgm_p;
773 QuantLib::ext::shared_ptr<FxBsParametrization> fxUsd_p, fxGbp_p;
774 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
775 Matrix c;
776 QuantLib::ext::shared_ptr<CrossAssetModel> ccLgmExact, ccLgmEuler;
777}; // LGM5FTestData
778
779struct IrFxCrModelTestData {
780 IrFxCrModelTestData(bool includeCirr = false)
781 : referenceDate(30, July, 2015), N(includeCirr ? 11 : 8),
782 eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed())),
783 usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed())),
784 gbpYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.04, Actual365Fixed())),
785 fxEurUsd(QuantLib::ext::make_shared<SimpleQuote>(0.90)), fxEurGbp(QuantLib::ext::make_shared<SimpleQuote>(1.35)),
786 n1Ts(QuantLib::ext::make_shared<FlatHazardRate>(referenceDate, 0.01, Actual365Fixed())),
787 n2Ts(QuantLib::ext::make_shared<FlatHazardRate>(referenceDate, 0.05, Actual365Fixed())),
788 n3Ts(QuantLib::ext::make_shared<FlatHazardRate>(referenceDate, 0.10, Actual365Fixed())), n1Alpha(0.01), n1Kappa(0.01),
789 n2Alpha(0.015), n2Kappa(0.015), n3Alpha(0.0050), n3Kappa(0.0050),
790 cirppKappa(0.206), cirppTheta(0.04), cirppSigma(sqrt(2 * cirppKappa * cirppTheta) - 1E-10),
791 cirppY0(cirppTheta), c(N, N, 0.0) {
792
793 Settings::instance().evaluationDate() = referenceDate;
794 volstepdates.push_back(Date(15, July, 2016));
795 volstepdates.push_back(Date(15, July, 2017));
796 volstepdates.push_back(Date(15, July, 2018));
797 volstepdates.push_back(Date(15, July, 2019));
798 volstepdates.push_back(Date(15, July, 2020));
799
800 volstepdatesFx.push_back(Date(15, July, 2016));
801 volstepdatesFx.push_back(Date(15, October, 2016));
802 volstepdatesFx.push_back(Date(15, May, 2017));
803 volstepdatesFx.push_back(Date(13, September, 2017));
804 volstepdatesFx.push_back(Date(15, July, 2018));
805
806 volsteptimes_a = Array(volstepdates.size());
807 volsteptimesFx_a = Array(volstepdatesFx.size());
808 for (Size i = 0; i < volstepdates.size(); ++i) {
809 volsteptimes_a[i] = eurYts->timeFromReference(volstepdates[i]);
810 }
811 for (Size i = 0; i < volstepdatesFx.size(); ++i) {
812 volsteptimesFx_a[i] = eurYts->timeFromReference(volstepdatesFx[i]);
813 }
814
815 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
816 eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
817 }
818 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
819 usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
820 }
821 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
822 gbpVols.push_back(0.0070 + (0.0095 - 0.0070) * std::exp(-0.3 * static_cast<double>(i)));
823 }
824 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
825 fxSigmasUsd.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
826 }
827 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
828 fxSigmasGbp.push_back(0.10 + (0.15 - 0.10) * std::exp(-0.3 * static_cast<double>(i)));
829 }
830 eurVols_a = Array(eurVols.begin(), eurVols.end());
831 usdVols_a = Array(usdVols.begin(), usdVols.end());
832 gbpVols_a = Array(gbpVols.begin(), gbpVols.end());
833 fxSigmasUsd_a = Array(fxSigmasUsd.begin(), fxSigmasUsd.end());
834 fxSigmasGbp_a = Array(fxSigmasGbp.begin(), fxSigmasGbp.end());
835
836 notimes_a = Array(0);
837 eurKappa_a = Array(1, 0.02);
838 usdKappa_a = Array(1, 0.03);
839 gbpKappa_a = Array(1, 0.04);
840
841 eurLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), eurYts, volsteptimes_a,
842 eurVols_a, notimes_a, eurKappa_a);
843 usdLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(USDCurrency(), usdYts, volsteptimes_a,
844 usdVols_a, notimes_a, usdKappa_a);
845 gbpLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(GBPCurrency(), gbpYts, volsteptimes_a,
846 gbpVols_a, notimes_a, gbpKappa_a);
847
848 fxUsd_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), fxEurUsd, volsteptimesFx_a,
849 fxSigmasUsd_a);
850 fxGbp_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(GBPCurrency(), fxEurGbp, volsteptimesFx_a,
851 fxSigmasGbp_a);
852
853 // credit LGM
854 n1_p = QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(EURCurrency(), n1Ts, n1Alpha, n1Kappa);
855 n2_p = QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(EURCurrency(), n2Ts, n2Alpha, n2Kappa);
856 n3_p = QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(EURCurrency(), n3Ts, n3Alpha, n3Kappa);
857
858 // credit cir++ (shifted = true), parameters are taken from CrCirppModelTest
859 n1_cirpp = QuantLib::ext::make_shared<CrCirppConstantWithFellerParametrization>(
860 EURCurrency(), n1Ts, cirppKappa, cirppTheta, cirppSigma, cirppY0, true);
861 n2_cirpp = QuantLib::ext::make_shared<CrCirppConstantWithFellerParametrization>(
862 USDCurrency(), n2Ts, cirppKappa, cirppTheta, cirppSigma, cirppY0, true);
863 n3_cirpp = QuantLib::ext::make_shared<CrCirppConstantWithFellerParametrization>(
864 GBPCurrency(), n3Ts, cirppKappa, cirppTheta, cirppSigma, cirppY0, true);
865
866 singleModels.push_back(eurLgm_p);
867 singleModels.push_back(usdLgm_p);
868 singleModels.push_back(gbpLgm_p);
869 singleModels.push_back(fxUsd_p);
870 singleModels.push_back(fxGbp_p);
871 singleModels.push_back(n1_p);
872 singleModels.push_back(n2_p);
873 singleModels.push_back(n3_p);
874 if (includeCirr) {
875 singleModels.push_back(n1_cirpp);
876 singleModels.push_back(n2_cirpp);
877 singleModels.push_back(n3_cirpp);
878 }
879
880 std::vector<std::vector<Real>> tmp;
881 if (includeCirr) {
882 tmp = {
883 // EUR USD GBP FX1 FX2 N1 N2 N3 NC1 NC2 NC3
884 { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // EUR
885 { 0.6, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // USD
886 { 0.3, 0.1, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // GBP
887 { 0.2, 0.2, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX1
888 { 0.3, 0.1, 0.1, 0.3, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX2
889 { 0.8, 0.2, 0.1, 0.4, 0.2, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // N1
890 { 0.6, 0.1, 0.2, 0.2, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0 }, // N2
891 { 0.3, 0.2, 0.1, 0.1, 0.3, 0.4, 0.2, 1.0, 0.0, 0.0, 0.0 }, // N3
892 { 0.0, 0.2, 0.1, 0.4, 0.2, 0.5, 0.3, 0.2, 1.0, 0.0, 0.0 }, // N1 (CIR++)
893 { 0.0, 0.1, 0.2, 0.0, 0.5, 0.4, 0.2, 0.1, 0.4, 1.0, 0.0 }, // N2 (CIR++)
894 { 0.0, 0.2, 0.1, 0.1, 0.0, 0.3, 0.2, 0.2, 0.3, 0.5, 1.0 } // N3 (CIR++)
895 };
896 } else {
897 tmp = {
898 // EUR USD GBP FX1 FX2 N1 N2 N3
899 { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // EUR
900 { 0.6, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // USD
901 { 0.3, 0.1, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // GBP
902 { 0.2, 0.2, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0 }, // FX1
903 { 0.3, 0.1, 0.1, 0.3, 1.0, 0.0, 0.0, 0.0 }, // FX2
904 { 0.8, 0.2, 0.1, 0.4, 0.2, 1.0, 0.0, 0.0 }, // N1
905 { 0.6, 0.1, 0.2, 0.2, 0.5, 0.5, 1.0, 0.0 }, // N2
906 { 0.3, 0.2, 0.1, 0.1, 0.3, 0.4, 0.2, 1.0 } // N3
907 };
908 };
909
910 for (Size i = 0; i < N; ++i) {
911 for (Size j = 0; j <= i; ++j) {
912 c[i][j] = c[j][i] = tmp[i][j];
913 }
914 }
915
916 BOOST_TEST_MESSAGE("input correlation matrix is\n" << c);
917 Matrix ctmp = pseudoSqrt(c, SalvagingAlgorithm::Spectral);
918 Matrix cs = ctmp * transpose(ctmp);
919 // this leads technically to non-zero correlations EUR/CIR++ and FX/CIR++, but
920 // these are small, so that the missing drift adjustment in the state process
921 // evolve method can be ignored
922 BOOST_TEST_MESSAGE("salvaged correlation matrix is\n" << cs);
923 if (includeCirr)
924 modelExact = modelEuler =
925 QuantLib::ext::make_shared<CrossAssetModel>(singleModels, cs, SalvagingAlgorithm::None, IrModel::Measure::LGM,
926 CrossAssetModel::Discretization::Euler);
927 else {
928 modelExact =
929 QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None, IrModel::Measure::LGM,
930 CrossAssetModel::Discretization::Exact);
931 modelEuler =
932 QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None, IrModel::Measure::LGM,
933 CrossAssetModel::Discretization::Euler);
934 }
935 BOOST_TEST_MESSAGE("cam+ model built.");
936 }
937
938 SavedSettings backup;
939 Date referenceDate;
940 Size N;
941 // ir-fx
942 Handle<YieldTermStructure> eurYts, usdYts, gbpYts;
943 std::vector<Date> volstepdates, volstepdatesFx;
944 Array volsteptimes_a, volsteptimesFx_a;
945 std::vector<Real> eurVols, usdVols, gbpVols, fxSigmasUsd, fxSigmasGbp;
946 Handle<Quote> fxEurUsd, fxEurGbp;
947 Array eurVols_a, usdVols_a, gbpVols_a, fxSigmasUsd_a, fxSigmasGbp_a;
948 Array notimes_a, eurKappa_a, usdKappa_a, gbpKappa_a;
949 QuantLib::ext::shared_ptr<IrLgm1fParametrization> eurLgm_p, usdLgm_p, gbpLgm_p;
950 QuantLib::ext::shared_ptr<FxBsParametrization> fxUsd_p, fxGbp_p;
951 // cr
952 Handle<DefaultProbabilityTermStructure> n1Ts, n2Ts, n3Ts;
953 // LGM
954 QuantLib::ext::shared_ptr<CrLgm1fParametrization> n1_p, n2_p, n3_p;
955 Real n1Alpha, n1Kappa, n2Alpha, n2Kappa, n3Alpha, n3Kappa;
956 // CIR++
957 QuantLib::ext::shared_ptr<CrCirppParametrization> n1_cirpp, n2_cirpp, n3_cirpp;
958 Real cirppKappa, cirppTheta, cirppSigma, cirppY0;
959 // model
960 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
961 Matrix c;
962 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact, modelEuler;
963}; // IrFxCrModelTestData
964
965} // anonymous namespace
966
967BOOST_AUTO_TEST_CASE(testLgm5fFxCalibration) {
968
969 BOOST_TEST_MESSAGE("Testing fx calibration in Ccy LGM 5F model...");
970
971 Lgm5fTestData d;
972
973 // we test the 5f model against the 3f model eur-gbp
974 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModelsProjected;
975 singleModelsProjected.push_back(d.eurLgm_p);
976 singleModelsProjected.push_back(d.gbpLgm_p);
977 singleModelsProjected.push_back(d.fxGbp_p);
978
979 Matrix cProjected(3, 3);
980 for (Size i = 0, ii = 0; i < 5; ++i) {
981 if (i != 0 && i != 3) {
982 for (Size j = 0, jj = 0; j < 5; ++j) {
983 if (j != 0 && j != 3)
984 cProjected[ii][jj++] = d.c[i][j];
985 }
986 ++ii;
987 }
988 }
989
990 QuantLib::ext::shared_ptr<CrossAssetModel> ccLgmProjected =
991 QuantLib::ext::make_shared<CrossAssetModel>(singleModelsProjected, cProjected, SalvagingAlgorithm::None);
992
993 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> ccLgmFxOptionEngineUsd =
994 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(d.ccLgmExact, 0);
995
996 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> ccLgmFxOptionEngineGbp =
997 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(d.ccLgmExact, 1);
998
999 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> ccLgmProjectedFxOptionEngineGbp =
1000 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(ccLgmProjected, 0);
1001
1002 ccLgmFxOptionEngineUsd->cache();
1003 ccLgmFxOptionEngineGbp->cache();
1004 ccLgmProjectedFxOptionEngineGbp->cache();
1005
1006 // while the initial fx vol starts at 0.2 for usd and 0.15 for gbp
1007 // we calibrate to helpers with 0.15 and 0.2 target implied vol
1008 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > helpersUsd, helpersGbp;
1009 for (Size i = 0; i <= d.volstepdatesFx.size(); ++i) {
1010 QuantLib::ext::shared_ptr<BlackCalibrationHelper> tmpUsd = QuantLib::ext::make_shared<FxEqOptionHelper>(
1011 i < d.volstepdatesFx.size() ? d.volstepdatesFx[i] : d.volstepdatesFx.back() + 365, 0.90, d.fxEurUsd,
1012 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.15)), d.ccLgmExact->irlgm1f(0)->termStructure(),
1013 d.ccLgmExact->irlgm1f(1)->termStructure());
1014 QuantLib::ext::shared_ptr<BlackCalibrationHelper> tmpGbp = QuantLib::ext::make_shared<FxEqOptionHelper>(
1015 i < d.volstepdatesFx.size() ? d.volstepdatesFx[i] : d.volstepdatesFx.back() + 365, 1.35, d.fxEurGbp,
1016 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.20)), d.ccLgmExact->irlgm1f(0)->termStructure(),
1017 d.ccLgmExact->irlgm1f(2)->termStructure());
1018 tmpUsd->setPricingEngine(ccLgmFxOptionEngineUsd);
1019 tmpGbp->setPricingEngine(ccLgmFxOptionEngineGbp);
1020 helpersUsd.push_back(tmpUsd);
1021 helpersGbp.push_back(tmpGbp);
1022 }
1023
1024 LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
1025 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
1026
1027 // calibrate USD-EUR FX volatility
1028 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::FX, 0, helpersUsd, lm, ec);
1029 // calibrate GBP-EUR FX volatility
1030 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::FX, 1, helpersGbp, lm, ec);
1031
1032 Real tol = 1E-6;
1033 for (Size i = 0; i < helpersUsd.size(); ++i) {
1034 Real market = helpersUsd[i]->marketValue();
1035 Real model = helpersUsd[i]->modelValue();
1036 Real calibratedVol = d.ccLgmExact->fxbs(0)->parameterValues(0)[i];
1037 if (std::fabs(market - model) > tol) {
1038 BOOST_ERROR("calibration for fx option helper #" << i << " (USD) failed, market premium is " << market
1039 << " while model premium is " << model);
1040 }
1041 // the stochastic rates produce some noise, but do not have a huge
1042 // impact on the effective volatility, so we check that they are
1043 // in line with a cached example (note that the analytic fx option
1044 // pricing engine was checked against MC in another test case)
1045 if (std::fabs(calibratedVol - 0.143) > 0.01) {
1046 BOOST_ERROR("calibrated fx volatility #" << i << " (USD) seems off, expected to be 0.143 +- 0.01, but is "
1047 << calibratedVol);
1048 }
1049 }
1050 for (Size i = 0; i < helpersGbp.size(); ++i) {
1051 Real market = helpersGbp[i]->marketValue();
1052 Real model = helpersGbp[i]->modelValue();
1053 Real calibratedVol = d.ccLgmExact->fxbs(1)->parameterValues(0)[i];
1054 if (std::fabs(market - model) > tol)
1055 BOOST_ERROR("calibration for fx option helper #" << i << " (GBP) failed, market premium is " << market
1056 << " while model premium is " << model);
1057 // see above
1058 if (std::fabs(calibratedVol - 0.193) > 0.01)
1059 BOOST_ERROR("calibrated fx volatility #" << i << " (USD) seems off, expected to be 0.193 +- 0.01, but is "
1060 << calibratedVol);
1061 }
1062
1063 // calibrate the projected model
1064
1065 for (Size i = 0; i < helpersGbp.size(); ++i) {
1066 helpersGbp[i]->setPricingEngine(ccLgmProjectedFxOptionEngineGbp);
1067 }
1068
1069 ccLgmProjected->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::FX, 0, helpersGbp, lm, ec);
1070
1071 for (Size i = 0; i < helpersGbp.size(); ++i) {
1072 Real fullModelVol = d.ccLgmExact->fxbs(1)->parameterValues(0)[i];
1073 Real projectedModelVol = ccLgmProjected->fxbs(0)->parameterValues(0)[i];
1074 if (std::fabs(fullModelVol - projectedModelVol) > tol)
1075 BOOST_ERROR("calibrated fx volatility of full model @"
1076 << i << " (" << fullModelVol << ") is inconsistent with that of the projected model ("
1077 << projectedModelVol << ")");
1078 }
1079
1080} // testLgm5fFxCalibration
1081
1082BOOST_AUTO_TEST_CASE(testLgm5fFullCalibration) {
1083
1084 BOOST_TEST_MESSAGE("Testing full calibration of Ccy LGM 5F model...");
1085
1086 Lgm5fTestData d;
1087
1088 // calibration baskets
1089
1090 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > basketEur, basketUsd, basketGbp, basketEurUsd, basketEurGbp;
1091
1092 QuantLib::ext::shared_ptr<IborIndex> euribor6m = QuantLib::ext::make_shared<Euribor>(6 * Months, d.eurYts);
1093 QuantLib::ext::shared_ptr<IborIndex> usdLibor3m = QuantLib::ext::make_shared<USDLibor>(3 * Months, d.usdYts);
1094 QuantLib::ext::shared_ptr<IborIndex> gbpLibor3m = QuantLib::ext::make_shared<GBPLibor>(3 * Months, d.gbpYts);
1095
1096 for (Size i = 0; i <= d.volstepdates.size(); ++i) {
1097 Date tmp = i < d.volstepdates.size() ? d.volstepdates[i] : d.volstepdates.back() + 365;
1098 // EUR: atm+200bp, 150bp normal vol
1099 basketEur.push_back(QuantLib::ext::shared_ptr<SwaptionHelper>(new SwaptionHelper(
1100 tmp, 10 * Years, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.015)), euribor6m, 1 * Years, Thirty360(Thirty360::BondBasis),
1101 Actual360(), d.eurYts, BlackCalibrationHelper::RelativePriceError, 0.04, 1.0, Normal)));
1102 // USD: atm, 20%, lognormal vol
1103 basketUsd.push_back(QuantLib::ext::shared_ptr<SwaptionHelper>(
1104 new SwaptionHelper(tmp, 10 * Years, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.30)), usdLibor3m,
1105 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(), d.usdYts,
1106 BlackCalibrationHelper::RelativePriceError, Null<Real>(), 1.0, ShiftedLognormal, 0.0)));
1107 // GBP: atm-200bp, 10%, shifted lognormal vol with shift = 2%
1108 basketGbp.push_back(QuantLib::ext::shared_ptr<SwaptionHelper>(new SwaptionHelper(
1109 tmp, 10 * Years, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.30)), gbpLibor3m, 1 * Years, Thirty360(Thirty360::BondBasis),
1110 Actual360(), d.usdYts, BlackCalibrationHelper::RelativePriceError, 0.02, 1.0, ShiftedLognormal, 0.02)));
1111 }
1112
1113 for (Size i = 0; i < d.volstepdatesFx.size(); ++i) {
1114 Date tmp = i < d.volstepdatesFx.size() ? d.volstepdatesFx[i] : d.volstepdatesFx.back() + 365;
1115 // EUR-USD: atm, 30% (lognormal) vol
1116 basketEurUsd.push_back(QuantLib::ext::make_shared<FxEqOptionHelper>(
1117 tmp, Null<Real>(), d.fxEurUsd, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.20)), d.eurYts, d.usdYts,
1118 BlackCalibrationHelper::RelativePriceError));
1119 // EUR-GBP: atm, 10% (lognormal) vol
1120 basketEurGbp.push_back(QuantLib::ext::make_shared<FxEqOptionHelper>(
1121 tmp, Null<Real>(), d.fxEurGbp, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.20)), d.eurYts, d.gbpYts,
1122 BlackCalibrationHelper::RelativePriceError));
1123 }
1124
1125 // pricing engines
1126
1127 QuantLib::ext::shared_ptr<PricingEngine> eurSwEng = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(d.ccLgmExact, 0);
1128 QuantLib::ext::shared_ptr<PricingEngine> usdSwEng = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(d.ccLgmExact, 1);
1129 QuantLib::ext::shared_ptr<PricingEngine> gbpSwEng = QuantLib::ext::make_shared<AnalyticLgmSwaptionEngine>(d.ccLgmExact, 2);
1130
1131 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> eurUsdFxoEng =
1132 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(d.ccLgmExact, 0);
1133 QuantLib::ext::shared_ptr<AnalyticCcLgmFxOptionEngine> eurGbpFxoEng =
1134 QuantLib::ext::make_shared<AnalyticCcLgmFxOptionEngine>(d.ccLgmExact, 1);
1135
1136 eurUsdFxoEng->cache();
1137 eurGbpFxoEng->cache();
1138
1139 // assign engines to calibration instruments
1140 for (Size i = 0; i < basketEur.size(); ++i) {
1141 basketEur[i]->setPricingEngine(eurSwEng);
1142 }
1143 for (Size i = 0; i < basketUsd.size(); ++i) {
1144 basketUsd[i]->setPricingEngine(usdSwEng);
1145 }
1146 for (Size i = 0; i < basketGbp.size(); ++i) {
1147 basketGbp[i]->setPricingEngine(gbpSwEng);
1148 }
1149 for (Size i = 0; i < basketEurUsd.size(); ++i) {
1150 basketEurUsd[i]->setPricingEngine(eurUsdFxoEng);
1151 }
1152 for (Size i = 0; i < basketEurGbp.size(); ++i) {
1153 basketEurGbp[i]->setPricingEngine(eurGbpFxoEng);
1154 }
1155
1156 // calibrate the model
1157
1158 LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
1159 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
1160
1161 d.ccLgmExact->calibrateIrLgm1fVolatilitiesIterative(0, basketEur, lm, ec);
1162 d.ccLgmExact->calibrateIrLgm1fVolatilitiesIterative(1, basketUsd, lm, ec);
1163 d.ccLgmExact->calibrateIrLgm1fVolatilitiesIterative(2, basketGbp, lm, ec);
1164
1165 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::FX, 0, basketEurUsd, lm, ec);
1166 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::FX, 1, basketEurGbp, lm, ec);
1167
1168 // check the results
1169
1170 Real tol = 1E-6;
1171
1172 for (Size i = 0; i < basketEur.size(); ++i) {
1173 Real model = basketEur[i]->modelValue();
1174 Real market = basketEur[i]->marketValue();
1175 if (std::abs((model - market) / market) > tol)
1176 BOOST_ERROR("calibration failed for instrument #"
1177 << i << " in EUR basket, model value is " << model << " market value is " << market
1178 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
1179 }
1180 for (Size i = 0; i < basketUsd.size(); ++i) {
1181 Real model = basketUsd[i]->modelValue();
1182 Real market = basketUsd[i]->marketValue();
1183 if (std::abs((model - market) / market) > tol)
1184 BOOST_ERROR("calibration failed for instrument #"
1185 << i << " in USD basket, model value is " << model << " market value is " << market
1186 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
1187 }
1188 for (Size i = 0; i < basketGbp.size(); ++i) {
1189 Real model = basketGbp[i]->modelValue();
1190 Real market = basketGbp[i]->marketValue();
1191 if (std::abs((model - market) / market) > tol)
1192 BOOST_ERROR("calibration failed for instrument #"
1193 << i << " in GBP basket, model value is " << model << " market value is " << market
1194 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
1195 }
1196 for (Size i = 0; i < basketEurUsd.size(); ++i) {
1197 Real model = basketEurUsd[i]->modelValue();
1198 Real market = basketEurUsd[i]->marketValue();
1199 if (std::abs((model - market) / market) > tol)
1200 BOOST_ERROR("calibration failed for instrument #"
1201 << i << " in EUR-USD basket, model value is " << model << " market value is " << market
1202 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
1203 }
1204 for (Size i = 0; i < basketEurUsd.size(); ++i) {
1205 Real model = basketEurGbp[i]->modelValue();
1206 Real market = basketEurGbp[i]->marketValue();
1207 if (std::abs((model - market) / market) > tol)
1208 BOOST_ERROR("calibration failed for instrument #"
1209 << i << " in EUR-GBP basket, model value is " << model << " market value is " << market
1210 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
1211 }
1212}
1213
1214BOOST_AUTO_TEST_CASE(testLgm5fMoments) {
1215
1216 BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization "
1217 "in Ccy LGM 5F model...");
1218
1219 Lgm5fTestData d;
1220
1221 QuantLib::ext::shared_ptr<StochasticProcess> p_exact = d.ccLgmExact->stateProcess();
1222 QuantLib::ext::shared_ptr<StochasticProcess> p_euler = d.ccLgmEuler->stateProcess();
1223
1224 Real T = 10.0; // horizon at which we compare the moments
1225 Size steps = static_cast<Size>(T * 10.0); // number of simulation steps
1226 Size paths = 25000; // number of paths
1227
1228 Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
1229 Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
1230
1231 TimeGrid grid(T, steps);
1232
1233 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
1234 tmp->resetCache(grid.size() - 1);
1235 }
1236 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
1237 tmp->resetCache(grid.size() - 1);
1238 }
1239 MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid);
1240 MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid);
1241
1242 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu[5], e_eu2[5];
1243 accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > v_eu[5][5], v_eu2[5][5];
1244
1245 for (Size i = 0; i < paths; ++i) {
1246 Sample<MultiPath> path = pgen.next();
1247 Sample<MultiPath> path2 = pgen2.next();
1248 for (Size ii = 0; ii < 5; ++ii) {
1249 Real cii = path.value[ii].back();
1250 Real cii2 = path2.value[ii].back();
1251 e_eu[ii](cii);
1252 e_eu2[ii](cii2);
1253 for (Size jj = 0; jj <= ii; ++jj) {
1254 Real cjj = path.value[jj].back();
1255 v_eu[ii][jj](cii, covariate1 = cjj);
1256 Real cjj2 = path2.value[jj].back();
1257 v_eu2[ii][jj](cii2, covariate1 = cjj2);
1258 }
1259 }
1260 }
1261
1262 Real errTolLd[] = { 0.2E-4, 0.2E-4, 0.2E-4, 10.0E-4, 10.0E-4 };
1263
1264 for (Size i = 0; i < 5; ++i) {
1265 // check expectation against analytical calculation (Euler)
1266 if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTolLd[i]) {
1267 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
1268 << ") is inconsistent with numerical value (Euler "
1269 "discretization, "
1270 << mean(e_eu[i]) << "), error is "
1271 << e_an[i] - mean(e_eu[i]) << " tolerance is "
1272 << errTolLd[i]);
1273 }
1274 // check expectation against analytical calculation (exact disc)
1275 if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTolLd[i]) {
1276 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
1277 << ") is inconsistent with numerical value (Exact "
1278 "discretization, "
1279 << mean(e_eu2[i]) << "), error is "
1280 << e_an[i] - mean(e_eu2[i]) << " tolerance is "
1281 << errTolLd[i]);
1282 }
1283 }
1284
1285 // we have to deal with different natures of volatility
1286 // for ir (normal) and fx (ln) so different error
1287 // tolerances apply
1288 Real tollNormal = 0.1E-4; // ir-ir
1289 Real tolMixed = 0.25E-4; // ir-fx
1290 Real tolLn = 8.0E-4; // fx-fx
1291 Real tol; // set below
1292
1293 for (Size i = 0; i < 5; ++i) {
1294 for (Size j = 0; j <= i; ++j) {
1295 if (i < 3) {
1296 tol = tollNormal;
1297 } else {
1298 if (j < 3) {
1299 tol = tolMixed;
1300 } else {
1301 tol = tolLn;
1302 }
1303 }
1304 if (std::fabs(boost::accumulators::covariance(v_eu[i][j]) - v_an[i][j]) > tol) {
1305 BOOST_ERROR("analytical covariance at ("
1306 << i << "," << j << ") (" << v_an[i][j]
1307 << ") is inconsistent with numerical "
1308 "value (Euler discretization, "
1309 << boost::accumulators::covariance(v_eu[i][j]) << "), error is "
1310 << v_an[i][j] - boost::accumulators::covariance(v_eu[i][j]) << " tolerance is " << tol);
1311 }
1312 if (std::fabs(boost::accumulators::covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
1313 BOOST_ERROR("analytical covariance at ("
1314 << i << "," << j << ") (" << v_an[i][j]
1315 << ") is inconsistent with numerical "
1316 "value (Exact discretization, "
1317 << boost::accumulators::covariance(v_eu2[i][j]) << "), error is "
1318 << v_an[i][j] - boost::accumulators::covariance(v_eu2[i][j]) << " tolerance is " << tol);
1319 }
1320 }
1321 }
1322
1323} // testLgm5fMoments
1324
1325BOOST_AUTO_TEST_CASE(testLgmGsrEquivalence) {
1326
1327 BOOST_TEST_MESSAGE("Testing equivalence of GSR and LGM models...");
1328
1329 SavedSettings backup;
1330
1331 Date evalDate(12, January, 2015);
1332 Settings::instance().evaluationDate() = evalDate;
1333 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(evalDate, 0.02, Actual365Fixed()));
1334
1335 Real T[] = { 10.0, 20.0, 50.0, 100.0 };
1336 Real sigma[] = { 0.0050, 0.01, 0.02 };
1337 Real kappa[] = { -0.02, -0.01, 0.0, 0.03, 0.07 };
1338
1339 for (Size i = 0; i < LENGTH(T); ++i) {
1340 for (Size j = 0; j < LENGTH(sigma); ++j) {
1341 for (Size k = 0; k < LENGTH(kappa); ++k) {
1342
1343 std::vector<Date> stepDates;
1344 std::vector<Real> sigmas(1, sigma[j]);
1345
1346 QuantLib::ext::shared_ptr<Gsr> gsr = QuantLib::ext::make_shared<Gsr>(yts, stepDates, sigmas, kappa[k], T[i]);
1347
1348 Array stepTimes_a(0);
1349 Array sigmas_a(1, sigma[j]);
1350 Array kappas_a(1, kappa[k]);
1351
1352 // for shift = -H(T) we change the LGM measure to the T forward
1353 // measure effectively
1354 Real shift = close_enough(kappa[k], 0.0) ? -T[i] : -(1.0 - std::exp(-kappa[k] * T[i])) / kappa[k];
1355 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm_p =
1356 QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(EURCurrency(), yts, stepTimes_a,
1357 sigmas_a, stepTimes_a, kappas_a);
1358 lgm_p->shift() = shift;
1359
1360 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> lgm = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm_p);
1361
1362 QuantLib::ext::shared_ptr<StochasticProcess1D> gsr_process = gsr->stateProcess();
1363 QuantLib::ext::shared_ptr<StochasticProcess1D> lgm_process =
1364 QuantLib::ext::dynamic_pointer_cast<StochasticProcess1D>(lgm->stateProcess());
1365
1366 Size N = 10000; // number of paths
1367 Size seed = 123456;
1368 Size steps = 1; // one large step
1369 Real T2 = T[i] - 5.0; // we check a distribution at this time
1370
1371 TimeGrid grid(T2, steps);
1372
1373 PseudoRandom::rsg_type sg = PseudoRandom::make_sequence_generator(steps * 1, seed);
1374 PathGenerator<PseudoRandom::rsg_type> pgen_gsr(gsr_process, grid, sg, false);
1375 PathGenerator<PseudoRandom::rsg_type> pgen_lgm(lgm_process, grid, sg, false);
1376
1377 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean>, tag::variance> > stat_lgm, stat_gsr;
1378
1379 Real tol = 1.0E-12;
1380 for (Size ii = 0; ii < N; ++ii) {
1381 Sample<Path> path_lgm = pgen_lgm.next();
1382 Sample<Path> path_gsr = pgen_gsr.next();
1383 Real yGsr = (path_gsr.value.back() - gsr_process->expectation(0.0, 0.0, T2)) /
1384 gsr_process->stdDeviation(0.0, 0.0, T2);
1385 Real xLgm = path_lgm.value.back();
1386 Real gsrRate = -std::log(gsr->zerobond(T2 + 1.0, T2, yGsr));
1387 // it's nice to have uniform interfaces in all models ...
1388 Real lgmRate = -std::log(lgm->discountBond(T2, T2 + 1.0, xLgm));
1389 stat_gsr(gsrRate);
1390 stat_lgm(lgmRate);
1391 // check pathwise identity
1392 if (std::fabs(gsrRate - lgmRate) >= tol) {
1393 BOOST_ERROR("lgm rate (" << lgmRate << ") deviates from gsr rate (" << gsrRate << ") on path #"
1394 << i);
1395 }
1396 }
1397
1398 // effectively we are checking a pathwise identity
1399 // here as well, but the statistics seems to better
1400 // summarize a possible problem, so we output differences
1401 // in the mean as well
1402 if (std::fabs(mean(stat_gsr) - mean(stat_lgm)) > tol ||
1403 std::fabs(boost::accumulators::variance(stat_gsr) - boost::accumulators::variance(stat_lgm)) >
1404 tol) {
1405 BOOST_ERROR("failed to verify LGM-GSR equivalence, "
1406 "(mean,variance) of zero rate is ("
1407 << mean(stat_gsr) << "," << boost::accumulators::variance(stat_gsr) << ") for GSR, ("
1408 << mean(stat_lgm) << "," << boost::accumulators::variance(stat_lgm)
1409 << ") for LGM, for T=" << T[i] << ", sigma=" << sigma[j] << ", kappa=" << kappa[k]
1410 << ", shift=" << shift);
1411 }
1412 }
1413 }
1414 }
1415
1416} // testLgmGsrEquivalence
1417
1418BOOST_AUTO_TEST_CASE(testLgmMcWithShift) {
1419 BOOST_TEST_MESSAGE("Testing LGM1F Monte Carlo simulation with shifted H...");
1420
1421 // cashflow time
1422 Real T = 50.0;
1423
1424 // shift horizons
1425 Real T_shift[] = { 0.0, 10.0, 20.0, 30.0, 40.0, 50.0 };
1426
1427 // tolerances for error of mean
1428 Real eom_tol[] = { 0.17, 0.05, 0.02, 0.01, 0.005, 1.0E-12 };
1429
1430 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
1431
1432 QuantLib::ext::shared_ptr<IrLgm1fParametrization> lgm =
1433 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), yts, 0.01, 0.01);
1434 QuantLib::ext::shared_ptr<StochasticProcess> p = QuantLib::ext::make_shared<IrLgm1fStateProcess>(lgm);
1435
1436 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> model = QuantLib::ext::make_shared<LinearGaussMarkovModel>(lgm);
1437
1438 Size steps = 1;
1439 Size paths = 10000;
1440 Size seed = 42;
1441 TimeGrid grid(T, steps);
1442
1443 MultiPathGeneratorMersenneTwister pgen(p, grid, seed, true);
1444
1445 for (Size ii = 0; ii < LENGTH(T_shift); ++ii) {
1446
1447 lgm->shift() = -(1.0 - exp(-0.01 * T_shift[ii])) / 0.01;
1448
1449 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu;
1450
1451 for (Size i = 0; i < paths; ++i) {
1452 Sample<MultiPath> path = pgen.next();
1453 Sample<MultiPath> path_a = pgen.next();
1454 e_eu(1.0 / model->numeraire(T, path.value[0].back()));
1455 e_eu(1.0 / model->numeraire(T, path_a.value[0].back()));
1456 }
1457
1458 Real discount = yts->discount(T);
1459
1460 if (error_of<tag::mean>(e_eu) / discount > eom_tol[ii]) {
1461 BOOST_ERROR("estimated error of mean for shifted mc simulation with shift "
1462 << T_shift[ii] << " can not be verified (" << error_of<tag::mean>(e_eu) / discount
1463 << "), tolerance is 1E-8");
1464 }
1465
1466 if (std::fabs(mean(e_eu) / discount - 1.0) > eom_tol[ii]) {
1467 BOOST_ERROR("estimated error for shifted mc simulation with shift " << T_shift[ii]
1468 << " can not "
1469 "be verified ("
1470 << mean(e_eu) / discount - 1.0
1471 << "), tolerance is 1E-8");
1472 }
1473 }
1474
1475} // testLgmMcWithShift
1476
1477BOOST_AUTO_TEST_CASE(testIrFxCrCirppMartingaleProperty) {
1478
1479 BOOST_TEST_MESSAGE("Testing martingale property in ir-fx-cr(lgm)-cf(cir++) model for "
1480 "Euler and exact discretizations...");
1481
1482 IrFxCrModelTestData d(false);
1483 IrFxCrModelTestData d_cirpp(true);
1484
1485 QuantLib::ext::shared_ptr<StochasticProcess> process1 = d.modelExact->stateProcess();
1486 QuantLib::ext::shared_ptr<StochasticProcess> process2 = d_cirpp.modelEuler->stateProcess();
1487
1488 Size n = 10000; // number of paths
1489 Size seed = 18; // rng seed
1490 Time T = 2.0; // maturity of payoff
1491 Time T2 = 20.0; // zerobond maturity
1492 Size steps = static_cast<Size>(T * 24); // number of steps taken (euler / Brigo-Alfonsi)
1493
1494 LowDiscrepancy::rsg_type sg1 = LowDiscrepancy::make_sequence_generator(process1->factors() * 1, seed);
1495 LowDiscrepancy::rsg_type sg2 = LowDiscrepancy::make_sequence_generator(process2->factors() * steps, seed);
1496
1497 TimeGrid grid1(T, 1);
1498 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process1)) {
1499 tmp->resetCache(grid1.size() - 1);
1500 }
1501 MultiPathGenerator<LowDiscrepancy::rsg_type> pg1(process1, grid1, sg1, false);
1502 TimeGrid grid2(T, steps);
1503 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process2)) {
1504 tmp->resetCache(grid2.size() - 1);
1505 }
1506 MultiPathGenerator<LowDiscrepancy::rsg_type> pg2(process2, grid2, sg2, false);
1507
1508 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb1, usdzb1, gbpzb1, n1eur1, n2usd1,
1509 n3gbp1;
1510 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb2, usdzb2, gbpzb2, n1eur2, n2usd2,
1511 n3gbp2, n1cir2, n2cir2, n3cir2;
1512
1513 for (Size j = 0; j < n; ++j) {
1514 Sample<MultiPath> path1 = pg1.next();
1515 Sample<MultiPath> path2 = pg2.next();
1516 Size l1 = path1.value[0].length() - 1;
1517 Size l2 = path2.value[0].length() - 1;
1518 Real zeur1 = path1.value[0][l1];
1519 Real zusd1 = path1.value[1][l1];
1520 Real zgbp1 = path1.value[2][l1];
1521 Real fxusd1 = std::exp(path1.value[3][l1]);
1522 Real fxgbp1 = std::exp(path1.value[4][l1]);
1523 Real crzn11 = path1.value[5][l1];
1524 Real cryn11 = path1.value[6][l1];
1525 Real crzn21 = path1.value[7][l1];
1526 Real cryn21 = path1.value[8][l1];
1527 Real crzn31 = path1.value[9][l1];
1528 Real cryn31 = path1.value[10][l1];
1529 Real zeur2 = path2.value[0][l2];
1530 Real zusd2 = path2.value[1][l2];
1531 Real zgbp2 = path2.value[2][l2];
1532 Real fxusd2 = std::exp(path2.value[3][l2]);
1533 Real fxgbp2 = std::exp(path2.value[4][l2]);
1534 Real crzn12 = path2.value[5][l2]; // CR LGM
1535 Real cryn12 = path2.value[6][l2];
1536 Real crzn22 = path2.value[7][l2];
1537 Real cryn22 = path2.value[8][l2];
1538 Real crzn32 = path2.value[9][l2];
1539 Real cryn32 = path2.value[10][l2];
1540 Real ciry12 = path2.value[11][l2]; // CR CIR++
1541 Real cirn12 = path2.value[12][l2];
1542 Real ciry22 = path2.value[13][l2];
1543 Real cirn22 = path2.value[14][l2];
1544 Real ciry32 = path2.value[15][l2];
1545 Real cirn32 = path2.value[16][l2];
1546
1547 // EUR zerobond
1548 eurzb1(d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
1549 // USD zerobond
1550 usdzb1(d.modelExact->discountBond(1, T, T2, zusd1) * fxusd1 / d.modelExact->numeraire(0, T, zeur1));
1551 // GBP zerobond
1552 gbpzb1(d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 / d.modelExact->numeraire(0, T, zeur1));
1553 // EUR defaultable zerobond for name 1
1554 std::pair<Real, Real> sn11 = d.modelExact->crlgm1fS(0, 0, T, T2, crzn11, cryn11);
1555 n1eur1(sn11.first * sn11.second * d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
1556 // USD defaultable zerobond for name 2
1557 std::pair<Real, Real> sn21 = d.modelExact->crlgm1fS(1, 1, T, T2, crzn21, cryn21);
1558 n2usd1(sn21.first * sn21.second * d.modelExact->discountBond(1, T, T2, zusd1) * fxusd1 /
1559 d.modelExact->numeraire(0, T, zeur1));
1560 // GBP defaultable zerobond for name 3
1561 std::pair<Real, Real> sn31 = d.modelExact->crlgm1fS(2, 2, T, T2, crzn31, cryn31);
1562 n3gbp1(sn31.first * sn31.second * d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 /
1563 d.modelExact->numeraire(0, T, zeur1));
1564
1565 // EUR zerobond
1566 eurzb2(d_cirpp.modelEuler->discountBond(0, T, T2, zeur2) / d_cirpp.modelEuler->numeraire(0, T, zeur2));
1567 // USD zerobond
1568 usdzb2(d_cirpp.modelEuler->discountBond(1, T, T2, zusd2) * fxusd2 / d_cirpp.modelEuler->numeraire(0, T, zeur2));
1569 // GBP zerobond
1570 gbpzb2(d_cirpp.modelEuler->discountBond(2, T, T2, zgbp2) * fxgbp2 / d_cirpp.modelEuler->numeraire(0, T, zeur2));
1571 // EUR defaultable zerobond for name 1
1572 std::pair<Real, Real> sn12 = d_cirpp.modelEuler->crlgm1fS(0, 0, T, T2, crzn12, cryn12);
1573 n1eur2(sn12.first * sn12.second * d_cirpp.modelEuler->discountBond(0, T, T2, zeur2) / d_cirpp.modelEuler->numeraire(0, T, zeur2));
1574 // USD defaultable zerobond for name 2
1575 std::pair<Real, Real> sn22 = d_cirpp.modelEuler->crlgm1fS(1, 1, T, T2, crzn22, cryn22);
1576 n2usd2(sn22.first * sn22.second * d_cirpp.modelEuler->discountBond(1, T, T2, zusd2) * fxusd2 /
1577 d_cirpp.modelEuler->numeraire(0, T, zeur2));
1578 // GBP defaultable zerobond for name 3
1579 std::pair<Real, Real> sn32 = d_cirpp.modelEuler->crlgm1fS(2, 2, T, T2, crzn32, cryn32);
1580 n3gbp2(sn32.first * sn32.second * d_cirpp.modelEuler->discountBond(2, T, T2, zgbp2) * fxgbp2 /
1581 d_cirpp.modelEuler->numeraire(0, T, zeur2));
1582 // EUR defaultable zerobond for name 1 (CIR++)
1583 std::pair<Real, Real> sc12 = d_cirpp.modelEuler->crcirppS(3, T, T2, ciry12, cirn12);
1584 n1cir2(sc12.first * sc12.second * d_cirpp.modelEuler->discountBond(0, T, T2, zeur2) / d_cirpp.modelEuler->numeraire(0, T, zeur2));
1585 // USD defaultable zerobond for name 2 (CIR++)
1586 std::pair<Real, Real> sc22 = d_cirpp.modelEuler->crcirppS(4, T, T2, ciry22, cirn22);
1587 n2cir2(sc22.first * sc22.second * d_cirpp.modelEuler->discountBond(1, T, T2, zusd2) * fxusd2 /
1588 d_cirpp.modelEuler->numeraire(0, T, zeur2));
1589 // GBP defaultable zerobond for name 3 (CIR++)
1590 std::pair<Real, Real> sc32 = d_cirpp.modelEuler->crcirppS(5, T, T2, ciry32, cirn32);
1591 n3cir2(sc32.first * sc32.second * d_cirpp.modelEuler->discountBond(2, T, T2, zgbp2) * fxgbp2 /
1592 d_cirpp.modelEuler->numeraire(0, T, zeur2));
1593 }
1594
1595 BOOST_TEST_MESSAGE("EXACT:");
1596 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb1) << " +- " << error_of<tag::mean>(eurzb1) << " vs analytical "
1597 << d.eurYts->discount(T2));
1598 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb1) << " +- " << error_of<tag::mean>(usdzb1) << " vs analytical "
1599 << d.usdYts->discount(T2) * d.fxEurUsd->value());
1600 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb1) << " +- " << error_of<tag::mean>(gbpzb1) << " vs analytical "
1601 << d.gbpYts->discount(T2) * d.fxEurGbp->value());
1602 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur1) << " +- " << error_of<tag::mean>(n1eur1) << " vs analytical "
1603 << d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2));
1604 BOOST_TEST_MESSAGE("N2 zb USD = " << mean(n2usd1) << " +- " << error_of<tag::mean>(n2usd1) << " vs analytical "
1605 << d.fxEurUsd->value() * d.usdYts->discount(T2) *
1606 d.n2Ts->survivalProbability(T2));
1607 BOOST_TEST_MESSAGE("N3 zb GBP = " << mean(n3gbp1) << " +- " << error_of<tag::mean>(n3gbp1) << " vs analytical "
1608 << d.fxEurGbp->value() * d.gbpYts->discount(T2) *
1609 d.n3Ts->survivalProbability(T2));
1610
1611 BOOST_TEST_MESSAGE("\nEULER:");
1612 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb2) << " +- " << error_of<tag::mean>(eurzb2) << " vs analytical "
1613 << d_cirpp.eurYts->discount(T2));
1614 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb2) << " +- " << error_of<tag::mean>(usdzb2) << " vs analytical "
1615 << d_cirpp.usdYts->discount(T2) * d_cirpp.fxEurUsd->value());
1616 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb2) << " +- " << error_of<tag::mean>(gbpzb2) << " vs analytical "
1617 << d_cirpp.gbpYts->discount(T2) * d_cirpp.fxEurGbp->value());
1618 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur2) << " +- " << error_of<tag::mean>(n1eur2) << " vs analytical "
1619 << d_cirpp.eurYts->discount(T2) * d_cirpp.n1Ts->survivalProbability(T2));
1620 BOOST_TEST_MESSAGE("N2 zb USD = " << mean(n2usd2) << " +- " << error_of<tag::mean>(n2usd2) << " vs analytical "
1621 << d_cirpp.fxEurUsd->value() * d_cirpp.usdYts->discount(T2) *
1622 d_cirpp.n2Ts->survivalProbability(T2));
1623 BOOST_TEST_MESSAGE("N3 zb GBP = " << mean(n3gbp2) << " +- " << error_of<tag::mean>(n3gbp2) << " vs analytical "
1624 << d_cirpp.fxEurGbp->value() * d_cirpp.gbpYts->discount(T2) *
1625 d_cirpp.n3Ts->survivalProbability(T2));
1626 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1cir2) << " +- " << error_of<tag::mean>(n1cir2) << " vs analytical "
1627 << d_cirpp.eurYts->discount(T2) * d_cirpp.n1Ts->survivalProbability(T2));
1628 BOOST_TEST_MESSAGE("N2 zb USD = " << mean(n2cir2) << " +- " << error_of<tag::mean>(n2cir2) << " vs analytical "
1629 << d_cirpp.fxEurUsd->value() * d_cirpp.usdYts->discount(T2) *
1630 d_cirpp.n2Ts->survivalProbability(T2));
1631 BOOST_TEST_MESSAGE("N3 zb GBP = " << mean(n3cir2) << " +- " << error_of<tag::mean>(n3cir2) << " vs analytical "
1632 << d_cirpp.fxEurGbp->value() * d_cirpp.gbpYts->discount(T2) *
1633 d_cirpp.n3Ts->survivalProbability(T2));
1634
1635 Real tol1 = 2.0E-4; // EXACT
1636 Real tol2 = 12.0E-4; // EULER
1637
1638 Real ev = d.eurYts->discount(T2);
1639 if (std::abs(mean(eurzb1) - ev) > tol1)
1640 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.), expected " << ev << ", got " << mean(eurzb1)
1641 << ", tolerance " << tol1);
1642 ev = d.usdYts->discount(T2) * d.fxEurUsd->value();
1643 if (std::abs(mean(usdzb1) - ev) > tol1)
1644 BOOST_TEST_ERROR("Martingale test failed for usdzb (exact discr.), expected " << ev << ", got " << mean(usdzb1)
1645 << ", tolerance " << tol1);
1646 ev = d.gbpYts->discount(T2) * d.fxEurGbp->value();
1647 if (std::abs(mean(gbpzb1) - ev) > tol1)
1648 BOOST_TEST_ERROR("Martingale test failed for gbpzb (exact discr.), expected " << ev << ", got " << mean(gbpzb1)
1649 << ", tolerance " << tol1);
1650 ev = d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2);
1651 if (std::abs(mean(n1eur1) - ev) > tol1)
1652 BOOST_TEST_ERROR("Martingale test failed for n1eur (exact discr.), expected " << ev << ", got " << mean(n1eur1)
1653 << ", tolerance " << tol1);
1654 ev = d.fxEurUsd->value() * d.usdYts->discount(T2) * d.n2Ts->survivalProbability(T2);
1655 if (std::abs(mean(n2usd1) - ev) > tol1)
1656 BOOST_TEST_ERROR("Martingale test failed for n2usd (exact discr.), expected " << ev << ", got " << mean(n2usd1)
1657 << ", tolerance " << tol1);
1658 ev = d.fxEurGbp->value() * d.gbpYts->discount(T2) * d.n3Ts->survivalProbability(T2);
1659 if (std::abs(mean(n3gbp1) - ev) > tol1)
1660 BOOST_TEST_ERROR("Martingale test failed for n3gbp (exact discr.), expected " << ev << ", got " << mean(n3gbp1)
1661 << ", tolerance " << tol1);
1662
1663 ev = d_cirpp.eurYts->discount(T2);
1664 if (std::abs(mean(eurzb2) - ev) > tol2)
1665 BOOST_TEST_ERROR("Martingale test failed for eurzb (Euler discr.), expected " << ev << ", got " << mean(eurzb2)
1666 << ", tolerance " << tol2);
1667 ev = d_cirpp.usdYts->discount(T2) * d_cirpp.fxEurUsd->value();
1668 if (std::abs(mean(usdzb2) - ev) > tol2)
1669 BOOST_TEST_ERROR("Martingale test failed for usdzb (Euler discr.), expected "
1670 << ev << ", got " << mean(usdzb2) << ", tolerance " << tol2 * error_of<tag::mean>(usdzb2));
1671 ev = d_cirpp.gbpYts->discount(T2) * d_cirpp.fxEurGbp->value();
1672 if (std::abs(mean(gbpzb2) - ev) > tol2)
1673 BOOST_TEST_ERROR("Martingale test failed for gbpzb (Euler discr.), expected " << ev << ", got " << mean(gbpzb2)
1674 << ", tolerance " << tol2);
1675
1676 // CR LGM
1677 ev = d_cirpp.eurYts->discount(T2) * d_cirpp.n1Ts->survivalProbability(T2);
1678 if (std::abs(mean(n1eur2) - ev) > tol2)
1679 BOOST_TEST_ERROR("Martingale test failed for n1eur (Euler discr.), expected " << ev << ", got " << mean(n1eur2)
1680 << ", tolerance " << tol2);
1681 ev = d_cirpp.fxEurUsd->value() * d_cirpp.usdYts->discount(T2) * d_cirpp.n2Ts->survivalProbability(T2);
1682 if (std::abs(mean(n2usd2) - ev) > tol2)
1683 BOOST_TEST_ERROR("Martingale test failed for n2usd (Euler discr.), expected " << ev << ", got " << mean(n2usd2)
1684 << ", tolerance " << tol2);
1685 ev = d_cirpp.fxEurGbp->value() * d_cirpp.gbpYts->discount(T2) * d_cirpp.n3Ts->survivalProbability(T2);
1686 if (std::abs(mean(n3gbp2) - ev) > tol2)
1687 BOOST_TEST_ERROR("Martingale test failed for n3gbp (Euler discr.), expected " << ev << ", got " << mean(n3gbp2)
1688 << ", tolerance " << tol2);
1689
1690 // CR CIR
1691 ev = d_cirpp.eurYts->discount(T2) * d_cirpp.n1Ts->survivalProbability(T2);
1692 if (std::abs(mean(n1cir2) - ev) > tol2)
1693 BOOST_TEST_ERROR("Martingale test failed for n1cir (Euler discr.), expected " << ev << ", got " << mean(n1cir2)
1694 << ", tolerance " << tol2);
1695 ev = d_cirpp.fxEurUsd->value() * d_cirpp.usdYts->discount(T2) * d_cirpp.n2Ts->survivalProbability(T2);
1696 if (std::abs(mean(n2cir2) - ev) > tol2)
1697 BOOST_TEST_ERROR("Martingale test failed for n2cir2 (Euler discr.), expected " << ev << ", got " << mean(n2cir2)
1698 << ", tolerance " << tol2);
1699 ev = d_cirpp.fxEurGbp->value() * d_cirpp.gbpYts->discount(T2) * d_cirpp.n3Ts->survivalProbability(T2);
1700 if (std::abs(mean(n3cir2) - ev) > tol2)
1701 BOOST_TEST_ERROR("Martingale test failed for n3cir2 (Euler discr.), expected " << ev << ", got " << mean(n3cir2)
1702 << ", tolerance " << tol2);
1703
1704} // testIrFxCrMartingaleProperty
1705
1706BOOST_AUTO_TEST_CASE(testIrFxCrMoments) {
1707
1708 BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization "
1709 "in ir-fx-cr model...");
1710
1711 IrFxCrModelTestData d;
1712
1713 QuantLib::ext::shared_ptr<StochasticProcess> p_exact = d.modelExact->stateProcess();
1714 QuantLib::ext::shared_ptr<StochasticProcess> p_euler = d.modelEuler->stateProcess();
1715
1716 Real T = 2.0; // horizon at which we compare the moments
1717 Size steps = static_cast<Size>(T * 10); // number of simulation steps (Euler and exact)
1718 Size paths = 50000; // number of paths
1719
1720 Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
1721 Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
1722
1723 Size seed = 18;
1724 TimeGrid grid(T, steps);
1725
1726 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
1727 tmp->resetCache(grid.size() - 1);
1728 }
1729 MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid, SobolBrownianGenerator::Diagonal, seed,
1730 SobolRsg::JoeKuoD7);
1731 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
1732 tmp->resetCache(grid.size() - 1);
1733 }
1734 MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid, SobolBrownianGenerator::Diagonal, seed,
1735 SobolRsg::JoeKuoD7);
1736
1737 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu[11], e_eu2[11];
1738 accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > v_eu[11][11], v_eu2[11][11];
1739
1740 for (Size i = 0; i < paths; ++i) {
1741 Sample<MultiPath> path = pgen.next();
1742 Sample<MultiPath> path2 = pgen2.next();
1743 for (Size ii = 0; ii < 11; ++ii) {
1744 Real cii = path.value[ii].back();
1745 Real cii2 = path2.value[ii].back();
1746 e_eu[ii](cii);
1747 e_eu2[ii](cii2);
1748 for (Size jj = 0; jj <= ii; ++jj) {
1749 Real cjj = path.value[jj].back();
1750 v_eu[ii][jj](cii, covariate1 = cjj);
1751 Real cjj2 = path2.value[jj].back();
1752 v_eu2[ii][jj](cii2, covariate1 = cjj2);
1753 }
1754 }
1755 }
1756
1757 for (Size i = 0; i < 11; ++i) {
1758 BOOST_TEST_MESSAGE("E_" << i << " " << e_an[i] << " " << mean(e_eu[i]) << " " << mean(e_eu2[i]));
1759 }
1760 BOOST_TEST_MESSAGE("==================");
1761
1762 BOOST_TEST_MESSAGE("one step analytical");
1763 for (Size i = 0; i < 11; ++i) {
1764 std::ostringstream tmp;
1765 for (Size j = 0; j <= i; ++j) {
1766 tmp << v_an[i][j] << " ";
1767 }
1768 BOOST_TEST_MESSAGE(tmp.str());
1769 }
1770 BOOST_TEST_MESSAGE("==================");
1771
1772 BOOST_TEST_MESSAGE("euler");
1773 for (Size i = 0; i < 11; ++i) {
1774 std::ostringstream tmp;
1775 for (Size j = 0; j <= i; ++j) {
1776 tmp << boost::accumulators::covariance(v_eu[i][j]) << " ";
1777 }
1778 BOOST_TEST_MESSAGE(tmp.str());
1779 }
1780 BOOST_TEST_MESSAGE("==================");
1781
1782 BOOST_TEST_MESSAGE("exact");
1783 for (Size i = 0; i < 11; ++i) {
1784 std::ostringstream tmp;
1785 for (Size j = 0; j <= i; ++j) {
1786 tmp << boost::accumulators::covariance(v_eu2[i][j]) << " ";
1787 }
1788 BOOST_TEST_MESSAGE(tmp.str());
1789 }
1790 BOOST_TEST_MESSAGE("==================");
1791
1792 Real errTolLd[] = { 0.5E-4, 0.5E-4, 0.5E-4, 10.0E-4, 10.0E-4, 0.7E-4, 0.7E-4, 0.7E-4, 0.7E-4, 0.7E-4, 0.7E-4 };
1793
1794 for (Size i = 0; i < 11; ++i) {
1795 // check expectation against analytical calculation (Euler)
1796 if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTolLd[i]) {
1797 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
1798 << ") is inconsistent with numerical value (Euler "
1799 "discretization, "
1800 << mean(e_eu[i]) << "), error is "
1801 << e_an[i] - mean(e_eu[i]) << " tolerance is "
1802 << errTolLd[i]);
1803 }
1804 // check expectation against analytical calculation (exact disc)
1805 if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTolLd[i]) {
1806 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
1807 << ") is inconsistent with numerical value (exact "
1808 "discretization, "
1809 << mean(e_eu2[i]) << "), error is "
1810 << e_an[i] - mean(e_eu2[i]) << " tolerance is "
1811 << errTolLd[i]);
1812 }
1813 }
1814
1815 // this is a bit rough compared to the more differentiated test
1816 // of the IR-FX model ...
1817 Real tol = 10.0E-4;
1818
1819 for (Size i = 0; i < 11; ++i) {
1820 for (Size j = 0; j <= i; ++j) {
1821 if (std::fabs(boost::accumulators::covariance(v_eu[i][j]) - v_an[i][j]) > tol) {
1822 BOOST_ERROR("analytical covariance at ("
1823 << i << "," << j << ") (" << v_an[i][j]
1824 << ") is inconsistent with numerical "
1825 "value (Euler discretization, "
1826 << boost::accumulators::covariance(v_eu[i][j]) << "), error is "
1827 << v_an[i][j] - boost::accumulators::covariance(v_eu[i][j]) << " tolerance is " << tol);
1828 }
1829 if (std::fabs(boost::accumulators::covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
1830 BOOST_ERROR("analytical covariance at ("
1831 << i << "," << j << ") (" << v_an[i][j]
1832 << ") is inconsistent with numerical "
1833 "value (exact discretization, "
1834 << boost::accumulators::covariance(v_eu2[i][j]) << "), error is "
1835 << v_an[i][j] - boost::accumulators::covariance(v_eu2[i][j]) << " tolerance is " << tol);
1836 }
1837 }
1838 }
1839
1840} // testIrFxCrMoments
1841
1842BOOST_AUTO_TEST_CASE(testIrFxCrCorrelationRecovery) {
1843
1844 BOOST_TEST_MESSAGE("Test if random correlation input is recovered for "
1845 "small dt in ir-fx-cr model...");
1846
1847 class PseudoCurrency : public Currency {
1848 public:
1849 PseudoCurrency(const Size id) {
1850 std::ostringstream ln, sn;
1851 ln << "Dummy " << id;
1852 sn << "DUM " << id;
1853 data_ = QuantLib::ext::make_shared<Data>(ln.str(), sn.str(), id, sn.str(), "", 100, Rounding(), "%3% %1$.2f");
1854 }
1855 };
1856
1857 Real dt = 1.0E-6;
1858 Real tol = 1.0E-7;
1859
1860 // for ir-fx this fully specifies the correlation matrix
1861 // for new asset classes add other possible combinations as well
1862 Size currencies[] = { 1, 2, 3, 4, 5, 10, 20 };
1863 Size creditnames[] = { 0, 1, 5, 10 };
1864
1865 MersenneTwisterUniformRng mt(42);
1866
1867 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
1868
1869 Handle<DefaultProbabilityTermStructure> hts(
1870 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.01, Actual365Fixed()));
1871
1872 Handle<Quote> fxspot(QuantLib::ext::make_shared<SimpleQuote>(1.00));
1873
1874 Array notimes(0);
1875 Array fxsigma(1, 0.10);
1876
1877 for (Size ii = 0; ii < LENGTH(currencies); ++ii) {
1878 for (Size jj = 0; jj < LENGTH(creditnames); ++jj) {
1879
1880 std::vector<Currency> pseudoCcy;
1881 for (Size i = 0; i < currencies[ii]; ++i) {
1882 PseudoCurrency tmp(i);
1883 pseudoCcy.push_back(tmp);
1884 }
1885
1886 Size dim = 2 * currencies[ii] - 1 + creditnames[jj];
1887
1888 // generate random correlation matrix
1889 Matrix b(dim, dim);
1890 Size maxTries = 100;
1891 bool valid = true;
1892 do {
1893 Matrix a(dim, dim);
1894 for (Size i = 0; i < dim; ++i) {
1895 for (Size j = 0; j <= i; ++j) {
1896 a[i][j] = a[j][i] = mt.nextReal() - 0.5;
1897 }
1898 }
1899 b = a * transpose(a);
1900 for (Size i = 0; i < dim; ++i) {
1901 if (b[i][i] < 1E-5)
1902 valid = false;
1903 }
1904 } while (!valid && --maxTries > 0);
1905
1906 if (maxTries == 0) {
1907 BOOST_ERROR("could no generate random matrix");
1908 return;
1909 }
1910
1911 Matrix c(dim, dim);
1912 for (Size i = 0; i < dim; ++i) {
1913 for (Size j = 0; j <= i; ++j) {
1914 c[i][j] = c[j][i] = b[i][j] / std::sqrt(b[i][i] * b[j][j]);
1915 }
1916 }
1917
1918 // set up model
1919
1920 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
1921
1922 // IR
1923 for (Size i = 0; i < currencies[ii]; ++i) {
1924 parametrizations.push_back(
1925 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(pseudoCcy[i], yts, 0.01, 0.01));
1926 }
1927 // FX
1928 for (Size i = 0; i < currencies[ii] - 1; ++i) {
1929 parametrizations.push_back(QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
1930 pseudoCcy[i + 1], fxspot, notimes, fxsigma));
1931 }
1932 // CR
1933 for (Size i = 0; i < creditnames[jj]; ++i) {
1934 parametrizations.push_back(
1935 QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(pseudoCcy[0], hts, 0.01, 0.01));
1936 }
1937
1938 // get QuantLib::Error: negative eigenvalue(s) (-3.649315e-16) with SalvagingAlgorithm::None
1939 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact =
1940 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::Spectral,
1941 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
1942 QuantLib::ext::shared_ptr<CrossAssetModel> modelEuler =
1943 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::Spectral,
1944 IrModel::Measure::LGM, CrossAssetModel::Discretization::Euler);
1945
1946 QuantLib::ext::shared_ptr<StochasticProcess> peuler = modelEuler->stateProcess();
1947 QuantLib::ext::shared_ptr<StochasticProcess> pexact = modelExact->stateProcess();
1948
1949 Matrix c1 = peuler->covariance(dt, peuler->initialValues(), dt);
1950 Matrix c2 = pexact->covariance(0.0, peuler->initialValues(), dt);
1951
1952 Matrix r1(dim, dim), r2(dim, dim);
1953
1954 for (Size i = 0; i < dim; ++i) {
1955 for (Size j = 0; j <= i; ++j) {
1956 // there are two state variables per credit name,
1957 Size subi = i < 2 * currencies[ii] - 1 ? 1 : 2;
1958 Size subj = j < 2 * currencies[ii] - 1 ? 1 : 2;
1959 for (Size k1 = 0; k1 < subi; ++k1) {
1960 for (Size k2 = 0; k2 < subj; ++k2) {
1961 Size i0 = i < 2 * currencies[ii] - 1
1962 ? i
1963 : 2 * currencies[ii] - 1 + 2 * (i - (2 * currencies[ii] - 1)) + k1;
1964 Size j0 = j < 2 * currencies[ii] - 1
1965 ? j
1966 : 2 * currencies[ii] - 1 + 2 * (j - (2 * currencies[ii] - 1)) + k2;
1967 r1[i][j] = r1[j][i] = c1[i0][j0] / std::sqrt(c1[i0][i0] * c1[j0][j0]);
1968 r2[i][j] = r2[j][i] = c2[i0][j0] / std::sqrt(c2[i0][i0] * c2[j0][j0]);
1969 if (std::fabs(r1[i][j] - c[i][j]) > tol) {
1970 BOOST_ERROR("failed to recover correlation matrix from "
1971 "Euler state process (i,j)=("
1972 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
1973 << "), input correlation is " << c[i][j] << ", output is " << r1[i][j]
1974 << ", difference " << (c[i][j] - r1[i][j]) << ", tolerance " << tol
1975 << " test configuration is " << currencies[ii] << " currencies and "
1976 << creditnames[jj] << " credit names");
1977 }
1978 if (k1 == k2) {
1979 if (std::fabs(r2[i][j] - c[i][j]) > tol) {
1980 BOOST_ERROR("failed to recover correlation matrix "
1981 "from "
1982 "exact state process (i,j)=("
1983 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
1984 << "), input correlation is " << c[i][j] << ", output is " << r2[i][j]
1985 << ", difference " << (c[i][j] - r2[i][j]) << ", tolerance " << tol
1986 << " test configuration is " << currencies[ii] << " currencies and "
1987 << creditnames[jj] << " credit names");
1988 }
1989 }
1990 }
1991 }
1992 }
1993 }
1994 } // for creditnames
1995 } // for currencies
1996
1997} // testIrFxCrCorrelationRecovery
1998
1999namespace {
2000
2001// arbitrary commodity price curve
2002std::vector<Period> comTerms = { 1*Days, 1*Years, 2*Years, 3*Years, 5*Years, 10*Years, 15*Years, 20*Years, 30*Years };
2003std::vector<Real> comPrices { 100, 101, 102, 103, 105, 110, 115, 120, 130 };
2004
2005struct IrFxInfCrComModelTestData {
2006
2007 IrFxInfCrComModelTestData(bool infEurIsDK = true, bool infGbpIsDK = true, bool flatVols = false, bool driftFreeState = false)
2008 : referenceDate(30, July, 2015),
2009 dc(Actual365Fixed()),
2010 eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, dc)),
2011 usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, dc)),
2012 gbpYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.04, dc)),
2013 fxEurUsd(QuantLib::ext::make_shared<SimpleQuote>(0.90)),
2014 fxEurGbp(QuantLib::ext::make_shared<SimpleQuote>(1.35)),
2015 n1Ts(QuantLib::ext::make_shared<FlatHazardRate>(referenceDate, 0.01, dc)),
2016 comTS(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(comTerms, comPrices, dc, USDCurrency())) {
2017
2018 Settings::instance().evaluationDate() = referenceDate;
2019
2020 // Store the individual models that will be fed to the CAM.
2021 vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
2022
2023 // IR parameterisations
2024 addSingleIrModel(flatVols, EURCurrency(), eurYts, 0.02, 0.0050, 0.0080, singleModels);
2025 addSingleIrModel(flatVols, USDCurrency(), usdYts, 0.03, 0.0030, 0.0110, singleModels);
2026 addSingleIrModel(flatVols, GBPCurrency(), gbpYts, 0.04, 0.0070, 0.0095, singleModels);
2027
2028 // FX parameterisations
2029 addSingleFxModel(flatVols, USDCurrency(), fxEurUsd, 0.15, 0.20, singleModels);
2030 addSingleFxModel(flatVols, GBPCurrency(), fxEurGbp, 0.10, 0.15, singleModels);
2031
2032 // Add the inflation parameterisations.
2033 vector<Date> infDates{ Date(30, April, 2015), Date(30, July, 2015) };
2034 vector<Real> infRates{ 0.01, 0.01 };
2035
2036 infEurTs = Handle<ZeroInflationTermStructure>(QuantLib::ext::make_shared<ZeroInflationCurve>(
2037 referenceDate, TARGET(), dc, 3 * Months, Monthly, infDates, infRates));
2038 infEurTs->enableExtrapolation();
2039
2040 infLag = inflationYearFraction(Monthly, false, dc, infEurTs->baseDate(), infEurTs->referenceDate());
2041
2042 Real infEurAlpha = 0.01;
2043 Real infEurKappa = 0.01;
2044 if (infEurIsDK) {
2045 singleModels.push_back(QuantLib::ext::make_shared<InfDkConstantParametrization>(
2046 EURCurrency(), infEurTs, infEurAlpha, infEurKappa));
2047 } else {
2048 Real infEurSigma = 0.15;
2049 Handle<Quote> baseCpiQuote(QuantLib::ext::make_shared<SimpleQuote>(1.0));
2050 auto index = QuantLib::ext::make_shared<EUHICP>(false);
2051 auto realRateParam = QuantLib::ext::make_shared<Lgm1fConstantParametrization<ZeroInflationTermStructure>>(
2052 EURCurrency(), infEurTs, infEurAlpha, infEurKappa);
2053 auto indexParam = QuantLib::ext::make_shared<FxBsConstantParametrization>(
2054 EURCurrency(), baseCpiQuote, infEurSigma);
2055 singleModels.push_back(QuantLib::ext::make_shared<InfJyParameterization>(realRateParam, indexParam, index));
2056 }
2057
2058 infGbpTs = Handle<ZeroInflationTermStructure>(QuantLib::ext::make_shared<ZeroInflationCurve>(referenceDate,
2059 UnitedKingdom(), dc, 3 * Months, Monthly, infDates, infRates));
2060 infGbpTs->enableExtrapolation();
2061
2062 Real infGbpAlpha = 0.01;
2063 Real infGbpKappa = 0.01;
2064 if (infGbpIsDK) {
2065 singleModels.push_back(QuantLib::ext::make_shared<InfDkConstantParametrization>(
2066 GBPCurrency(), infGbpTs, infGbpAlpha, infGbpKappa));
2067 } else {
2068 Real infGbpSigma = 0.10;
2069 Handle<Quote> baseCpiQuote(QuantLib::ext::make_shared<SimpleQuote>(1.0));
2070 auto index = QuantLib::ext::make_shared<UKRPI>(false);
2071 auto realRateParam = QuantLib::ext::make_shared<Lgm1fConstantParametrization<ZeroInflationTermStructure>>(
2072 GBPCurrency(), infGbpTs, infGbpAlpha, infGbpKappa);
2073 auto indexParam = QuantLib::ext::make_shared<FxBsConstantParametrization>(
2074 GBPCurrency(), baseCpiQuote, infGbpSigma);
2075 singleModels.push_back(QuantLib::ext::make_shared<InfJyParameterization>(realRateParam, indexParam, index));
2076 }
2077
2078 // Add the credit parameterisation.
2079 singleModels.push_back(QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(
2080 EURCurrency(), n1Ts, 0.01, 0.01));
2081
2082 // Add commodity parameterisations
2083 bool df = driftFreeState;
2084 comParametrizationA = QuantLib::ext::make_shared<CommoditySchwartzParametrization>(USDCurrency(), "WTI", comTS, fxEurUsd, 0.1, 0.05, df);
2085 comParametrizationB = QuantLib::ext::make_shared<CommoditySchwartzParametrization>(USDCurrency(), "NG", comTS, fxEurUsd, 0.15, 0.05, df);
2086 comModelA = QuantLib::ext::make_shared<CommoditySchwartzModel>(comParametrizationA);
2087 comModelB = QuantLib::ext::make_shared<CommoditySchwartzModel>(comParametrizationB);
2088 singleModels.push_back(comParametrizationA);
2089 singleModels.push_back(comParametrizationB);
2090
2091 // Create the correlation matrix.
2092 Matrix c = createCorrelation(infEurIsDK, infGbpIsDK);
2093 BOOST_TEST_MESSAGE("correlation matrix is\n" << c);
2094
2095 BOOST_TEST_MESSAGE("creating CAM with exact discretization");
2096 modelExact = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
2097 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
2098 BOOST_TEST_MESSAGE("creating CAM with Euler discretization");
2099 modelEuler = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
2100 IrModel::Measure::LGM, CrossAssetModel::Discretization::Euler);
2101 BOOST_TEST_MESSAGE("test date done");
2102 }
2103
2104 // Add an IR model with flat or time depending volatilities.
2105 void addSingleIrModel(bool flatVols, const Currency& ccy, Handle<YieldTermStructure> yts, Real kappa,
2106 Volatility lowerBound, Volatility upperBound,
2107 vector<QuantLib::ext::shared_ptr<Parametrization> >& singleModels) {
2108
2109 // If use flat vols, add the parameterisation and return.
2110 if (flatVols) {
2111 singleModels.push_back(QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(ccy, yts, lowerBound, kappa));
2112 return;
2113 }
2114
2115 // Use non-flat volatilities
2116 static vector<Date> vDates{
2117 Date(15, July, 2016),
2118 Date(15, July, 2017),
2119 Date(15, July, 2018),
2120 Date(15, July, 2019),
2121 Date(15, July, 2020)
2122 };
2123
2124 Array vTimes(vDates.size());
2125 for (Size i = 0; i < vTimes.size(); ++i) {
2126 vTimes[i] = yts->timeFromReference(vDates[i]);
2127 }
2128
2129 Array vols(vDates.size() + 1);
2130 for (Size i = 0; i < vols.size(); ++i) {
2131 vols[i] = lowerBound + (upperBound - lowerBound) * std::exp(-0.3 * i);
2132 }
2133
2134 singleModels.push_back(QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(
2135 ccy, yts, vTimes, vols, Array(), Array(1, kappa)));
2136 }
2137
2138 // Add an FX model with flat or time depending volatilities.
2139 void addSingleFxModel(bool flatVols, const Currency& ccy, Handle<Quote> spot,
2140 Volatility lowerBound, Volatility upperBound,
2141 vector<QuantLib::ext::shared_ptr<Parametrization> >& singleModels) {
2142
2143 // If use flat vols, add the parameterisation and return.
2144 if (flatVols) {
2145 singleModels.push_back(QuantLib::ext::make_shared<FxBsConstantParametrization>(ccy, spot, lowerBound));
2146 return;
2147 }
2148
2149 // Use non-flat volatilities
2150 static vector<Date> vDates{
2151 Date(15, July, 2016),
2152 Date(15, October, 2016),
2153 Date(15, May, 2017),
2154 Date(13, September, 2017),
2155 Date(15, July, 2018)
2156 };
2157
2158 Array vTimes(vDates.size());
2159 for (Size i = 0; i < vTimes.size(); ++i) {
2160 vTimes[i] = dc.yearFraction(referenceDate, vDates[i]);
2161 }
2162
2163 Array vols(vDates.size() + 1);
2164 for (Size i = 0; i < vols.size(); ++i) {
2165 vols[i] = lowerBound + (upperBound - lowerBound) * std::exp(-0.3 * i);
2166 }
2167
2168 singleModels.push_back(QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
2169 ccy, spot, vTimes, vols));
2170 }
2171
2172 // Create the correlation matrix.
2173 Matrix createCorrelation(bool infEurIsDK, bool infGbpIsDK) {
2174
2175 // Four different correlation matrices depending on type of two inflation components.
2176 vector<vector<Real>> tmp;
2177 if (infEurIsDK && infGbpIsDK) {
2178 tmp = {
2179 // EUR USD GBP FX1 FX2 INF_EUR INF_GBP CR COM1 COM2
2180 { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // EUR
2181 { 0.6, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // USD
2182 { 0.3, 0.1, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // GBP
2183 { 0.2, 0.2, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX1
2184 { 0.3, 0.1, 0.1, 0.3, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX2
2185 { 0.8, 0.2, 0.1, 0.4, 0.2, 1.0, 0.0, 0.0, 0.0, 0.0 }, // INF_EUR
2186 { 0.6, 0.1, 0.2, 0.2, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0 }, // INF_GBP
2187 { 0.3, 0.2, 0.1, 0.1, 0.3, 0.4, 0.2, 1.0, 0.0, 0.0 }, // CR
2188 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 }, // COM1
2189 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0 } // COM2
2190 };
2191 } else if (!infEurIsDK && infGbpIsDK) {
2192 tmp = {
2193 {1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_EUR
2194 {0.600, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_USD
2195 {0.300, 0.100, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_GBP
2196 {0.200, 0.200, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURUSD
2197 {0.300, 0.100, 0.100, 0.300, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURGBP
2198 {0.400, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // INF_EUR_RR
2199 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000}, // INF_EUR_IDX
2200 {0.000, 0.000, 0.600, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000}, // INF_GBP
2201 {0.300, 0.200, 0.100, 0.100, 0.300, 0.400, 0.000, 0.200, 1.000, 0.000, 0.000}, // CR
2202 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000}, // COM1
2203 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.500, 1.000} // COM2
2204 };
2205 } else if (infEurIsDK && !infGbpIsDK) {
2206 tmp = {
2207 {1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_EUR
2208 {0.600, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_USD
2209 {0.300, 0.100, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_GBP
2210 {0.200, 0.200, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURUSD
2211 {0.300, 0.100, 0.100, 0.300, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURGBP
2212 {0.600, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // INF_EUR
2213 {0.000, 0.000, 0.400, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000}, // INF_GBP_RR
2214 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000}, // INF_GBP_IDX
2215 {0.300, 0.200, 0.100, 0.100, 0.300, 0.400, 0.200, 0.000, 1.000, 0.000, 0.000}, // CR
2216 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000}, // COM1
2217 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.500, 1.000} // COM2
2218 };
2219 } else {
2220 tmp = {
2221 {1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_EUR
2222 {0.600, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_USD
2223 {0.300, 0.100, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // IR_GBP
2224 {0.200, 0.200, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURUSD
2225 {0.300, 0.100, 0.100, 0.300, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // FX_EURGBP
2226 {0.600, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // INF_EUR_RR
2227 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000}, // INF_EUR_IDX
2228 {0.000, 0.000, 0.600, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000}, // INF_GBP_RR
2229 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000}, // INF_GBP_IDX
2230 {0.300, 0.200, 0.100, 0.100, 0.300, 0.400, 0.000, 0.200, 0.000, 1.000, 0.000, 0.000}, // CR
2231 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 0.000}, // COM1
2232 {0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.500, 1.000} // COM2
2233 };
2234 }
2235
2236 Matrix c(tmp.size(), tmp.size());
2237 for (Size i = 0; i < tmp.size(); ++i) {
2238 for (Size j = 0; j <= i; ++j) {
2239 c[i][j] = c[j][i] = tmp[i][j];
2240 }
2241 }
2242
2243 return c;
2244 }
2245
2246 SavedSettings backup;
2247 Date referenceDate;
2248 DayCounter dc;
2249
2250 // IR
2251 Handle<YieldTermStructure> eurYts;
2252 Handle<YieldTermStructure> usdYts;
2253 Handle<YieldTermStructure> gbpYts;
2254
2255 // FX
2256 Handle<Quote> fxEurUsd;
2257 Handle<Quote> fxEurGbp;
2258
2259 // Inflation
2260 Handle<ZeroInflationTermStructure> infEurTs;
2261 Handle<ZeroInflationTermStructure> infGbpTs;
2262 Real infLag;
2263
2264 // Credit
2265 Handle<DefaultProbabilityTermStructure> n1Ts;
2266
2267 // Commodities
2268 Handle<QuantExt::PriceTermStructure> comTS;
2269 QuantLib::ext::shared_ptr<CommoditySchwartzParametrization> comParametrizationA, comParametrizationB;
2270 QuantLib::ext::shared_ptr<CommoditySchwartzModel> comModelA, comModelB;
2271 QuantLib::ext::shared_ptr<CommoditySchwartzStateProcess> comProcessA, comProcessB;
2272
2273 // Model
2274 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact, modelEuler;
2275
2276}; // IrFxInfCrModelTestData
2277
2278} // anonymous namespace
2279
2280// Test case options
2281vector<bool> infEurFlags{ true, false };
2282vector<bool> infGbpFlags{ true, false };
2283vector<bool> flatVolsFlags{ true, false };
2284vector<bool> driftFreeState{ true, false };
2285
2286BOOST_DATA_TEST_CASE(testIrFxInfCrComMartingaleProperty,
2287 bdata::make(infEurFlags) * bdata::make(infGbpFlags) * bdata::make(flatVolsFlags) * bdata::make(driftFreeState),
2288 infEurIsDk, infGbpIsDk, flatVols, driftFreeState) {
2289
2290 BOOST_TEST_MESSAGE("Testing martingale property in ir-fx-inf-cr-com model for Euler and exact discretizations...");
2291 BOOST_TEST_MESSAGE("EUR inflation model is: " << (infEurIsDk ? "DK" : "JY"));
2292 BOOST_TEST_MESSAGE("GBP inflation model is: " << (infGbpIsDk ? "DK" : "JY"));
2293 BOOST_TEST_MESSAGE("Using " << (flatVols ? "" : "non-") << "flat volatilities.");
2294
2295 IrFxInfCrComModelTestData d(infEurIsDk, infGbpIsDk, flatVols, driftFreeState);
2296
2297 BOOST_TEST_MESSAGE("get exact state process");
2298 QuantLib::ext::shared_ptr<StochasticProcess> process1 = d.modelExact->stateProcess();
2299 BOOST_TEST_MESSAGE("get Euler state process");
2300 QuantLib::ext::shared_ptr<StochasticProcess> process2 = d.modelEuler->stateProcess();
2301
2302 Size n = 5000; // number of paths
2303 Size seed = 18; // rng seed
2304 Time T = 2.0; // maturity of payoff
2305 Time T2 = 20.0; // zerobond maturity
2306 Size steps = static_cast<Size>(T * 40); // number of steps taken (euler)
2307
2308 BOOST_TEST_MESSAGE("build sequence generators");
2309 LowDiscrepancy::rsg_type sg1 = LowDiscrepancy::make_sequence_generator(process1->factors() * 1, seed);
2310 LowDiscrepancy::rsg_type sg2 = LowDiscrepancy::make_sequence_generator(process2->factors() * steps, seed);
2311
2312 BOOST_TEST_MESSAGE("build multi path generator");
2313 TimeGrid grid1(T, 1);
2314 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process1)) {
2315 tmp->resetCache(grid1.size() - 1);
2316 }
2317 MultiPathGenerator<LowDiscrepancy::rsg_type> pg1(process1, grid1, sg1, false);
2318 TimeGrid grid2(T, steps);
2319 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process2)) {
2320 tmp->resetCache(grid2.size() - 1);
2321 }
2322 MultiPathGenerator<LowDiscrepancy::rsg_type> pg2(process2, grid2, sg2, false);
2323
2324 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb1, usdzb1, gbpzb1, infeur1, infgbp1,
2325 n1eur1, commodityA_1, commodityB_1;
2326 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb2, usdzb2, gbpzb2, infeur2, infgbp2,
2327 n1eur2, commodityA_2, commodityB_2;
2328
2329 for (Size j = 0; j < n; ++j) {
2330 Sample<MultiPath> path2 = pg2.next();
2331 Size l2 = path2.value[0].length() - 1;
2332 Sample<MultiPath> path1 = pg1.next();
2333 Size l1 = path1.value[0].length() - 1;
2334 Real zeur1 = path1.value[0][l1];
2335 Real zusd1 = path1.value[1][l1];
2336 Real zgbp1 = path1.value[2][l1];
2337 Real fxusd1 = std::exp(path1.value[3][l1]);
2338 Real fxgbp1 = std::exp(path1.value[4][l1]);
2339 Real infeurz1 = path1.value[5][l1];
2340 Real infeury1 = path1.value[6][l1];
2341 Real infgbpz1 = path1.value[7][l1];
2342 Real infgbpy1 = path1.value[8][l1];
2343 Real crzn11 = path1.value[9][l1];
2344 Real cryn11 = path1.value[10][l1];
2345 Real coma1 = path1.value[11][l1];
2346 Real comb1 = path1.value[12][l1];
2347 Real zeur2 = path2.value[0][l2];
2348 Real zusd2 = path2.value[1][l2];
2349 Real zgbp2 = path2.value[2][l2];
2350 Real fxusd2 = std::exp(path2.value[3][l2]);
2351 Real fxgbp2 = std::exp(path2.value[4][l2]);
2352 Real infeurz2 = path2.value[5][l2];
2353 Real infeury2 = path2.value[6][l2];
2354 Real infgbpz2 = path2.value[7][l2];
2355 Real infgbpy2 = path2.value[8][l2];
2356 Real crzn12 = path2.value[9][l2];
2357 Real cryn12 = path2.value[10][l2];
2358 Real coma2 = path2.value[11][l2];
2359 Real comb2 = path2.value[12][l2];
2360 // EUR zerobond
2361 eurzb1(d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
2362 // USD zerobond
2363 usdzb1(d.modelExact->discountBond(1, T, T2, zusd1) * fxusd1 / d.modelExact->numeraire(0, T, zeur1));
2364 // GBP zerobond
2365 gbpzb1(d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 / d.modelExact->numeraire(0, T, zeur1));
2366 // EUR CPI indexed bond
2367 bool indexIsInterpolated = true;
2368 if (infEurIsDk) {
2369 std::pair<Real, Real> sinfeur1 = d.modelExact->infdkI(0, T, T2, infeurz1, infeury1);
2370 infeur1(sinfeur1.first * sinfeur1.second * d.modelExact->discountBond(0, T, T2, zeur1) /
2371 d.modelExact->numeraire(0, T, zeur1));
2372 } else {
2373 infeur1(exp(infeury1) * inflationGrowth(d.modelExact, 0, T, T2, zeur1, infeurz1, indexIsInterpolated) *
2374 d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
2375 }
2376 // GBP CPI indexed bond
2377 if (infGbpIsDk) {
2378 std::pair<Real, Real> sinfgbp1 = d.modelExact->infdkI(1, T, T2, infgbpz1, infgbpy1);
2379 infgbp1(sinfgbp1.first * sinfgbp1.second * d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 /
2380 d.modelExact->numeraire(0, T, zeur1));
2381 } else {
2382 infgbp1(exp(infgbpy1) * inflationGrowth(d.modelExact, 1, T, T2, zgbp1, infgbpz1, indexIsInterpolated) *
2383 d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 / d.modelExact->numeraire(0, T, zeur1));
2384 }
2385 // EUR defaultable zerobond
2386 std::pair<Real, Real> sn11 = d.modelExact->crlgm1fS(0, 0, T, T2, crzn11, cryn11);
2387 n1eur1(sn11.first * sn11.second * d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
2388
2389 // EUR zerobond
2390 eurzb2(d.modelExact->discountBond(0, T, T2, zeur2) / d.modelExact->numeraire(0, T, zeur2));
2391 // USD zerobond
2392 usdzb2(d.modelExact->discountBond(1, T, T2, zusd2) * fxusd2 / d.modelExact->numeraire(0, T, zeur2));
2393 // GBP zerobond
2394 gbpzb2(d.modelExact->discountBond(2, T, T2, zgbp2) * fxgbp2 / d.modelExact->numeraire(0, T, zeur2));
2395 // EUR CPI indexed bond
2396 if (infEurIsDk) {
2397 std::pair<Real, Real> sinfeur2 = d.modelExact->infdkI(0, T, T2, infeurz2, infeury2);
2398 infeur2(sinfeur2.first * sinfeur2.second * d.modelExact->discountBond(0, T, T2, zeur2) /
2399 d.modelExact->numeraire(0, T, zeur2));
2400 } else {
2401 infeur2(exp(infeury2) * inflationGrowth(d.modelExact, 0, T, T2, zeur2, infeurz2, indexIsInterpolated) *
2402 d.modelExact->discountBond(0, T, T2, zeur2) / d.modelExact->numeraire(0, T, zeur2));
2403 }
2404 // GBP CPI indexed bond
2405 if (infGbpIsDk) {
2406 std::pair<Real, Real> sinfgbp2 = d.modelExact->infdkI(1, T, T2, infgbpz2, infgbpy2);
2407 infgbp2(sinfgbp2.first * sinfgbp2.second * d.modelExact->discountBond(2, T, T2, zgbp2) * fxgbp2 /
2408 d.modelExact->numeraire(0, T, zeur2));
2409 } else {
2410 infgbp2(exp(infgbpy2) * inflationGrowth(d.modelExact, 1, T, T2, zgbp2, infgbpz2, indexIsInterpolated) *
2411 d.modelExact->discountBond(2, T, T2, zgbp2) * fxgbp2 / d.modelExact->numeraire(0, T, zeur2));
2412 }
2413 // EUR defaultable zerobond
2414 std::pair<Real, Real> sn12 = d.modelExact->crlgm1fS(0, 0, T, T2, crzn12, cryn12);
2415 n1eur2(sn12.first * sn12.second * d.modelExact->discountBond(0, T, T2, zeur2) / d.modelExact->numeraire(0, T, zeur2));
2416 // commodity forward prices
2417 commodityA_1(d.comModelA->forwardPrice(T, T2, Array(1, coma1)));
2418 commodityB_1(d.comModelB->forwardPrice(T, T2, Array(1, comb1)));
2419 commodityA_2(d.comModelA->forwardPrice(T, T2, Array(1, coma2)));
2420 commodityB_2(d.comModelB->forwardPrice(T, T2, Array(1, comb2)));
2421 }
2422
2423 BOOST_TEST_MESSAGE("EXACT:");
2424 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb1) << " +- " << error_of<tag::mean>(eurzb1) << " vs analytical "
2425 << d.eurYts->discount(T2));
2426 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb1) << " +- " << error_of<tag::mean>(usdzb1) << " vs analytical "
2427 << d.usdYts->discount(T2) * d.fxEurUsd->value());
2428 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb1) << " +- " << error_of<tag::mean>(gbpzb1) << " vs analytical "
2429 << d.gbpYts->discount(T2) * d.fxEurGbp->value());
2430 BOOST_TEST_MESSAGE("IDX zb EUR = " << mean(infeur1) << " +- " << error_of<tag::mean>(infeur1) << " vs analytical "
2431 << d.eurYts->discount(T2) *
2432 std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2));
2433 BOOST_TEST_MESSAGE("IDX zb GBP = " << mean(infgbp1) << " +- " << error_of<tag::mean>(infgbp1) << " vs analytical "
2434 << d.gbpYts->discount(T2) *
2435 std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) *
2436 d.fxEurGbp->value());
2437 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur1) << " +- " << error_of<tag::mean>(n1eur1) << " vs analytical "
2438 << d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2));
2439
2440 BOOST_TEST_MESSAGE("\nEULER:");
2441 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb2) << " +- " << error_of<tag::mean>(eurzb2) << " vs analytical "
2442 << d.eurYts->discount(T2));
2443 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb2) << " +- " << error_of<tag::mean>(usdzb2) << " vs analytical "
2444 << d.usdYts->discount(T2) * d.fxEurUsd->value());
2445 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb2) << " +- " << error_of<tag::mean>(gbpzb2) << " vs analytical "
2446 << d.gbpYts->discount(T2) * d.fxEurGbp->value());
2447 BOOST_TEST_MESSAGE("IDX zb EUR = " << mean(infeur2) << " +- " << error_of<tag::mean>(infeur2) << " vs analytical "
2448 << d.eurYts->discount(T2) *
2449 std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2));
2450 BOOST_TEST_MESSAGE("IDX zb GBP = " << mean(infgbp2) << " +- " << error_of<tag::mean>(infgbp2) << " vs analytical "
2451 << d.gbpYts->discount(T2) *
2452 std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) *
2453 d.fxEurGbp->value());
2454 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur2) << " +- " << error_of<tag::mean>(n1eur2) << " vs analytical "
2455 << d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2));
2456
2457 // a bit higher than for plain zero bond , since we look at indexed zero
2458 // bonds, too
2459 Real tol1 = 5.0E-4; // EXACT
2460 Real tol2 = 14.0E-4; // EULER
2461
2462 Real ev = d.eurYts->discount(T2);
2463 if (std::abs(mean(eurzb1) - ev) > tol1)
2464 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
2465 "expected "
2466 << ev << ", got " << mean(eurzb1) << ", tolerance " << tol1);
2467 ev = d.usdYts->discount(T2) * d.fxEurUsd->value();
2468 if (std::abs(mean(usdzb1) - ev) > tol1)
2469 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
2470 "expected "
2471 << ev << ", got " << mean(usdzb1) << ", tolerance " << tol1);
2472 ev = d.gbpYts->discount(T2) * d.fxEurGbp->value();
2473 if (std::abs(mean(gbpzb1) - ev) > tol1)
2474 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
2475 "expected "
2476 << ev << ", got " << mean(gbpzb1) << ", tolerance " << tol1);
2477 ev = d.eurYts->discount(T2) * std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2);
2478 if (std::abs(mean(infeur1) - ev) > tol1)
2479 BOOST_TEST_ERROR("Martingale test failed for idx eurzb (exact discr.),"
2480 "expected "
2481 << ev << ", got " << mean(infeur1) << ", tolerance " << tol1);
2482 ev = d.gbpYts->discount(T2) * std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) * d.fxEurGbp->value();
2483 if (std::abs(mean(infgbp1) - ev) > tol1)
2484 BOOST_TEST_ERROR("Martingale test failed for idx gbpzb (exact discr.),"
2485 "expected "
2486 << ev << ", got " << mean(infgbp1) << ", tolerance " << tol1);
2487 ev = d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2);
2488 if (std::abs(mean(n1eur1) - ev) > tol1)
2489 BOOST_TEST_ERROR("Martingale test failed for def eurzb (exact discr.),"
2490 "expected "
2491 << ev << ", got " << mean(n1eur1) << ", tolerance " << tol1);
2492
2493 ev = d.eurYts->discount(T2);
2494 if (std::abs(mean(eurzb2) - ev) > tol2)
2495 BOOST_TEST_ERROR("Martingale test failed for eurzb (Euler discr.),"
2496 "expected "
2497 << ev << ", got " << mean(eurzb2) << ", tolerance " << tol2);
2498 ev = d.usdYts->discount(T2) * d.fxEurUsd->value();
2499 if (std::abs(mean(usdzb2) - ev) > tol2)
2500 BOOST_TEST_ERROR("Martingale test failed for usdzb (Euler discr.),"
2501 "expected "
2502 << ev << ", got " << mean(usdzb2) << ", tolerance " << tol2 * error_of<tag::mean>(usdzb2));
2503 ev = d.gbpYts->discount(T2) * d.fxEurGbp->value();
2504 if (std::abs(mean(gbpzb2) - ev) > tol2)
2505 BOOST_TEST_ERROR("Martingale test failed for gbpzb (Euler discr.),"
2506 "expected "
2507 << ev << ", got " << mean(gbpzb2) << ", tolerance " << tol2);
2508 ev = d.eurYts->discount(T2) * std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2);
2509 if (std::abs(mean(infeur2) - ev) > tol2)
2510 BOOST_TEST_ERROR("Martingale test failed for idx eurzb (Euler discr.),"
2511 "expected "
2512 << ev << ", got " << mean(infeur2) << ", tolerance " << tol1);
2513 ev = d.gbpYts->discount(T2) * std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) * d.fxEurGbp->value();
2514 if (std::abs(mean(infgbp2) - ev) > tol2)
2515 BOOST_TEST_ERROR("Martingale test failed for idx gbpzb (Euler discr.),"
2516 "expected "
2517 << ev << ", got " << mean(infgbp2) << ", tolerance " << tol1);
2518 ev = d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2);
2519 if (std::abs(mean(n1eur2) - ev) > tol2)
2520 BOOST_TEST_ERROR("Martingale test failed for def eurzb (Euler discr.),"
2521 "expected "
2522 << ev << ", got " << mean(n1eur1) << ", tolerance " << tol1);
2523
2524 // commodity A forward prices
2525 Real tol;
2526 ev = d.comParametrizationA->priceCurve()->price(T2);
2527 tol = error_of<tag::mean>(commodityA_1);
2528 if (std::abs(mean(commodityA_1) - ev) > tol)
2529 BOOST_TEST_ERROR("Martingale test failed for commodity A (exact discr.),"
2530 "expected " << ev << ", got " << mean(commodityA_1) << " +- " << tol);
2531 tol = error_of<tag::mean>(commodityA_2);
2532 if (std::abs(mean(commodityA_2) - ev) > tol)
2533 BOOST_TEST_ERROR("Martingale test failed for commodity A (Euler discr.),"
2534 "expected " << ev << ", got " << mean(commodityA_2) << " +- " << tol);
2535
2536 // commodity B forward prices
2537 ev = d.comParametrizationB->priceCurve()->price(T2);
2538 tol = error_of<tag::mean>(commodityB_1);
2539 if (std::abs(mean(commodityB_1) - ev) > tol)
2540 BOOST_TEST_ERROR("Martingale test failed for commodity B (exact discr.),"
2541 "expected " << ev << ", got " << mean(commodityB_1) << " +- " << tol);
2542 tol = error_of<tag::mean>(commodityB_2);
2543 if (std::abs(mean(commodityB_2) - ev) > tol)
2544 BOOST_TEST_ERROR("Martingale test failed for commodity B (Euler discr.),"
2545 "expected " << ev << ", got " << mean(commodityB_2) << " +- " << tol);
2546
2547
2548} // testIrFxInfCrMartingaleProperty
2549
2550BOOST_DATA_TEST_CASE(testIrFxInfCrComMoments,
2551 bdata::make(infEurFlags) * bdata::make(infGbpFlags) * bdata::make(flatVolsFlags) * bdata::make(driftFreeState),
2552 infEurIsDk, infGbpIsDk, flatVols, driftFreeState) {
2553
2554 BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization in ir-fx-inf-cr-com model...");
2555 BOOST_TEST_MESSAGE("EUR inflation model is: " << (infEurIsDk ? "DK" : "JY"));
2556 BOOST_TEST_MESSAGE("GBP inflation model is: " << (infGbpIsDk ? "DK" : "JY"));
2557 BOOST_TEST_MESSAGE("Using " << (flatVols ? "" : "non-") << "flat volatilities.");
2558
2559 IrFxInfCrComModelTestData d(infEurIsDk, infGbpIsDk, flatVols, driftFreeState);
2560
2561 Size n = d.modelExact->dimension();
2562
2563 QuantLib::ext::shared_ptr<StochasticProcess> p_exact = d.modelExact->stateProcess();
2564 QuantLib::ext::shared_ptr<StochasticProcess> p_euler = d.modelExact->stateProcess();
2565
2566 Real T = 2.0; // horizon at which we compare the moments
2567 Size steps = static_cast<Size>(T * 10); // number of simulation steps (Euler and exact)
2568 Size paths = 10000; // number of paths
2569
2570 Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
2571 Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
2572
2573 Size seed = 18;
2574 TimeGrid grid(T, steps);
2575
2576 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
2577 tmp->resetCache(grid.size() - 1);
2578 }
2579 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
2580 tmp->resetCache(grid.size() - 1);
2581 }
2582 MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid, SobolBrownianGenerator::Diagonal, seed,
2583 SobolRsg::JoeKuoD7);
2584 MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid, SobolBrownianGenerator::Diagonal, seed,
2585 SobolRsg::JoeKuoD7);
2586
2587 vector<accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > > e_eu(n);
2588 vector<accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > > e_eu2(n);
2589 vector<vector<accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > > > v_eu(n);
2590 vector<vector<accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > > > v_eu2(n);
2591 for (Size i = 0; i < n; ++i) {
2592 v_eu[i] = vector<accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > >(n);
2593 v_eu2[i] = vector<accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > >(n);
2594 }
2595
2596 for (Size i = 0; i < paths; ++i) {
2597 Sample<MultiPath> path = pgen.next();
2598 Sample<MultiPath> path2 = pgen2.next();
2599 for (Size ii = 0; ii < n; ++ii) {
2600 Real cii = path.value[ii].back();
2601 Real cii2 = path2.value[ii].back();
2602 e_eu[ii](cii);
2603 e_eu2[ii](cii2);
2604 for (Size jj = 0; jj <= ii; ++jj) {
2605 Real cjj = path.value[jj].back();
2606 v_eu[ii][jj](cii, covariate1 = cjj);
2607 Real cjj2 = path2.value[jj].back();
2608 v_eu2[ii][jj](cii2, covariate1 = cjj2);
2609 }
2610 }
2611 }
2612
2613 for (Size i = 0; i < n; ++i) {
2614 BOOST_TEST_MESSAGE("E_" << i << " " << std::fixed << std::setprecision(12) <<
2615 e_an[i] << " " << mean(e_eu[i]) << " " << mean(e_eu2[i]));
2616 }
2617 BOOST_TEST_MESSAGE("==================");
2618
2619 BOOST_TEST_MESSAGE("one step analytical");
2620 for (Size i = 0; i < n; ++i) {
2621 std::ostringstream tmp;
2622 for (Size j = 0; j <= i; ++j) {
2623 tmp << v_an[i][j] << " ";
2624 }
2625 BOOST_TEST_MESSAGE(tmp.str());
2626 }
2627 BOOST_TEST_MESSAGE("==================");
2628
2629 BOOST_TEST_MESSAGE("euler");
2630 for (Size i = 0; i < n; ++i) {
2631 std::ostringstream tmp;
2632 for (Size j = 0; j <= i; ++j) {
2633 tmp << boost::accumulators::covariance(v_eu[i][j]) << " ";
2634 }
2635 BOOST_TEST_MESSAGE(tmp.str());
2636 }
2637 BOOST_TEST_MESSAGE("==================");
2638
2639 BOOST_TEST_MESSAGE("exact");
2640 for (Size i = 0; i < n; ++i) {
2641 std::ostringstream tmp;
2642 for (Size j = 0; j <= i; ++j) {
2643 tmp << boost::accumulators::covariance(v_eu2[i][j]) << " ";
2644 }
2645 BOOST_TEST_MESSAGE(tmp.str());
2646 }
2647 BOOST_TEST_MESSAGE("==================");
2648
2649 Real errTolLd[] = { 0.5E-4, 0.5E-4, 0.5E-4, 10.0E-4, 10.0E-4, 1E-4, 1E-4, 1E-4, 1E-4, 1E-4, 1E-4, 1E-4, 1E-4 };
2650
2651 for (Size i = 0; i < n; ++i) {
2652 // check expectation against analytical calculation (Euler)
2653 if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTolLd[i]) {
2654 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
2655 << ") is inconsistent with numerical value (Euler "
2656 "discretization, "
2657 << mean(e_eu[i]) << "), error is "
2658 << e_an[i] - mean(e_eu[i]) << " tolerance is "
2659 << errTolLd[i]);
2660 }
2661 // check expectation against analytical calculation (exact disc)
2662 if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTolLd[i]) {
2663 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
2664 << ") is inconsistent with numerical value (exact "
2665 "discretization, "
2666 << mean(e_eu2[i]) << "), error is "
2667 << e_an[i] - mean(e_eu2[i]) << " tolerance is "
2668 << errTolLd[i]);
2669 }
2670 }
2671
2672 // as above, this is a bit rough compared to the more differentiated
2673 // test of the IR-FX model ...
2674 Real tol = 10.0E-4;
2675
2676 for (Size i = 0; i < n; ++i) {
2677 for (Size j = 0; j <= i; ++j) {
2678 if (std::fabs(boost::accumulators::covariance(v_eu[i][j]) - v_an[i][j]) > tol) {
2679 BOOST_ERROR("analytical covariance at ("
2680 << i << "," << j << ") (" << v_an[i][j]
2681 << ") is inconsistent with numerical "
2682 "value (Euler discretization, "
2683 << boost::accumulators::covariance(v_eu[i][j]) << "), error is "
2684 << v_an[i][j] - boost::accumulators::covariance(v_eu[i][j]) << " tolerance is " << tol);
2685 }
2686 if (std::fabs(boost::accumulators::covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
2687 BOOST_ERROR("analytical covariance at ("
2688 << i << "," << j << ") (" << v_an[i][j]
2689 << ") is inconsistent with numerical "
2690 "value (exact discretization, "
2691 << boost::accumulators::covariance(v_eu2[i][j]) << "), error is "
2692 << v_an[i][j] - boost::accumulators::covariance(v_eu2[i][j]) << " tolerance is " << tol);
2693 }
2694 }
2695 }
2696
2697} // testIrFxInfCrMoments
2698
2699namespace {
2700
2701struct IrFxInfCrEqModelTestData {
2702 IrFxInfCrEqModelTestData()
2703 : referenceDate(30, July, 2015), eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed())),
2704 usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed())),
2705 gbpYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.04, Actual365Fixed())),
2706 fxEurUsd(QuantLib::ext::make_shared<SimpleQuote>(0.90)), fxEurGbp(QuantLib::ext::make_shared<SimpleQuote>(1.35)),
2707 fxEurEur(QuantLib::ext::make_shared<SimpleQuote>(1.00)), infEurAlpha(0.01), infEurKappa(0.01), infGbpAlpha(0.01),
2708 infGbpKappa(0.01), n1Ts(QuantLib::ext::make_shared<FlatHazardRate>(referenceDate, 0.01, Actual365Fixed())),
2709 n1Alpha(0.01), n1Kappa(0.01), spSpotToday(QuantLib::ext::make_shared<SimpleQuote>(2100)),
2710 lhSpotToday(QuantLib::ext::make_shared<SimpleQuote>(12.50)),
2711 eqDivSp(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.01, Actual365Fixed())),
2712 eqDivLh(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.0075, Actual365Fixed())), c(10, 10, 0.0) {
2713
2714 std::vector<Date> infDates;
2715 std::vector<Real> infRates;
2716 infDates.push_back(Date(30, April, 2015));
2717 infDates.push_back(Date(30, July, 2015));
2718 infRates.push_back(0.01);
2719 infRates.push_back(0.01);
2720 infEurTs = Handle<ZeroInflationTermStructure>(QuantLib::ext::make_shared<ZeroInflationCurve>(
2721 referenceDate, TARGET(), Actual365Fixed(), 3 * Months, Monthly, infDates, infRates));
2722 infGbpTs = Handle<ZeroInflationTermStructure>(QuantLib::ext::make_shared<ZeroInflationCurve>(
2723 referenceDate, UnitedKingdom(), Actual365Fixed(), 3 * Months, Monthly, infDates, infRates));
2724 infEurTs->enableExtrapolation();
2725 infGbpTs->enableExtrapolation();
2726 // same for eur and gbp (doesn't matter anyway, since we are
2727 // using flat ts here)
2728 infLag =
2729 inflationYearFraction(Monthly, false, Actual365Fixed(), infEurTs->baseDate(), infEurTs->referenceDate());
2730
2731 Settings::instance().evaluationDate() = referenceDate;
2732 volstepdates.push_back(Date(15, July, 2016));
2733 volstepdates.push_back(Date(15, July, 2017));
2734 volstepdates.push_back(Date(15, July, 2018));
2735 volstepdates.push_back(Date(15, July, 2019));
2736 volstepdates.push_back(Date(15, July, 2020));
2737
2738 volstepdatesFx.push_back(Date(15, July, 2016));
2739 volstepdatesFx.push_back(Date(15, October, 2016));
2740 volstepdatesFx.push_back(Date(15, May, 2017));
2741 volstepdatesFx.push_back(Date(13, September, 2017));
2742 volstepdatesFx.push_back(Date(15, July, 2018));
2743
2744 volstepdatesEqSp.push_back(Date(13, April, 2016)); // shared with USD
2745 volstepdatesEqSp.push_back(Date(15, October, 2016)); // shared with FX
2746 volstepdatesEqSp.push_back(Date(15, March, 2017));
2747 volstepdatesEqSp.push_back(Date(13, October, 2017));
2748 volstepdatesEqSp.push_back(Date(15, July, 2018)); // shared with EUR and USD
2749 volstepdatesEqSp.push_back(Date(13, October, 2018));
2750
2751 volstepdatesEqLh.push_back(Date(13, June, 2016));
2752 volstepdatesEqLh.push_back(Date(15, September, 2016));
2753 volstepdatesEqLh.push_back(Date(15, April, 2017));
2754 volstepdatesEqLh.push_back(Date(13, October, 2017));
2755 volstepdatesEqLh.push_back(Date(15, July, 2018)); // shared with EUR and USD
2756 volstepdatesEqLh.push_back(Date(13, December, 2018));
2757
2758 volsteptimes_a = Array(volstepdates.size());
2759 volsteptimesFx_a = Array(volstepdatesFx.size());
2760 eqSpTimes = Array(volstepdatesEqSp.size());
2761 eqLhTimes = Array(volstepdatesEqLh.size());
2762
2763 for (Size i = 0; i < volstepdates.size(); ++i) {
2764 volsteptimes_a[i] = eurYts->timeFromReference(volstepdates[i]);
2765 }
2766 for (Size i = 0; i < volstepdatesFx.size(); ++i) {
2767 volsteptimesFx_a[i] = eurYts->timeFromReference(volstepdatesFx[i]);
2768 }
2769 for (Size i = 0; i < eqSpTimes.size(); ++i) {
2770 eqSpTimes[i] = eurYts->timeFromReference(volstepdatesEqSp[i]);
2771 }
2772 for (Size i = 0; i < eqLhTimes.size(); ++i) {
2773 eqLhTimes[i] = eurYts->timeFromReference(volstepdatesEqLh[i]);
2774 }
2775
2776 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
2777 eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
2778 }
2779 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
2780 usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
2781 }
2782 for (Size i = 0; i < volstepdates.size() + 1; ++i) {
2783 gbpVols.push_back(0.0070 + (0.0095 - 0.0070) * std::exp(-0.3 * static_cast<double>(i)));
2784 }
2785 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
2786 fxSigmasUsd.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
2787 }
2788 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
2789 fxSigmasGbp.push_back(0.10 + (0.15 - 0.10) * std::exp(-0.3 * static_cast<double>(i)));
2790 }
2791 for (Size i = 0; i < volstepdatesEqSp.size() + 1; ++i) {
2792 eqSpVols.push_back(0.20 + (0.35 - 0.20) * std::exp(-0.3 * static_cast<double>(i)));
2793 }
2794 for (Size i = 0; i < volstepdatesEqLh.size() + 1; ++i) {
2795 eqLhVols.push_back(0.25 + (0.45 - 0.25) * std::exp(-0.3 * static_cast<double>(i)));
2796 }
2797 eurVols_a = Array(eurVols.begin(), eurVols.end());
2798 usdVols_a = Array(usdVols.begin(), usdVols.end());
2799 gbpVols_a = Array(gbpVols.begin(), gbpVols.end());
2800 fxSigmasUsd_a = Array(fxSigmasUsd.begin(), fxSigmasUsd.end());
2801 fxSigmasGbp_a = Array(fxSigmasGbp.begin(), fxSigmasGbp.end());
2802 spSigmas = Array(eqSpVols.begin(), eqSpVols.end());
2803 lhSigmas = Array(eqLhVols.begin(), eqLhVols.end());
2804
2805 notimes_a = Array(0);
2806 eurKappa_a = Array(1, 0.02);
2807 usdKappa_a = Array(1, 0.03);
2808 gbpKappa_a = Array(1, 0.04);
2809
2810 eurLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), eurYts, volsteptimes_a,
2811 eurVols_a, notimes_a, eurKappa_a);
2812 usdLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(USDCurrency(), usdYts, volsteptimes_a,
2813 usdVols_a, notimes_a, usdKappa_a);
2814 gbpLgm_p = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(GBPCurrency(), gbpYts, volsteptimes_a,
2815 gbpVols_a, notimes_a, gbpKappa_a);
2816
2817 fxUsd_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), fxEurUsd, volsteptimesFx_a,
2818 fxSigmasUsd_a);
2819 fxGbp_p = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(GBPCurrency(), fxEurGbp, volsteptimesFx_a,
2820 fxSigmasGbp_a);
2821
2822 // inflation
2823 infEur_p = QuantLib::ext::make_shared<InfDkConstantParametrization>(EURCurrency(), infEurTs, infEurAlpha, infEurKappa);
2824 infGbp_p = QuantLib::ext::make_shared<InfDkConstantParametrization>(GBPCurrency(), infGbpTs, infGbpAlpha, infGbpKappa);
2825
2826 // credit
2827 n1_p = QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(EURCurrency(), n1Ts, n1Alpha, n1Kappa);
2828
2829 // equity
2830 QuantLib::ext::shared_ptr<EqBsParametrization> eqSpBsParam = QuantLib::ext::make_shared<EqBsPiecewiseConstantParametrization>(
2831 USDCurrency(), "SP", spSpotToday, fxEurUsd, eqSpTimes, spSigmas, usdYts, eqDivSp);
2832
2833 QuantLib::ext::shared_ptr<EqBsParametrization> eqLhBsParam = QuantLib::ext::make_shared<EqBsPiecewiseConstantParametrization>(
2834 EURCurrency(), "LH", lhSpotToday, fxEurEur, eqLhTimes, lhSigmas, eurYts, eqDivLh);
2835
2836 singleModels.push_back(eurLgm_p);
2837 singleModels.push_back(usdLgm_p);
2838 singleModels.push_back(gbpLgm_p);
2839 singleModels.push_back(fxUsd_p);
2840 singleModels.push_back(fxGbp_p);
2841 singleModels.push_back(infEur_p);
2842 singleModels.push_back(infGbp_p);
2843 singleModels.push_back(n1_p);
2844 singleModels.push_back(eqSpBsParam);
2845 singleModels.push_back(eqLhBsParam);
2846
2847 Real tmp[10][10] = {
2848 // EUR USD GBP FX1 FX2 INF_EUR INF_GBP CR EQ1 EQ2
2849 { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // EUR
2850 { 0.6, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // USD
2851 { 0.3, 0.1, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // GBP
2852 { 0.2, 0.2, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX1
2853 { 0.3, 0.1, 0.1, 0.3, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // FX2
2854 { 0.8, 0.2, 0.1, 0.4, 0.2, 1.0, 0.0, 0.0, 0.0, 0.0 }, // INF_EUR
2855 { 0.6, 0.1, 0.2, 0.2, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0 }, // INF_GBP
2856 { 0.3, 0.2, 0.1, 0.1, 0.3, 0.4, 0.2, 1.0, 0.0, 0.0 }, // CR
2857 { 0.1, 0.08, 0.06, 0.04, 0.02, 0.00, -0.02, -0.04, 1.0, 0.0 }, // EQ1
2858 { 0.14, 0.12, 0.10, 0.08, 0.06, 0.04, 0.02, 0.00, -0.02, 1.0 } // EQ2
2859 };
2860
2861 for (Size i = 0; i < 10; ++i) {
2862 for (Size j = 0; j <= i; ++j) {
2863 c[i][j] = c[j][i] = tmp[i][j];
2864 }
2865 }
2866
2867 BOOST_TEST_MESSAGE("correlation matrix is\n" << c);
2868
2869 modelExact = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
2870 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
2871 modelEuler = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, c, SalvagingAlgorithm::None,
2872 IrModel::Measure::LGM, CrossAssetModel::Discretization::Euler);
2873 }
2874
2875 SavedSettings backup;
2876 Date referenceDate;
2877 // ir-fx
2878 Handle<YieldTermStructure> eurYts, usdYts, gbpYts;
2879 std::vector<Date> volstepdates, volstepdatesFx;
2880 Array volsteptimes_a, volsteptimesFx_a;
2881 std::vector<Real> eurVols, usdVols, gbpVols, fxSigmasUsd, fxSigmasGbp;
2882 Handle<Quote> fxEurUsd, fxEurGbp, fxEurEur;
2883 Array eurVols_a, usdVols_a, gbpVols_a, fxSigmasUsd_a, fxSigmasGbp_a;
2884 Array notimes_a, eurKappa_a, usdKappa_a, gbpKappa_a;
2885 QuantLib::ext::shared_ptr<IrLgm1fParametrization> eurLgm_p, usdLgm_p, gbpLgm_p;
2886 QuantLib::ext::shared_ptr<FxBsParametrization> fxUsd_p, fxGbp_p;
2887 // inf
2888 Handle<ZeroInflationTermStructure> infEurTs, infGbpTs;
2889 QuantLib::ext::shared_ptr<InfDkParametrization> infEur_p, infGbp_p;
2890 Real infEurAlpha, infEurKappa, infGbpAlpha, infGbpKappa;
2891 Real infLag;
2892 // cr
2893 Handle<DefaultProbabilityTermStructure> n1Ts, n2Ts, n3Ts;
2894 QuantLib::ext::shared_ptr<CrLgm1fParametrization> n1_p, n2_p, n3_p;
2895 Real n1Alpha, n1Kappa, n2Alpha, n2Kappa, n3Alpha, n3Kappa;
2896 // eq
2897 std::vector<Date> volstepdatesEqSp, volstepdatesEqLh;
2898 std::vector<Real> eqSpVols, eqLhVols;
2899 Array eqSpTimes, spSigmas, eqLhTimes, lhSigmas;
2900 Handle<Quote> spSpotToday, lhSpotToday;
2901 Handle<YieldTermStructure> eqDivSp, eqDivLh;
2902 // model
2903 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
2904 Matrix c;
2905 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact, modelEuler;
2906}; // IrFxInfCrEqModelTestData
2907
2908} // anonymous namespace
2909
2910BOOST_AUTO_TEST_CASE(testIrFxInfCrEqMartingaleProperty) {
2911
2912 BOOST_TEST_MESSAGE("Testing martingale property in ir-fx-inf-cr-eq model for "
2913 "Euler and exact discretizations...");
2914
2915 IrFxInfCrEqModelTestData d;
2916
2917 QuantLib::ext::shared_ptr<StochasticProcess> process1 = d.modelExact->stateProcess();
2918 QuantLib::ext::shared_ptr<StochasticProcess> process2 = d.modelEuler->stateProcess();
2919
2920 Size n = 50000; // number of paths
2921 Size seed = 18; // rng seed
2922 Time T = 2.0; // maturity of payoff
2923 Time T2 = 20.0; // zerobond maturity
2924 Size steps = static_cast<Size>(T * 24); // number of steps taken (euler)
2925
2926 // this can be made more accurate by using LowDiscrepancy instead
2927 // of PseudoRandom, but we use an error estimator for the check
2928 LowDiscrepancy::rsg_type sg1 = LowDiscrepancy::make_sequence_generator(process1->factors() * 1, seed);
2929 LowDiscrepancy::rsg_type sg2 = LowDiscrepancy::make_sequence_generator(process2->factors() * steps, seed);
2930
2931 TimeGrid grid1(T, 1);
2932 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process1)) {
2933 tmp->resetCache(grid1.size() - 1);
2934 }
2935 MultiPathGenerator<LowDiscrepancy::rsg_type> pg1(process1, grid1, sg1, false);
2936 TimeGrid grid2(T, steps);
2937 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process2)) {
2938 tmp->resetCache(grid2.size() - 1);
2939 }
2940 MultiPathGenerator<LowDiscrepancy::rsg_type> pg2(process2, grid2, sg2, false);
2941
2942 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb1, usdzb1, gbpzb1, infeur1, infgbp1,
2943 n1eur1, eqsp1, eqlh1;
2944 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > eurzb2, usdzb2, gbpzb2, infeur2, infgbp2,
2945 n1eur2, eqsp2, eqlh2;
2946
2947 for (Size j = 0; j < n; ++j) {
2948 Sample<MultiPath> path1 = pg1.next();
2949 Sample<MultiPath> path2 = pg2.next();
2950 Size l1 = path1.value[0].length() - 1;
2951 Size l2 = path2.value[0].length() - 1;
2952 Real zeur1 = path1.value[0][l1];
2953 Real zusd1 = path1.value[1][l1];
2954 Real zgbp1 = path1.value[2][l1];
2955 Real fxusd1 = std::exp(path1.value[3][l1]);
2956 Real fxgbp1 = std::exp(path1.value[4][l1]);
2957 Real infeurz1 = path1.value[5][l1];
2958 Real infeury1 = path1.value[6][l1];
2959 Real infgbpz1 = path1.value[7][l1];
2960 Real infgbpy1 = path1.value[8][l1];
2961 Real crzn11 = path1.value[9][l1];
2962 Real cryn11 = path1.value[10][l1];
2963 Real eq11 = path1.value[11][l1];
2964 Real eq21 = path1.value[12][l1];
2965 Real zeur2 = path2.value[0][l2];
2966 Real zusd2 = path2.value[1][l2];
2967 Real zgbp2 = path2.value[2][l2];
2968 Real fxusd2 = std::exp(path2.value[3][l2]);
2969 Real fxgbp2 = std::exp(path2.value[4][l2]);
2970 Real infeurz2 = path2.value[5][l2];
2971 Real infeury2 = path2.value[6][l2];
2972 Real infgbpz2 = path2.value[7][l2];
2973 Real infgbpy2 = path2.value[8][l2];
2974 Real crzn12 = path2.value[9][l2];
2975 Real cryn12 = path2.value[10][l2];
2976 Real eq12 = path2.value[11][l2];
2977 Real eq22 = path2.value[12][l2];
2978
2979 // EUR zerobond
2980 eurzb1(d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
2981 // USD zerobond
2982 usdzb1(d.modelExact->discountBond(1, T, T2, zusd1) * fxusd1 / d.modelExact->numeraire(0, T, zeur1));
2983 // GBP zerobond
2984 gbpzb1(d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 / d.modelExact->numeraire(0, T, zeur1));
2985 // EUR CPI indexed bond
2986 std::pair<Real, Real> sinfeur1 = d.modelExact->infdkI(0, T, T2, infeurz1, infeury1);
2987 infeur1(sinfeur1.first * sinfeur1.second * d.modelExact->discountBond(0, T, T2, zeur1) /
2988 d.modelExact->numeraire(0, T, zeur1));
2989 // GBP CPI indexed bond
2990 std::pair<Real, Real> sinfgbp1 = d.modelExact->infdkI(1, T, T2, infgbpz1, infgbpy1);
2991 infgbp1(sinfgbp1.first * sinfgbp1.second * d.modelExact->discountBond(2, T, T2, zgbp1) * fxgbp1 /
2992 d.modelExact->numeraire(0, T, zeur1));
2993 // EUR defaultable zerobond
2994 std::pair<Real, Real> sn11 = d.modelExact->crlgm1fS(0, 0, T, T2, crzn11, cryn11);
2995 n1eur1(sn11.first * sn11.second * d.modelExact->discountBond(0, T, T2, zeur1) / d.modelExact->numeraire(0, T, zeur1));
2996 // EQ
2997 eqsp1(std::exp(eq11) * fxusd1 / d.modelExact->numeraire(0, T, zeur1));
2998 eqlh1(std::exp(eq21) / d.modelExact->numeraire(0, T, zeur1));
2999
3000 // EUR zerobond
3001 eurzb2(d.modelExact->discountBond(0, T, T2, zeur2) / d.modelExact->numeraire(0, T, zeur2));
3002 // USD zerobond
3003 usdzb2(d.modelExact->discountBond(1, T, T2, zusd2) * fxusd2 / d.modelExact->numeraire(0, T, zeur2));
3004 // GBP zerobond
3005 gbpzb2(d.modelExact->discountBond(2, T, T2, zgbp2) * fxgbp2 / d.modelExact->numeraire(0, T, zeur2));
3006 // EUR CPI indexed bond
3007 std::pair<Real, Real> sinfeur2 = d.modelExact->infdkI(0, T, T2, infeurz2, infeury2);
3008 infeur2(sinfeur2.first * sinfeur2.second * d.modelExact->discountBond(0, T, T2, zeur2) /
3009 d.modelExact->numeraire(0, T, zeur2));
3010 // GBP CPI indexed bond
3011 std::pair<Real, Real> sinfgbp2 = d.modelExact->infdkI(1, T, T2, infgbpz2, infgbpy2);
3012 infgbp2(sinfgbp2.first * sinfgbp2.second * d.modelExact->discountBond(2, T, T2, zgbp2) * fxgbp2 /
3013 d.modelExact->numeraire(0, T, zeur2));
3014 // EUR defaultable zerobond
3015 std::pair<Real, Real> sn12 = d.modelExact->crlgm1fS(0, 0, T, T2, crzn12, cryn12);
3016 n1eur2(sn12.first * sn12.second * d.modelExact->discountBond(0, T, T2, zeur2) / d.modelExact->numeraire(0, T, zeur2));
3017 // EQ
3018 eqsp2(std::exp(eq12) * fxusd2 / d.modelExact->numeraire(0, T, zeur2));
3019 eqlh2(std::exp(eq22) / d.modelExact->numeraire(0, T, zeur2));
3020 }
3021
3022 BOOST_TEST_MESSAGE("EXACT:");
3023 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb1) << " +- " << error_of<tag::mean>(eurzb1) << " vs analytical "
3024 << d.eurYts->discount(T2));
3025 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb1) << " +- " << error_of<tag::mean>(usdzb1) << " vs analytical "
3026 << d.usdYts->discount(T2) * d.fxEurUsd->value());
3027 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb1) << " +- " << error_of<tag::mean>(gbpzb1) << " vs analytical "
3028 << d.gbpYts->discount(T2) * d.fxEurGbp->value());
3029 BOOST_TEST_MESSAGE("IDX zb EUR = " << mean(infeur1) << " +- " << error_of<tag::mean>(infeur1) << " vs analytical "
3030 << d.eurYts->discount(T2) *
3031 std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2));
3032 BOOST_TEST_MESSAGE("IDX zb GBP = " << mean(infgbp1) << " +- " << error_of<tag::mean>(infgbp1) << " vs analytical "
3033 << d.gbpYts->discount(T2) *
3034 std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) *
3035 d.fxEurGbp->value());
3036 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur1) << " +- " << error_of<tag::mean>(n1eur1) << " vs analytical "
3037 << d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2));
3038 BOOST_TEST_MESSAGE("EQSP USD = " << mean(eqsp1) << " +- " << error_of<tag::mean>(eqsp1) << " vs analytical "
3039 << d.spSpotToday->value() * d.eqDivSp->discount(T) * d.fxEurUsd->value());
3040 BOOST_TEST_MESSAGE("EQLH EUR = " << mean(eqlh1) << " +- " << error_of<tag::mean>(eqlh1) << " vs analytical "
3041 << d.lhSpotToday->value() * d.eqDivLh->discount(T));
3042
3043 BOOST_TEST_MESSAGE("\nEULER:");
3044 BOOST_TEST_MESSAGE("EUR zb = " << mean(eurzb2) << " +- " << error_of<tag::mean>(eurzb2) << " vs analytical "
3045 << d.eurYts->discount(T2));
3046 BOOST_TEST_MESSAGE("USD zb = " << mean(usdzb2) << " +- " << error_of<tag::mean>(usdzb2) << " vs analytical "
3047 << d.usdYts->discount(T2) * d.fxEurUsd->value());
3048 BOOST_TEST_MESSAGE("GBP zb = " << mean(gbpzb2) << " +- " << error_of<tag::mean>(gbpzb2) << " vs analytical "
3049 << d.gbpYts->discount(T2) * d.fxEurGbp->value());
3050 BOOST_TEST_MESSAGE("IDX zb EUR = " << mean(infeur2) << " +- " << error_of<tag::mean>(infeur2) << " vs analytical "
3051 << d.eurYts->discount(T2) *
3052 std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2));
3053 BOOST_TEST_MESSAGE("IDX zb GBP = " << mean(infgbp2) << " +- " << error_of<tag::mean>(infgbp2) << " vs analytical "
3054 << d.gbpYts->discount(T2) *
3055 std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) *
3056 d.fxEurGbp->value());
3057 BOOST_TEST_MESSAGE("N1 zb EUR = " << mean(n1eur2) << " +- " << error_of<tag::mean>(n1eur2) << " vs analytical "
3058 << d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2));
3059 BOOST_TEST_MESSAGE("EQSP USD = " << mean(eqsp2) << " +- " << error_of<tag::mean>(eqsp2) << " vs analytical "
3060 << d.spSpotToday->value() * d.eqDivSp->discount(T) * d.fxEurUsd->value());
3061 BOOST_TEST_MESSAGE("EQLH EUR = " << mean(eqlh2) << " +- " << error_of<tag::mean>(eqlh2) << " vs analytical "
3062 << d.lhSpotToday->value() * d.eqDivLh->discount(T));
3063
3064 // a bit higher than for plain zero bond , since we look at indexed zero
3065 // bonds, too
3066 Real tol1 = 3.0E-4, tol1r = 0.001; // EXACT
3067 Real tol2 = 14.0E-4, tol2r = 0.01; // EULER
3068
3069 Real ev = d.eurYts->discount(T2);
3070 if (std::abs(mean(eurzb1) - ev) > tol1)
3071 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
3072 "expected "
3073 << ev << ", got " << mean(eurzb1) << ", tolerance " << tol1);
3074 ev = d.usdYts->discount(T2) * d.fxEurUsd->value();
3075 if (std::abs(mean(usdzb1) - ev) > tol1)
3076 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
3077 "expected "
3078 << ev << ", got " << mean(usdzb1) << ", tolerance " << tol1);
3079 ev = d.gbpYts->discount(T2) * d.fxEurGbp->value();
3080 if (std::abs(mean(gbpzb1) - ev) > tol1)
3081 BOOST_TEST_ERROR("Martingale test failed for eurzb (exact discr.),"
3082 "expected "
3083 << ev << ", got " << mean(gbpzb1) << ", tolerance " << tol1);
3084 ev = d.eurYts->discount(T2) * std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2);
3085 if (std::abs(mean(infeur1) - ev) > tol1)
3086 BOOST_TEST_ERROR("Martingale test failed for idx eurzb (exact discr.),"
3087 "expected "
3088 << ev << ", got " << mean(infeur1) << ", tolerance " << tol1);
3089 ev = d.gbpYts->discount(T2) * std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) * d.fxEurGbp->value();
3090 if (std::abs(mean(infgbp1) - ev) > tol1)
3091 BOOST_TEST_ERROR("Martingale test failed for idx gbpzb (exact discr.),"
3092 "expected "
3093 << ev << ", got " << mean(infgbp1) << ", tolerance " << tol1);
3094 ev = d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2);
3095 if (std::abs(mean(n1eur1) - ev) > tol1)
3096 BOOST_TEST_ERROR("Martingale test failed for def eurzb (exact discr.),"
3097 "expected "
3098 << ev << ", got " << mean(n1eur1) << ", tolerance " << tol1);
3099 ev = d.spSpotToday->value() * d.eqDivSp->discount(T) * d.fxEurUsd->value();
3100 if (std::abs(mean(eqsp1) - ev) / ev > tol1r)
3101 BOOST_TEST_ERROR("Martingale test failed for eq sp (exact discr.),"
3102 "expected "
3103 << ev << ", got " << mean(eqsp1) << ", rel tolerance " << tol1r);
3104 ev = d.lhSpotToday->value() * d.eqDivLh->discount(T);
3105 if (std::abs(mean(eqlh1) - ev) / ev > tol1r)
3106 BOOST_TEST_ERROR("Martingale test failed for eq lh (exact discr.),"
3107 "expected "
3108 << ev << ", got " << mean(eqlh1) << ", rel tolerance " << tol1r);
3109
3110 ev = d.eurYts->discount(T2);
3111 if (std::abs(mean(eurzb2) - ev) > tol2)
3112 BOOST_TEST_ERROR("Martingale test failed for eurzb (Euler discr.),"
3113 "expected "
3114 << ev << ", got " << mean(eurzb2) << ", tolerance " << tol2);
3115 ev = d.usdYts->discount(T2) * d.fxEurUsd->value();
3116 if (std::abs(mean(usdzb2) - ev) > tol2)
3117 BOOST_TEST_ERROR("Martingale test failed for usdzb (Euler discr.),"
3118 "expected "
3119 << ev << ", got " << mean(usdzb2) << ", tolerance " << tol2 * error_of<tag::mean>(usdzb2));
3120 ev = d.gbpYts->discount(T2) * d.fxEurGbp->value();
3121 if (std::abs(mean(gbpzb2) - ev) > tol2)
3122 BOOST_TEST_ERROR("Martingale test failed for gbpzb (Euler discr.),"
3123 "expected "
3124 << ev << ", got " << mean(gbpzb2) << ", tolerance " << tol2);
3125 ev = d.eurYts->discount(T2) * std::pow(1.0 + d.infEurTs->zeroRate(T2 - d.infLag), T2);
3126 if (std::abs(mean(infeur2) - ev) > tol2)
3127 BOOST_TEST_ERROR("Martingale test failed for idx eurzb (Euler discr.),"
3128 "expected "
3129 << ev << ", got " << mean(infeur2) << ", tolerance " << tol2);
3130 ev = d.gbpYts->discount(T2) * std::pow(1.0 + d.infGbpTs->zeroRate(T2 - d.infLag), T2) * d.fxEurGbp->value();
3131 if (std::abs(mean(infgbp2) - ev) > tol2)
3132 BOOST_TEST_ERROR("Martingale test failed for idx gbpzb (Euler discr.),"
3133 "expected "
3134 << ev << ", got " << mean(infgbp2) << ", tolerance " << tol2);
3135 ev = d.eurYts->discount(T2) * d.n1Ts->survivalProbability(T2);
3136 if (std::abs(mean(n1eur2) - ev) > tol2)
3137 BOOST_TEST_ERROR("Martingale test failed for def eurzb (Euler discr.),"
3138 "expected "
3139 << ev << ", got " << mean(n1eur1) << ", tolerance " << tol2);
3140 ev = d.spSpotToday->value() * d.eqDivSp->discount(T) * d.fxEurUsd->value();
3141 if (std::abs(mean(eqsp2) - ev) / ev > tol2r)
3142 BOOST_TEST_ERROR("Martingale test failed for eq sp (Euler discr.),"
3143 "expected "
3144 << ev << ", got " << mean(eqsp2) << ", rel tolerance " << tol2r);
3145 ev = d.lhSpotToday->value() * d.eqDivLh->discount(T);
3146 if (std::abs(mean(eqlh2) - ev) / ev > tol2r)
3147 BOOST_TEST_ERROR("Martingale test failed for eq lh (exact discr.),"
3148 "expected "
3149 << ev << ", got " << mean(eqlh2) << ", rel tolerance " << tol2r);
3150
3151} // testIrFxInfCrEqMartingaleProperty
3152
3153BOOST_AUTO_TEST_CASE(testIrFxInfCrEqMoments) {
3154
3155 BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization "
3156 "in ir-fx-inf-cr-eq model...");
3157
3158 IrFxInfCrEqModelTestData d;
3159
3160 const Size n = 13; // d.modelExact->dimension();
3161
3162 QuantLib::ext::shared_ptr<StochasticProcess> p_exact = d.modelExact->stateProcess();
3163 QuantLib::ext::shared_ptr<StochasticProcess> p_euler = d.modelExact->stateProcess();
3164
3165 Real T = 2.0; // horizon at which we compare the moments
3166 Size steps = static_cast<Size>(T * 10); // number of simulation steps (Euler and exact)
3167 Size paths = 60000; // number of paths
3168
3169 Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
3170 Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
3171
3172 Size seed = 18;
3173 TimeGrid grid(T, steps);
3174
3175 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
3176 tmp->resetCache(grid.size() - 1);
3177 }
3178 MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid, SobolBrownianGenerator::Diagonal, seed,
3179 SobolRsg::JoeKuoD7);
3180 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
3181 tmp->resetCache(grid.size() - 1);
3182 }
3183 MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid, SobolBrownianGenerator::Diagonal, seed,
3184 SobolRsg::JoeKuoD7);
3185
3186 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu[n], e_eu2[n];
3187 accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > v_eu[n][n], v_eu2[n][n];
3188
3189 for (Size i = 0; i < paths; ++i) {
3190 Sample<MultiPath> path = pgen.next();
3191 Sample<MultiPath> path2 = pgen2.next();
3192 for (Size ii = 0; ii < n; ++ii) {
3193 Real cii = path.value[ii].back();
3194 Real cii2 = path2.value[ii].back();
3195 e_eu[ii](cii);
3196 e_eu2[ii](cii2);
3197 for (Size jj = 0; jj <= ii; ++jj) {
3198 Real cjj = path.value[jj].back();
3199 v_eu[ii][jj](cii, covariate1 = cjj);
3200 Real cjj2 = path2.value[jj].back();
3201 v_eu2[ii][jj](cii2, covariate1 = cjj2);
3202 }
3203 }
3204 }
3205
3206 for (Size i = 0; i < n; ++i) {
3207 BOOST_TEST_MESSAGE("E_" << i << " " << e_an[i] << " " << mean(e_eu[i]) << " " << mean(e_eu2[i]));
3208 }
3209 BOOST_TEST_MESSAGE("==================");
3210
3211 BOOST_TEST_MESSAGE("one step analytical");
3212 for (Size i = 0; i < n; ++i) {
3213 std::ostringstream tmp;
3214 for (Size j = 0; j <= i; ++j) {
3215 tmp << v_an[i][j] << " ";
3216 }
3217 BOOST_TEST_MESSAGE(tmp.str());
3218 }
3219 BOOST_TEST_MESSAGE("==================");
3220
3221 BOOST_TEST_MESSAGE("euler");
3222 for (Size i = 0; i < n; ++i) {
3223 std::ostringstream tmp;
3224 for (Size j = 0; j <= i; ++j) {
3225 tmp << boost::accumulators::covariance(v_eu[i][j]) << " ";
3226 }
3227 BOOST_TEST_MESSAGE(tmp.str());
3228 }
3229 BOOST_TEST_MESSAGE("==================");
3230
3231 BOOST_TEST_MESSAGE("exact");
3232 for (Size i = 0; i < n; ++i) {
3233 std::ostringstream tmp;
3234 for (Size j = 0; j <= i; ++j) {
3235 tmp << boost::accumulators::covariance(v_eu2[i][j]) << " ";
3236 }
3237 BOOST_TEST_MESSAGE(tmp.str());
3238 }
3239 BOOST_TEST_MESSAGE("==================");
3240
3241 Real errTolLd[] = { 0.5E-4, 0.5E-4, 0.5E-4, 10.0E-4, 10.0E-4, 0.7E-4, 0.7E-4,
3242 0.7E-4, 0.7E-4, 0.7E-4, 0.7E-4, 10.0E-4, 10.0E-4 };
3243 Real errTolLdEuler[] = { 0.5E-4, 0.5E-4, 0.5E-4, 10.0E-4, 10.0E-4, 0.7E-4, 0.7E-4,
3244 0.7E-4, 0.7E-4, 0.7E-4, 0.7E-4, 40.0E-4, 40.0E-4 };
3245
3246 for (Size i = 0; i < n; ++i) {
3247 // check expectation against analytical calculation (Euler)
3248 if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTolLdEuler[i]) {
3249 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
3250 << ") is inconsistent with numerical value (Euler "
3251 "discretization, "
3252 << mean(e_eu[i]) << "), error is "
3253 << e_an[i] - mean(e_eu[i]) << " tolerance is "
3254 << errTolLdEuler[i]);
3255 }
3256 // check expectation against analytical calculation (exact disc)
3257 if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTolLd[i]) {
3258 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
3259 << ") is inconsistent with numerical value (exact "
3260 "discretization, "
3261 << mean(e_eu2[i]) << "), error is "
3262 << e_an[i] - mean(e_eu2[i]) << " tolerance is "
3263 << errTolLd[i]);
3264 }
3265 }
3266
3267 // as above, this is a bit rough compared to the more differentiated
3268 // test of the IR-FX model ...
3269 Real tol = 10.0E-4, tolEuler = 65.0E-4;
3270
3271 for (Size i = 0; i < n; ++i) {
3272 for (Size j = 0; j <= i; ++j) {
3273 if (std::fabs(boost::accumulators::covariance(v_eu[i][j]) - v_an[i][j]) > tolEuler) {
3274 BOOST_ERROR("analytical covariance at ("
3275 << i << "," << j << ") (" << v_an[i][j]
3276 << ") is inconsistent with numerical "
3277 "value (Euler discretization, "
3278 << boost::accumulators::covariance(v_eu[i][j]) << "), error is "
3279 << v_an[i][j] - boost::accumulators::covariance(v_eu[i][j]) << " tolerance is "
3280 << tolEuler);
3281 }
3282 if (std::fabs(boost::accumulators::covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
3283 BOOST_ERROR("analytical covariance at ("
3284 << i << "," << j << ") (" << v_an[i][j]
3285 << ") is inconsistent with numerical "
3286 "value (exact discretization, "
3287 << boost::accumulators::covariance(v_eu2[i][j]) << "), error is "
3288 << v_an[i][j] - boost::accumulators::covariance(v_eu2[i][j]) << " tolerance is " << tol);
3289 }
3290 }
3291 }
3292
3293} // testIrFxInfCrEqMoments
3294
3295namespace {
3296
3297struct IrFxEqModelTestData {
3298 IrFxEqModelTestData()
3299 : referenceDate(30, July, 2015), eurYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed())),
3300 usdYts(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed())),
3301 eqDivSp(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.01, Actual365Fixed())),
3302 eqDivLh(QuantLib::ext::make_shared<FlatForward>(referenceDate, 0.0075, Actual365Fixed())),
3303 usdEurSpotToday(QuantLib::ext::make_shared<SimpleQuote>(0.90)), eurEurSpotToday(QuantLib::ext::make_shared<SimpleQuote>(1.0)),
3304 spSpotToday(QuantLib::ext::make_shared<SimpleQuote>(2100)), lhSpotToday(QuantLib::ext::make_shared<SimpleQuote>(12.50)) {
3305
3306 SavedSettings backup;
3307
3308 Settings::instance().evaluationDate() = referenceDate;
3309
3310 // use different grids for each of the individual processes
3311 // to test the piecewise numerical integration ...
3312
3313 std::vector<Date> volstepdatesEur, volstepdatesUsd, volstepdatesFx;
3314 volstepdatesEqSp.resize(0);
3315 volstepdatesEqLh.resize(0);
3316
3317 volstepdatesEur.push_back(Date(15, July, 2016));
3318 volstepdatesEur.push_back(Date(15, July, 2017));
3319 volstepdatesEur.push_back(Date(15, July, 2018));
3320 volstepdatesEur.push_back(Date(15, July, 2019));
3321 volstepdatesEur.push_back(Date(15, July, 2020));
3322
3323 volstepdatesUsd.push_back(Date(13, April, 2016));
3324 volstepdatesUsd.push_back(Date(13, September, 2016));
3325 volstepdatesUsd.push_back(Date(13, April, 2017));
3326 volstepdatesUsd.push_back(Date(13, September, 2017));
3327 volstepdatesUsd.push_back(Date(13, April, 2018));
3328 volstepdatesUsd.push_back(Date(15, July, 2018)); // shared with EUR
3329 volstepdatesUsd.push_back(Date(13, April, 2019));
3330 volstepdatesUsd.push_back(Date(13, September, 2019));
3331
3332 volstepdatesFx.push_back(Date(15, July, 2016)); // shared with EUR
3333 volstepdatesFx.push_back(Date(15, October, 2016));
3334 volstepdatesFx.push_back(Date(15, May, 2017));
3335 volstepdatesFx.push_back(Date(13, September, 2017)); // shared with USD
3336 volstepdatesFx.push_back(Date(15, July, 2018)); // shared with EUR and USD
3337
3338 volstepdatesEqSp.push_back(Date(13, April, 2016)); // shared with USD
3339 volstepdatesEqSp.push_back(Date(15, October, 2016)); // shared with FX
3340 volstepdatesEqSp.push_back(Date(15, March, 2017));
3341 volstepdatesEqSp.push_back(Date(13, October, 2017));
3342 volstepdatesEqSp.push_back(Date(15, July, 2018)); // shared with EUR and USD
3343 volstepdatesEqSp.push_back(Date(13, October, 2018));
3344
3345 volstepdatesEqLh.push_back(Date(13, June, 2016));
3346 volstepdatesEqLh.push_back(Date(15, September, 2016));
3347 volstepdatesEqLh.push_back(Date(15, April, 2017));
3348 volstepdatesEqLh.push_back(Date(13, October, 2017));
3349 volstepdatesEqLh.push_back(Date(15, July, 2018)); // shared with EUR and USD
3350 volstepdatesEqLh.push_back(Date(13, December, 2018));
3351
3352 std::vector<Real> eurVols, usdVols, fxVols, eqSpVols, eqLhVols;
3353
3354 for (Size i = 0; i < volstepdatesEur.size() + 1; ++i) {
3355 eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
3356 }
3357 for (Size i = 0; i < volstepdatesUsd.size() + 1; ++i) {
3358 usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
3359 }
3360 for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
3361 fxVols.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
3362 }
3363 for (Size i = 0; i < volstepdatesEqSp.size() + 1; ++i) {
3364 eqSpVols.push_back(0.20 + (0.35 - 0.20) * std::exp(-0.3 * static_cast<double>(i)));
3365 }
3366 for (Size i = 0; i < volstepdatesEqLh.size() + 1; ++i) {
3367 eqLhVols.push_back(0.25 + (0.45 - 0.25) * std::exp(-0.3 * static_cast<double>(i)));
3368 }
3369
3370 Array alphaTimesEur(volstepdatesEur.size()), alphaEur(eurVols.begin(), eurVols.end()), kappaTimesEur(0),
3371 kappaEur(1, 0.02);
3372 Array alphaTimesUsd(volstepdatesUsd.size()), alphaUsd(usdVols.begin(), usdVols.end()), kappaTimesUsd(0),
3373 kappaUsd(1, 0.04);
3374 Array fxTimes(volstepdatesFx.size()), fxSigmas(fxVols.begin(), fxVols.end());
3375 Array eqSpTimes(volstepdatesEqSp.size()), spSigmas(eqSpVols.begin(), eqSpVols.end());
3376 Array eqLhTimes(volstepdatesEqLh.size()), lhSigmas(eqLhVols.begin(), eqLhVols.end());
3377
3378 for (Size i = 0; i < alphaTimesEur.size(); ++i) {
3379 alphaTimesEur[i] = eurYts->timeFromReference(volstepdatesEur[i]);
3380 }
3381 for (Size i = 0; i < alphaTimesUsd.size(); ++i) {
3382 alphaTimesUsd[i] = eurYts->timeFromReference(volstepdatesUsd[i]);
3383 }
3384 for (Size i = 0; i < fxTimes.size(); ++i) {
3385 fxTimes[i] = eurYts->timeFromReference(volstepdatesFx[i]);
3386 }
3387 for (Size i = 0; i < eqSpTimes.size(); ++i) {
3388 eqSpTimes[i] = eurYts->timeFromReference(volstepdatesEqSp[i]);
3389 }
3390 for (Size i = 0; i < eqLhTimes.size(); ++i) {
3391 eqLhTimes[i] = eurYts->timeFromReference(volstepdatesEqLh[i]);
3392 }
3393
3394 QuantLib::ext::shared_ptr<IrLgm1fParametrization> eurLgmParam =
3395 QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), eurYts, alphaTimesEur, alphaEur,
3396 kappaTimesEur, kappaEur);
3397
3398 QuantLib::ext::shared_ptr<IrLgm1fParametrization> usdLgmParam =
3399 QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantParametrization>(USDCurrency(), usdYts, alphaTimesUsd, alphaUsd,
3400 kappaTimesUsd, kappaUsd);
3401
3402 QuantLib::ext::shared_ptr<FxBsParametrization> fxUsdEurBsParam =
3403 QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), usdEurSpotToday, fxTimes, fxSigmas);
3404
3405 QuantLib::ext::shared_ptr<EqBsParametrization> eqSpBsParam = QuantLib::ext::make_shared<EqBsPiecewiseConstantParametrization>(
3406 USDCurrency(), "SP", spSpotToday, usdEurSpotToday, eqSpTimes, spSigmas, usdYts, eqDivSp);
3407
3408 QuantLib::ext::shared_ptr<EqBsParametrization> eqLhBsParam = QuantLib::ext::make_shared<EqBsPiecewiseConstantParametrization>(
3409 EURCurrency(), "LH", lhSpotToday, eurEurSpotToday, eqLhTimes, lhSigmas, eurYts, eqDivLh);
3410
3411 singleModels.resize(0);
3412 singleModels.push_back(eurLgmParam);
3413 singleModels.push_back(usdLgmParam);
3414 singleModels.push_back(fxUsdEurBsParam);
3415 singleModels.push_back(eqSpBsParam);
3416 singleModels.push_back(eqLhBsParam);
3417
3418 ccLgmEuler = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, Matrix(), SalvagingAlgorithm::None,
3419 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
3420 ccLgmExact = QuantLib::ext::make_shared<CrossAssetModel>(singleModels, Matrix(), SalvagingAlgorithm::None,
3421 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
3422
3423 eurIdx = ccLgmEuler->ccyIndex(EURCurrency());
3424 usdIdx = ccLgmEuler->ccyIndex(USDCurrency());
3425 eurUsdIdx = usdIdx - 1;
3426 eqSpIdx = ccLgmEuler->eqIndex("SP");
3427 eqLhIdx = ccLgmEuler->eqIndex("LH");
3428
3429 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::IR, usdIdx, -0.2);
3430 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, 0.8);
3431 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::IR, usdIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, -0.5);
3432 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::EQ, eqLhIdx, 0.6);
3433 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::IR, usdIdx, -0.1);
3434 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::EQ, eqLhIdx, CrossAssetModel::AssetType::IR, eurIdx, -0.05);
3435 ccLgmEuler->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, 0.1);
3436
3437 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::IR, usdIdx, -0.2);
3438 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::IR, eurIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, 0.8);
3439 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::IR, usdIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, -0.5);
3440 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::EQ, eqLhIdx, 0.6);
3441 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::IR, usdIdx, -0.1);
3442 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::EQ, eqLhIdx, CrossAssetModel::AssetType::IR, eurIdx, -0.05);
3443 ccLgmExact->setCorrelation(CrossAssetModel::AssetType::EQ, eqSpIdx, CrossAssetModel::AssetType::FX, eurUsdIdx, 0.1);
3444 }
3445
3446 SavedSettings backup;
3447 Date referenceDate;
3448 Handle<YieldTermStructure> eurYts, usdYts;
3449 Handle<YieldTermStructure> eqDivSp, eqDivLh;
3450 Handle<Quote> usdEurSpotToday, eurEurSpotToday, spSpotToday, lhSpotToday;
3451 std::vector<QuantLib::ext::shared_ptr<Parametrization> > singleModels;
3452 QuantLib::ext::shared_ptr<CrossAssetModel> ccLgmExact, ccLgmEuler;
3453 Size eurIdx, usdIdx, eurUsdIdx, eqSpIdx, eqLhIdx;
3454 std::vector<Date> volstepdatesEqSp, volstepdatesEqLh;
3455}; // IrFxEqModelTestData
3456
3457} // anonymous namespace
3458
3459BOOST_AUTO_TEST_CASE(testEqLgm5fPayouts) {
3460
3461 BOOST_TEST_MESSAGE("Testing pricing of equity payouts under domestic "
3462 "measure in CrossAsset LGM model...");
3463
3464 IrFxEqModelTestData d;
3465 Settings::instance().evaluationDate() = d.referenceDate;
3466
3467 QuantLib::ext::shared_ptr<StochasticProcess> process = d.ccLgmExact->stateProcess();
3468 QuantLib::ext::shared_ptr<StochasticProcess> process2 = d.ccLgmEuler->stateProcess();
3469
3470 // path generation
3471
3472 Size n = 500000; // number of paths
3473 Size seed = 121; // seed
3474 // maturity of test payoffs
3475 Time T = 5.0;
3476 // take large steps, but not only one (since we are testing)
3477 Size steps = static_cast<Size>(T * 2.0);
3478 Size steps_euler = static_cast<Size>(T * 52.0);
3479 TimeGrid grid(T, steps);
3480 TimeGrid grid_euler(T, steps_euler);
3481 PseudoRandom::rsg_type sg2 = PseudoRandom::make_sequence_generator(steps, seed);
3482
3483 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
3484 tmp->resetCache(grid.size() - 1);
3485 }
3486 MultiPathGeneratorMersenneTwister pg(process, grid, seed, false);
3487 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process2)) {
3488 tmp->resetCache(grid_euler.size() - 1);
3489 }
3490 MultiPathGeneratorMersenneTwister pg2(process2, grid_euler, seed, false);
3491
3492 // test
3493 // 1 LH (EUR) forward under CrossAsset numeraire vs. analytic pricing engine
3494 // 2 SP (USD) forward (converted to EUR) under CrossAsset numeraire vs. analytic pricing engine
3495 // 3 LH (EUR) EQ option under CrossAsset numeraire vs. analytic pricing engine
3496 // 4 SP (USD) EQ option under CrossAsset numeraire vs. analytic pricing engine
3497
3498 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > stat1, stat2, stat3a, stat3b, stat4a, stat4b;
3499
3500 Real strikeLh = 12.7;
3501 Real strikeSp = 2150.0;
3502
3503 // same for paths2 since shared time grid
3504 for (Size j = 0; j < n; ++j) {
3505 Sample<MultiPath> path = pg.next();
3506 // Sample<MultiPath> path = pg2.next();
3507 Size l = path.value[0].length() - 1;
3508 Real eurusdfx = std::exp(path.value[2][l]);
3509 Real zeur = path.value[0][l];
3510 Real eqsp = std::exp(path.value[3][l]);
3511 Real eqlh = std::exp(path.value[4][l]);
3512 Real ccnum = d.ccLgmExact->numeraire(0, T, zeur);
3513
3514 // 1 LH forward settled at T deflated with numeraire
3515 Real lhFwd = eqlh - strikeLh;
3516 stat1(lhFwd / ccnum);
3517
3518 // 2 SP forward settled at T (converted to base) deflated with numeraire
3519 Real spFwd = eurusdfx * (eqsp - strikeSp);
3520 stat2(spFwd / ccnum);
3521
3522 // 3 LH option exercise at T deflated with numeraire
3523 Real lhCall = std::max(lhFwd, 0.0);
3524 Real lhPut = std::max(-1.0 * lhFwd, 0.0);
3525 stat3a(lhCall / ccnum);
3526 stat3b(lhPut / ccnum);
3527
3528 // 4 SP option exercise at T (converted to base) deflated with numeraire
3529 Real spCall = std::max(spFwd, 0.0);
3530 Real spPut = std::max(-1.0 * spFwd, 0.0);
3531 stat4a(spCall / ccnum);
3532 stat4b(spPut / ccnum);
3533 }
3534
3535 Date tradeMaturity = d.referenceDate + 5 * 365;
3536
3537 QuantLib::ext::shared_ptr<EquityForward> lhFwdTrade =
3538 QuantLib::ext::make_shared<EquityForward>("LH", EURCurrency(), Position::Long, 1, tradeMaturity, strikeLh);
3539 QuantLib::ext::shared_ptr<EquityForward> spFwdTrade =
3540 QuantLib::ext::make_shared<EquityForward>("SP", USDCurrency(), Position::Long, 1, tradeMaturity, strikeSp);
3541
3542 QuantLib::ext::shared_ptr<VanillaOption> lhCall =
3543 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, strikeLh),
3544 QuantLib::ext::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
3545 QuantLib::ext::shared_ptr<VanillaOption> lhPut =
3546 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, strikeLh),
3547 QuantLib::ext::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
3548 QuantLib::ext::shared_ptr<VanillaOption> spCall =
3549 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, strikeSp),
3550 QuantLib::ext::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
3551 QuantLib::ext::shared_ptr<VanillaOption> spPut =
3552 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, strikeSp),
3553 QuantLib::ext::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
3554
3555 QuantLib::ext::shared_ptr<DiscountingEquityForwardEngine> lhFwdEngine =
3556 QuantLib::ext::make_shared<DiscountingEquityForwardEngine>(d.eurYts, d.eqDivLh, d.lhSpotToday, d.eurYts);
3557 QuantLib::ext::shared_ptr<DiscountingEquityForwardEngine> spFwdEngine =
3558 QuantLib::ext::make_shared<DiscountingEquityForwardEngine>(d.usdYts, d.eqDivSp, d.spSpotToday, d.usdYts);
3559
3560 lhFwdTrade->setPricingEngine(lhFwdEngine);
3561 spFwdTrade->setPricingEngine(spFwdEngine);
3562
3563 QuantLib::ext::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> spEqOptionEngine =
3564 QuantLib::ext::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
3565 d.ccLgmExact, d.eqSpIdx, d.ccLgmExact->ccyIndex(d.ccLgmExact->eqbs(d.eqSpIdx)->currency()));
3566 QuantLib::ext::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> lhEqOptionEngine =
3567 QuantLib::ext::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
3568 d.ccLgmExact, d.eqLhIdx, d.ccLgmExact->ccyIndex(d.ccLgmExact->eqbs(d.eqLhIdx)->currency()));
3569
3570 lhCall->setPricingEngine(lhEqOptionEngine);
3571 lhPut->setPricingEngine(lhEqOptionEngine);
3572 spCall->setPricingEngine(spEqOptionEngine);
3573 spPut->setPricingEngine(spEqOptionEngine);
3574
3575 Real npv1 = mean(stat1);
3576 Real error1 = error_of<tag::mean>(stat1);
3577 Real expected1 = lhFwdTrade->NPV();
3578
3579 Real npv2 = mean(stat2);
3580 Real error2 = error_of<tag::mean>(stat2);
3581 Real expected2 = d.usdEurSpotToday->value() * spFwdTrade->NPV();
3582
3583 Real npv3a = mean(stat3a);
3584 Real error3a = error_of<tag::mean>(stat3a);
3585 Real expected3a = lhCall->NPV();
3586 Real npv3b = mean(stat3b);
3587 Real error3b = error_of<tag::mean>(stat3b);
3588 Real expected3b = lhPut->NPV();
3589
3590 Real npv4a = mean(stat4a);
3591 Real error4a = error_of<tag::mean>(stat4a);
3592 Real expected4a = d.usdEurSpotToday->value() * spCall->NPV();
3593 Real npv4b = mean(stat4b);
3594 Real error4b = error_of<tag::mean>(stat4b);
3595 Real expected4b = d.usdEurSpotToday->value() * spPut->NPV();
3596
3597 Real tolErrEst = 1.5; // allow absolute diffs to be within 1.5 standard errors
3598 // TODO : IS THIS STRICT ENOUGH?
3599 BOOST_CHECK_LE(std::fabs(npv1 - expected1), tolErrEst * error1);
3600 BOOST_CHECK_LE(std::fabs(npv2 - expected2), tolErrEst * error2);
3601 BOOST_CHECK_LE(std::fabs(npv3a - expected3a), tolErrEst * error3a);
3602 BOOST_CHECK_LE(std::fabs(npv3b - expected3b), tolErrEst * error3b);
3603 BOOST_CHECK_LE(std::fabs(npv4a - expected4a), tolErrEst * error4a);
3604 BOOST_CHECK_LE(std::fabs(npv4b - expected4b), tolErrEst * error4b);
3605
3606} // testEqLgm5fPayouts
3607
3608BOOST_AUTO_TEST_CASE(testEqLgm5fCalibration) {
3609
3610 BOOST_TEST_MESSAGE("Testing EQ calibration of IR-FX-EQ LGM 5F model...");
3611
3612 IrFxEqModelTestData d;
3613 Settings::instance().evaluationDate() = d.referenceDate;
3614
3615 // calibration baskets
3616 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > basketSp, basketLh;
3617
3618 for (Size i = 0; i < d.volstepdatesEqSp.size(); ++i) {
3619 Date tmp = i < d.volstepdatesEqSp.size() ? d.volstepdatesEqSp[i] : d.volstepdatesEqSp.back() + 365;
3620 basketSp.push_back(QuantLib::ext::make_shared<FxEqOptionHelper>(
3621 tmp, Null<Real>(), d.spSpotToday, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.20)), d.usdYts, d.eqDivSp,
3622 BlackCalibrationHelper::RelativePriceError));
3623 }
3624 for (Size i = 0; i < d.volstepdatesEqLh.size(); ++i) {
3625 Date tmp = i < d.volstepdatesEqLh.size() ? d.volstepdatesEqLh[i] : d.volstepdatesEqLh.back() + 365;
3626 basketLh.push_back(QuantLib::ext::make_shared<FxEqOptionHelper>(
3627 tmp, Null<Real>(), d.lhSpotToday, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.20)), d.eurYts, d.eqDivLh,
3628 BlackCalibrationHelper::RelativePriceError));
3629 }
3630
3631 // pricing engines
3632 QuantLib::ext::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> spEqOptionEngine =
3633 QuantLib::ext::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
3634 d.ccLgmExact, d.eqSpIdx, d.ccLgmExact->ccyIndex(d.ccLgmExact->eqbs(d.eqSpIdx)->currency()));
3635 QuantLib::ext::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> lhEqOptionEngine =
3636 QuantLib::ext::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
3637 d.ccLgmExact, d.eqLhIdx, d.ccLgmExact->ccyIndex(d.ccLgmExact->eqbs(d.eqLhIdx)->currency()));
3638
3639 // assign engines to calibration instruments
3640 for (Size i = 0; i < basketSp.size(); ++i) {
3641 basketSp[i]->setPricingEngine(spEqOptionEngine);
3642 }
3643 for (Size i = 0; i < basketLh.size(); ++i) {
3644 basketLh[i]->setPricingEngine(lhEqOptionEngine);
3645 }
3646
3647 // calibrate the model
3648 LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
3649 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
3650
3651 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::EQ, d.eqSpIdx, basketSp, lm, ec);
3652 d.ccLgmExact->calibrateBsVolatilitiesIterative(CrossAssetModel::AssetType::EQ, d.eqLhIdx, basketLh, lm, ec);
3653
3654 // check the results
3655 Real tol = 1E-6;
3656
3657 for (Size i = 0; i < basketSp.size(); ++i) {
3658 Real model = basketSp[i]->modelValue();
3659 Real market = basketSp[i]->marketValue();
3660 if (std::abs((model - market) / market) > tol)
3661 BOOST_ERROR("calibration failed for instrument #"
3662 << i << " in SP basket, model value is " << model << " market value is " << market
3663 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
3664 }
3665 for (Size i = 0; i < basketLh.size(); ++i) {
3666 Real model = basketLh[i]->modelValue();
3667 Real market = basketLh[i]->marketValue();
3668 if (std::abs((model - market) / market) > tol)
3669 BOOST_ERROR("calibration failed for instrument #"
3670 << i << " in LH basket, model value is " << model << " market value is " << market
3671 << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
3672 }
3673} // testEqLgm5fCalibration
3674
3675BOOST_AUTO_TEST_CASE(testEqLgm5fMoments) {
3676
3677 // TODO : REVIEW TOLERANCES
3678 BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization "
3679 "in IR-FX-EQ LGM 5F model...");
3680
3681 IrFxEqModelTestData d;
3682 Settings::instance().evaluationDate() = d.referenceDate;
3683
3684 QuantLib::ext::shared_ptr<StochasticProcess> p_exact = d.ccLgmExact->stateProcess();
3685 QuantLib::ext::shared_ptr<StochasticProcess> p_euler = d.ccLgmEuler->stateProcess();
3686
3687 Real T = 2.0; // horizon at which we compare the moments
3688 Size steps_euler = static_cast<Size>(T * 50.0); // number of simulation steps
3689 Size steps_exact = 1;
3690 Size paths = 25000; // number of paths
3691
3692 Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
3693 Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
3694 Matrix v_an_eu = p_euler->covariance(0.0, p_euler->initialValues(), T);
3695
3696 TimeGrid grid_euler(T, steps_euler);
3697 TimeGrid grid_exact(T, steps_exact);
3698
3699 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
3700 tmp->resetCache(grid_euler.size() - 1);
3701 }
3702 MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid_euler);
3703
3704 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
3705 tmp->resetCache(grid_exact.size() - 1);
3706 }
3707 MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid_exact);
3708
3709 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu[5], e_eu2[5];
3710 accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > v_eu[5][5], v_eu2[5][5];
3711
3712 for (Size i = 0; i < paths; ++i) {
3713 Sample<MultiPath> path = pgen.next();
3714 Sample<MultiPath> path2 = pgen2.next();
3715 for (Size ii = 0; ii < 5; ++ii) {
3716 Real cii = path.value[ii].back();
3717 Real cii2 = path2.value[ii].back();
3718 e_eu[ii](cii);
3719 e_eu2[ii](cii2);
3720 for (Size jj = 0; jj <= ii; ++jj) {
3721 Real cjj = path.value[jj].back();
3722 v_eu[ii][jj](cii, covariate1 = cjj);
3723 Real cjj2 = path2.value[jj].back();
3724 v_eu2[ii][jj](cii2, covariate1 = cjj2);
3725 }
3726 }
3727 }
3728
3729 Real errTol[] = { 0.2E-4, 0.2E-4, 10.0E-4, 10.0E-4, 10.0E-4 };
3730
3731 for (Size i = 0; i < 5; ++i) {
3732 // check expectation against analytical calculation (Euler)
3733 if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTol[i]) {
3734 BOOST_ERROR("analytical expectation for component #"
3735 << i << " (" << e_an[i]
3736 << ") is inconsistent with numerical value (Euler "
3737 "discretization, "
3738 << mean(e_eu[i]) << "), error is " << e_an[i] - mean(e_eu[i]) << " tolerance is " << errTol[i]);
3739 }
3740 // check expectation against analytical calculation (exact disc)
3741 if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTol[i]) {
3742 BOOST_ERROR("analytical expectation for component #" << i << " (" << e_an[i]
3743 << ") is inconsistent with numerical value (Exact "
3744 "discretization, "
3745 << mean(e_eu2[i]) << "), error is "
3746 << e_an[i] - mean(e_eu2[i]) << " tolerance is "
3747 << errTol[i]);
3748 }
3749 }
3750
3751 // we have to deal with different natures of volatility
3752 // for ir (normal) and fx (ln) so different error
3753 // tolerances apply
3754 Real tollNormal = 0.1E-4; // ir-ir
3755 Real tolMixed = 0.25E-4; // ir-fx, ir-eq
3756 Real tolLn = 8.0E-4; // fx-fx, fx-eq
3757 Real tolEq = 12.0E-4; // eq-eq (to account for higher eq vols)
3758 Real tol; // set below
3759
3760 for (Size i = 0; i < 5; ++i) {
3761 for (Size j = 0; j <= i; ++j) {
3762 if (i < 2) {
3763 tol = tollNormal;
3764 } else if ((i >= 3) && (j >= 3)) {
3765 tol = tolEq;
3766 } else {
3767 if (j < 2) {
3768 tol = tolMixed;
3769 } else {
3770 tol = tolLn;
3771 }
3772 }
3773 if (std::fabs(boost::accumulators::covariance(v_eu[i][j]) - v_an[i][j]) > tol) {
3774 BOOST_ERROR("analytical covariance at ("
3775 << i << "," << j << ") (" << v_an[i][j]
3776 << ") is inconsistent with numerical "
3777 "value (Euler discretization, "
3778 << boost::accumulators::covariance(v_eu[i][j]) << "), error is "
3779 << v_an[i][j] - boost::accumulators::covariance(v_eu[i][j]) << " tolerance is " << tol);
3780 }
3781 if (std::fabs(boost::accumulators::covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
3782 BOOST_ERROR("analytical covariance at ("
3783 << i << "," << j << ") (" << v_an[i][j]
3784 << ") is inconsistent with numerical "
3785 "value (Exact discretization, "
3786 << boost::accumulators::covariance(v_eu2[i][j]) << "), error is "
3787 << v_an[i][j] - boost::accumulators::covariance(v_eu2[i][j]) << " tolerance is " << tol);
3788 }
3789 }
3790 }
3791
3792 BOOST_TEST_MESSAGE("Testing correlation matrix recovery in presence of equity simulation");
3793
3794 // reset caching so that we can retrieve further info from the processes
3795 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_euler)) {
3796 tmp->resetCache(0);
3797 }
3798 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(p_exact)) {
3799 tmp->resetCache(grid_euler.size() - 1);
3800 }
3801
3802
3803 Matrix corr_input = d.ccLgmExact->correlation();
3804 BOOST_CHECK(corr_input.rows() == corr_input.columns());
3805 Size dim = corr_input.rows();
3806 BOOST_CHECK(corr_input.rows() == 5);
3807 Matrix r1(dim, dim), r2(dim, dim);
3808 Real dt = 1.0E-6;
3809 Real tol_corr = 1.0E-7;
3810 Matrix v_an_dt = p_exact->covariance(0.0, p_exact->initialValues(), dt);
3811 Matrix v_an_eu_dt = p_euler->covariance(0.0, p_euler->initialValues(), dt);
3812 BOOST_CHECK(v_an_dt.rows() == v_an_eu_dt.rows());
3813 BOOST_CHECK(v_an_dt.columns() == v_an_eu_dt.columns());
3814 BOOST_CHECK(corr_input.rows() == v_an_dt.rows());
3815 BOOST_CHECK(corr_input.columns() == corr_input.columns());
3816
3817 for (Size i = 0; i < dim; ++i) {
3818 for (Size j = 0; j <= i; ++j) {
3819 r1[i][j] = r1[j][i] = v_an_dt[i][j] / std::sqrt(v_an_dt[i][i] * v_an_dt[j][j]);
3820 r2[i][j] = r2[j][i] = v_an_eu_dt[i][j] / std::sqrt(v_an_eu_dt[i][i] * v_an_eu_dt[j][j]);
3821 BOOST_CHECK_MESSAGE(std::fabs(r1[i][j] - corr_input[i][j]) < tol_corr,
3822 "failed to recover correlation matrix from "
3823 "exact state process (i,j)=("
3824 << i << "," << j << "), input correlation is " << corr_input[i][j] << ", output is "
3825 << r1[i][j] << ", difference " << (corr_input[i][j] - r1[i][j]) << ", tolerance "
3826 << tol_corr);
3827 BOOST_CHECK_MESSAGE(std::fabs(r2[i][j] - corr_input[i][j]) < tol_corr,
3828 "failed to recover correlation matrix from "
3829 "Euler state process (i,j)=("
3830 << i << "," << j << "), input correlation is " << corr_input[i][j] << ", output is "
3831 << r2[i][j] << ", difference " << (corr_input[i][j] - r2[i][j]) << ", tolerance "
3832 << tol_corr);
3833 }
3834 }
3835
3836 for (Size i = 0; i < 5; ++i) {
3837 Real meu = mean(e_eu[i]);
3838 Real s_meu = error_of<tag::mean>(e_eu[i]);
3839 BOOST_TEST_MESSAGE(i << ";EULER;" << e_an[i] << ";" << meu << ";" << s_meu);
3840 Real meu2 = mean(e_eu2[i]);
3841 Real s_meu2 = error_of<tag::mean>(e_eu2[i]);
3842 BOOST_TEST_MESSAGE(i << ";EXACT;" << e_an[i] << ";" << meu2 << ";" << s_meu2);
3843 }
3844 for (Size i = 0; i < 5; ++i) {
3845 for (Size j = 0; j <= i; ++j) {
3846 Real cov = boost::accumulators::covariance(v_eu[i][j]);
3847 BOOST_TEST_MESSAGE(i << ";" << j << ";"
3848 << "EULER;" << v_an[i][j] << ";" << cov);
3849 Real cov2 = boost::accumulators::covariance(v_eu2[i][j]);
3850 BOOST_TEST_MESSAGE(i << ";" << j << ";"
3851 << "EXACT;" << v_an[i][j] << ";" << cov2);
3852 }
3853 }
3854} // testEqLgm5fMoments
3855
3856BOOST_AUTO_TEST_CASE(testCorrelationRecovery) {
3857
3858 BOOST_TEST_MESSAGE("Test if random correlation input is recovered for "
3859 "small dt in Ccy LGM model...");
3860
3861 class PseudoCurrency : public Currency {
3862 public:
3863 PseudoCurrency(const Size id) {
3864 std::ostringstream ln, sn;
3865 ln << "Dummy " << id;
3866 sn << "DUM " << id;
3867 data_ = QuantLib::ext::make_shared<Data>(ln.str(), sn.str(), id, sn.str(), "", 100, Rounding(), "%3% %1$.2f");
3868 }
3869 };
3870
3871 Real dt = 1.0E-6;
3872 Real tol = 1.0E-7;
3873
3874 // for ir-fx this fully specifies the correlation matrix
3875 // for new asset classes add other possible combinations as well
3876 Size currencies[] = { 1, 2, 3, 4, 5, 10, 20 };
3877
3878 MersenneTwisterUniformRng mt(42);
3879
3880 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
3881
3882 Handle<Quote> fxspot(QuantLib::ext::make_shared<SimpleQuote>(1.00));
3883
3884 Array notimes(0);
3885 Array fxsigma(1, 0.10);
3886
3887 for (Size ii = 0; ii < LENGTH(currencies); ++ii) {
3888
3889 std::vector<Currency> pseudoCcy;
3890 for (Size i = 0; i < currencies[ii]; ++i) {
3891 PseudoCurrency tmp(i);
3892 pseudoCcy.push_back(tmp);
3893 }
3894
3895 Size dim = 2 * currencies[ii] - 1;
3896
3897 // generate random correlation matrix
3898 Matrix b(dim, dim);
3899 Size maxTries = 100;
3900 bool valid = true;
3901 do {
3902 Matrix a(dim, dim);
3903 for (Size i = 0; i < dim; ++i) {
3904 for (Size j = 0; j <= i; ++j) {
3905 a[i][j] = a[j][i] = mt.nextReal() - 0.5;
3906 }
3907 }
3908 b = a * transpose(a);
3909 for (Size i = 0; i < dim; ++i) {
3910 if (b[i][i] < 1E-5)
3911 valid = false;
3912 }
3913 } while (!valid && --maxTries > 0);
3914
3915 if (maxTries == 0) {
3916 BOOST_ERROR("could no generate random matrix");
3917 return;
3918 }
3919
3920 Matrix c(dim, dim);
3921 for (Size i = 0; i < dim; ++i) {
3922 for (Size j = 0; j <= i; ++j) {
3923 c[i][j] = c[j][i] = b[i][j] / std::sqrt(b[i][i] * b[j][j]);
3924 }
3925 }
3926
3927 // set up model
3928
3929 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
3930
3931 // IR
3932 for (Size i = 0; i < currencies[ii]; ++i) {
3933 parametrizations.push_back(
3934 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(pseudoCcy[i], yts, 0.01, 0.01));
3935 }
3936 // FX
3937 for (Size i = 0; i < currencies[ii] - 1; ++i) {
3938 parametrizations.push_back(
3939 QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(pseudoCcy[i + 1], fxspot, notimes, fxsigma));
3940 }
3941
3942 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact =
3943 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::None, IrModel::Measure::LGM,
3944 CrossAssetModel::Discretization::Exact);
3945 QuantLib::ext::shared_ptr<CrossAssetModel> modelEuler =
3946 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::None, IrModel::Measure::LGM,
3947 CrossAssetModel::Discretization::Euler);
3948
3949 QuantLib::ext::shared_ptr<StochasticProcess> peuler = modelExact->stateProcess();
3950 QuantLib::ext::shared_ptr<StochasticProcess> pexact = modelEuler->stateProcess();
3951
3952 Matrix c1 = peuler->covariance(dt, peuler->initialValues(), dt);
3953 Matrix c2 = pexact->covariance(0.0, peuler->initialValues(), dt);
3954
3955 Matrix r1(dim, dim), r2(dim, dim);
3956
3957 for (Size i = 0; i < dim; ++i) {
3958 for (Size j = 0; j <= i; ++j) {
3959 r1[i][j] = r1[j][i] = c1[i][j] / std::sqrt(c1[i][i] * c1[j][j]);
3960 r2[i][j] = r2[j][i] = c2[i][j] / std::sqrt(c2[i][i] * c2[j][j]);
3961 if (std::fabs(r1[i][j] - c[i][j]) > tol) {
3962 BOOST_ERROR("failed to recover correlation matrix from "
3963 "Euler state process (i,j)=("
3964 << i << "," << j << "), input correlation is " << c[i][j] << ", output is " << r1[i][j]
3965 << ", difference " << (c[i][j] - r1[i][j]) << ", tolerance " << tol);
3966 }
3967 if (std::fabs(r2[i][j] - c[i][j]) > tol) {
3968 BOOST_ERROR("failed to recover correlation matrix from "
3969 "exact state process (i,j)=("
3970 << i << "," << j << "), input correlation is " << c[i][j] << ", output is " << r2[i][j]
3971 << ", difference " << (c[i][j] - r2[i][j]) << ", tolerance " << tol);
3972 }
3973 }
3974 }
3975
3976 } // for currencies
3977
3978} // test correlation recovery
3979
3980BOOST_AUTO_TEST_CASE(testIrFxInfCrCorrelationRecovery) {
3981
3982 BOOST_TEST_MESSAGE("Test if random correlation input is recovered for "
3983 "small dt in ir-fx-inf-cr model...");
3984
3985 SavedSettings backup;
3986 Settings::instance().evaluationDate() = Date(30, July, 2015);
3987
3988 class PseudoCurrency : public Currency {
3989 public:
3990 PseudoCurrency(const Size id) {
3991 std::ostringstream ln, sn;
3992 ln << "Dummy " << id;
3993 sn << "DUM " << id;
3994 data_ = QuantLib::ext::make_shared<Data>(ln.str(), sn.str(), id, sn.str(), "", 100, Rounding(), "%3% %1$.2f");
3995 }
3996 };
3997
3998 Real dt = 1.0E-6;
3999 Real tol = 1.0E-7;
4000
4001 // for ir-fx this fully specifies the correlation matrix
4002 // for new asset classes add other possible combinations as well
4003 Size currencies[] = { 1, 2, 3, 4, 5, 10, 20 };
4004 Size cpiindexes[] = { 0, 1, 10 };
4005 Size creditnames[] = { 0, 1, 5 };
4006
4007 MersenneTwisterUniformRng mt(42);
4008
4009 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
4010
4011 std::vector<Date> infDates;
4012 std::vector<Real> infRates;
4013 infDates.push_back(Date(30, April, 2015));
4014 infDates.push_back(Date(30, July, 2015));
4015 infRates.push_back(0.01);
4016 infRates.push_back(0.01);
4017 Handle<ZeroInflationTermStructure> its(
4018 QuantLib::ext::make_shared<ZeroInflationCurve>(Settings::instance().evaluationDate(), NullCalendar(), Actual365Fixed(),
4019 3 * Months, Monthly, infDates, infRates));
4020
4021 Handle<DefaultProbabilityTermStructure> hts(
4022 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.01, Actual365Fixed()));
4023
4024 Handle<Quote> fxspot(QuantLib::ext::make_shared<SimpleQuote>(1.00));
4025
4026 Array notimes(0);
4027 Array fxsigma(1, 0.10);
4028
4029 for (Size ii = 0; ii < LENGTH(currencies); ++ii) {
4030 for (Size kk = 0; kk < LENGTH(cpiindexes); ++kk) {
4031 for (Size jj = 0; jj < LENGTH(creditnames); ++jj) {
4032
4033 std::vector<Currency> pseudoCcy;
4034 for (Size i = 0; i < currencies[ii]; ++i) {
4035 PseudoCurrency tmp(i);
4036 pseudoCcy.push_back(tmp);
4037 }
4038
4039 Size dim = 2 * currencies[ii] - 1 + cpiindexes[kk] + creditnames[jj];
4040
4041 // generate random correlation matrix
4042 Matrix b(dim, dim);
4043 Size maxTries = 100;
4044 bool valid = true;
4045 do {
4046 Matrix a(dim, dim);
4047 for (Size i = 0; i < dim; ++i) {
4048 for (Size j = 0; j <= i; ++j) {
4049 a[i][j] = a[j][i] = mt.nextReal() - 0.5;
4050 }
4051 }
4052 b = a * transpose(a);
4053 for (Size i = 0; i < dim; ++i) {
4054 if (b[i][i] < 1E-5)
4055 valid = false;
4056 }
4057 } while (!valid && --maxTries > 0);
4058
4059 if (maxTries == 0) {
4060 BOOST_ERROR("could no generate random matrix");
4061 return;
4062 }
4063
4064 Matrix c(dim, dim);
4065 for (Size i = 0; i < dim; ++i) {
4066 for (Size j = 0; j <= i; ++j) {
4067 c[i][j] = c[j][i] = b[i][j] / std::sqrt(b[i][i] * b[j][j]);
4068 }
4069 }
4070
4071 // set up model
4072
4073 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
4074
4075 // IR
4076 for (Size i = 0; i < currencies[ii]; ++i) {
4077 parametrizations.push_back(
4078 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(pseudoCcy[i], yts, 0.01, 0.01));
4079 }
4080 // FX
4081 for (Size i = 0; i < currencies[ii] - 1; ++i) {
4082 parametrizations.push_back(QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
4083 pseudoCcy[i + 1], fxspot, notimes, fxsigma));
4084 }
4085 // INF
4086 for (Size i = 0; i < cpiindexes[kk]; ++i) {
4087 parametrizations.push_back(
4088 QuantLib::ext::make_shared<InfDkConstantParametrization>(pseudoCcy[0], its, 0.01, 0.01));
4089 }
4090 // CR
4091 for (Size i = 0; i < creditnames[jj]; ++i) {
4092 parametrizations.push_back(
4093 QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(pseudoCcy[0], hts, 0.01, 0.01));
4094 }
4095
4096 // get QuantLib::Error: negative eigenvalue(s) (-3.649315e-16) with SalvagingAlgorithm::None
4097 QuantLib::ext::shared_ptr<CrossAssetModel> modelEuler =
4098 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::Spectral,
4099 IrModel::Measure::LGM, CrossAssetModel::Discretization::Euler);
4100 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact =
4101 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, c, SalvagingAlgorithm::Spectral,
4102 IrModel::Measure::LGM, CrossAssetModel::Discretization::Exact);
4103
4104 QuantLib::ext::shared_ptr<StochasticProcess> peuler = modelEuler->stateProcess();
4105 QuantLib::ext::shared_ptr<StochasticProcess> pexact = modelExact->stateProcess();
4106
4107 Matrix c1 = peuler->covariance(dt, peuler->initialValues(), dt);
4108 Matrix c2 = pexact->covariance(0.0, peuler->initialValues(), dt);
4109
4110 Matrix r1(dim, dim), r2(dim, dim);
4111
4112 for (Size i = 0; i < dim; ++i) {
4113 for (Size j = 0; j <= i; ++j) {
4114 // there are two state variables per credit name,
4115 // and per inflation index
4116 Size subi = i < 2 * currencies[ii] - 1 ? 1 : 2;
4117 Size subj = j < 2 * currencies[ii] - 1 ? 1 : 2;
4118 for (Size k1 = 0; k1 < subi; ++k1) {
4119 for (Size k2 = 0; k2 < subj; ++k2) {
4120 Size i0 = i < 2 * currencies[ii] - 1
4121 ? i
4122 : 2 * currencies[ii] - 1 + 2 * (i - (2 * currencies[ii] - 1)) + k1;
4123 Size j0 = j < 2 * currencies[ii] - 1
4124 ? j
4125 : 2 * currencies[ii] - 1 + 2 * (j - (2 * currencies[ii] - 1)) + k2;
4126 r1[i][j] = r1[j][i] = c1[i0][j0] / std::sqrt(c1[i0][i0] * c1[j0][j0]);
4127 r2[i][j] = r2[j][i] = c2[i0][j0] / std::sqrt(c2[i0][i0] * c2[j0][j0]);
4128 if (std::fabs(r1[i][j] - c[i][j]) > tol) {
4129 BOOST_ERROR("failed to recover correlation matrix "
4130 "from "
4131 "Euler state process (i,j)=("
4132 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
4133 << "), input correlation is " << c[i][j] << ", output is " << r1[i][j]
4134 << ", difference " << (c[i][j] - r1[i][j]) << ", tolerance " << tol
4135 << " test configuration is " << currencies[ii] << " currencies and "
4136 << cpiindexes[kk] << " cpi indexes and " << creditnames[jj]
4137 << " credit names");
4138 }
4139 if (k1 == k2) {
4140 if (std::fabs(r2[i][j] - c[i][j]) > tol) {
4141 BOOST_ERROR("failed to recover correlation "
4142 "matrix "
4143 "from "
4144 "exact state process (i,j)=("
4145 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
4146 << "), input correlation is " << c[i][j] << ", output is "
4147 << r2[i][j] << ", difference " << (c[i][j] - r2[i][j])
4148 << ", tolerance " << tol << " test configuration is "
4149 << currencies[ii] << " currencies and " << cpiindexes[kk]
4150 << " cpi indexes and " << creditnames[jj] << " credit names");
4151 }
4152 }
4153 }
4154 }
4155 }
4156 }
4157 } // for creditnames
4158 } // for cpiindexes
4159 } // for currencies
4160} // testIrFxInfCrCorrelationRecovery
4161
4162BOOST_AUTO_TEST_CASE(testIrFxInfCrEqCorrelationRecovery) {
4163
4164 BOOST_TEST_MESSAGE("Test if random correlation input is recovered for "
4165 "small dt in ir-fx-inf-cr-eq model...");
4166
4167 SavedSettings backup;
4168 Settings::instance().evaluationDate() = Date(30, July, 2015);
4169
4170 class PseudoCurrency : public Currency {
4171 public:
4172 PseudoCurrency(const Size id) {
4173 std::ostringstream ln, sn;
4174 ln << "Dummy " << id;
4175 sn << "DUM " << id;
4176 data_ = QuantLib::ext::make_shared<Data>(ln.str(), sn.str(), id, sn.str(), "", 100, Rounding(), "%3% %1$.2f");
4177 }
4178 };
4179
4180 Real dt = 1.0E-6;
4181 Real tol = 1.0E-7;
4182
4183 // for ir-fx this fully specifies the correlation matrix
4184 // for new asset classes add other possible combinations as well
4185 Size currencies[] = { 1, 2, 3, 4, 5 };
4186 Size cpiindexes[] = { 0, 1, 10 };
4187 Size creditnames[] = { 0, 1, 5 };
4188 Size eqs[] = { 0, 1, 5 };
4189
4190 MersenneTwisterUniformRng mt(42);
4191
4192 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
4193
4194 std::vector<Date> infDates;
4195 std::vector<Real> infRates;
4196 infDates.push_back(Date(30, April, 2015));
4197 infDates.push_back(Date(30, July, 2015));
4198 infRates.push_back(0.01);
4199 infRates.push_back(0.01);
4200 Handle<ZeroInflationTermStructure> its(
4201 QuantLib::ext::make_shared<ZeroInflationCurve>(Settings::instance().evaluationDate(), NullCalendar(), Actual365Fixed(),
4202 3 * Months, Monthly, infDates, infRates));
4203
4204 Handle<DefaultProbabilityTermStructure> hts(
4205 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.01, Actual365Fixed()));
4206
4207 Handle<Quote> fxspot(QuantLib::ext::make_shared<SimpleQuote>(1.00)), eqspot(QuantLib::ext::make_shared<SimpleQuote>(1.00));
4208
4209 Array notimes(0);
4210 Array fxsigma(1, 0.10), eqsigma(1, 0.10);
4211
4212 for (Size ii = 0; ii < LENGTH(currencies); ++ii) {
4213 for (Size kk = 0; kk < LENGTH(cpiindexes); ++kk) {
4214 for (Size jj = 0; jj < LENGTH(creditnames); ++jj) {
4215 for (Size ee = 0; ee < LENGTH(eqs); ++ee) {
4216
4217 std::vector<Currency> pseudoCcy;
4218 for (Size i = 0; i < currencies[ii]; ++i) {
4219 PseudoCurrency tmp(i);
4220 pseudoCcy.push_back(tmp);
4221 }
4222
4223 Size dim = 2 * currencies[ii] - 1 + cpiindexes[kk] + creditnames[jj] + eqs[ee];
4224
4225 // generate random correlation matrix
4226 Matrix b(dim, dim);
4227 Size maxTries = 100;
4228 bool valid = true;
4229 do {
4230 Matrix a(dim, dim);
4231 for (Size i = 0; i < dim; ++i) {
4232 for (Size j = 0; j <= i; ++j) {
4233 a[i][j] = a[j][i] = mt.nextReal() - 0.5;
4234 }
4235 }
4236 b = a * transpose(a);
4237 for (Size i = 0; i < dim; ++i) {
4238 if (b[i][i] < 1E-5)
4239 valid = false;
4240 }
4241 } while (!valid && --maxTries > 0);
4242
4243 if (maxTries == 0) {
4244 BOOST_ERROR("could no generate random matrix");
4245 return;
4246 }
4247
4248 Matrix c(dim, dim);
4249 for (Size i = 0; i < dim; ++i) {
4250 for (Size j = 0; j <= i; ++j) {
4251 c[i][j] = c[j][i] = b[i][j] / std::sqrt(b[i][i] * b[j][j]);
4252 }
4253 }
4254
4255 // set up model
4256
4257 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
4258
4259 // IR
4260 for (Size i = 0; i < currencies[ii]; ++i) {
4261 parametrizations.push_back(
4262 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(pseudoCcy[i], yts, 0.01, 0.01));
4263 }
4264 // FX
4265 for (Size i = 0; i < currencies[ii] - 1; ++i) {
4266 parametrizations.push_back(QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
4267 pseudoCcy[i + 1], fxspot, notimes, fxsigma));
4268 }
4269 // INF
4270 for (Size i = 0; i < cpiindexes[kk]; ++i) {
4271 parametrizations.push_back(
4272 QuantLib::ext::make_shared<InfDkConstantParametrization>(pseudoCcy[0], its, 0.01, 0.01));
4273 }
4274 // CR
4275 for (Size i = 0; i < creditnames[jj]; ++i) {
4276 parametrizations.push_back(
4277 QuantLib::ext::make_shared<CrLgm1fConstantParametrization>(pseudoCcy[0], hts, 0.01, 0.01));
4278 }
4279 // EQ
4280 for (Size i = 0; i < eqs[ee]; ++i) {
4281 parametrizations.push_back(QuantLib::ext::make_shared<EqBsPiecewiseConstantParametrization>(
4282 pseudoCcy[0], "dummy", eqspot, fxspot, notimes, eqsigma, yts, yts));
4283 }
4284
4285 // get QuantLib::Error: negative eigenvalue(s) (-3.649315e-16) with SalvagingAlgorithm::None
4286 QuantLib::ext::shared_ptr<CrossAssetModel> modelEuler = QuantLib::ext::make_shared<CrossAssetModel>(
4287 parametrizations, c, SalvagingAlgorithm::Spectral, IrModel::Measure::LGM,
4288 CrossAssetModel::Discretization::Euler);
4289 QuantLib::ext::shared_ptr<CrossAssetModel> modelExact = QuantLib::ext::make_shared<CrossAssetModel>(
4290 parametrizations, c, SalvagingAlgorithm::Spectral, IrModel::Measure::LGM,
4291 CrossAssetModel::Discretization::Exact);
4292
4293 QuantLib::ext::shared_ptr<StochasticProcess> peuler = modelEuler->stateProcess();
4294 QuantLib::ext::shared_ptr<StochasticProcess> pexact = modelExact->stateProcess();
4295
4296 Matrix c1 = peuler->covariance(dt, peuler->initialValues(), dt);
4297 Matrix c2 = pexact->covariance(0.0, peuler->initialValues(), dt);
4298
4299 Matrix r1(dim, dim), r2(dim, dim);
4300
4301 Size sizeIrFx = 2 * currencies[ii] - 1;
4302
4303 for (Size i = 0; i < dim; ++i) {
4304 for (Size j = 0; j <= i; ++j) {
4305 // there are two state variables per credit name,
4306 // and per inflation index
4307 Size subi = i < sizeIrFx || i >= sizeIrFx + cpiindexes[kk] + creditnames[jj] ? 1 : 2;
4308 Size subj = j < sizeIrFx || i >= sizeIrFx + cpiindexes[kk] + creditnames[jj] ? 1 : 2;
4309 for (Size k1 = 0; k1 < subi; ++k1) {
4310 for (Size k2 = 0; k2 < subj; ++k2) {
4311 Size i0 = i < sizeIrFx
4312 ? i
4313 : (i < sizeIrFx + cpiindexes[kk] + creditnames[jj]
4314 ? sizeIrFx + 2 * (i - sizeIrFx) + k1
4315 : sizeIrFx + 2 * cpiindexes[kk] + 2 * creditnames[jj] +
4316 (i - sizeIrFx - cpiindexes[kk] - creditnames[jj]));
4317 Size j0 = j < sizeIrFx
4318 ? j
4319 : (j < sizeIrFx + cpiindexes[kk] + creditnames[jj]
4320 ? sizeIrFx + 2 * (j - sizeIrFx) + k2
4321 : sizeIrFx + 2 * cpiindexes[kk] + 2 * creditnames[jj] +
4322 (j - sizeIrFx - cpiindexes[kk] - creditnames[jj]));
4323 r1[i][j] = r1[j][i] = c1[i0][j0] / std::sqrt(c1[i0][i0] * c1[j0][j0]);
4324 r2[i][j] = r2[j][i] = c2[i0][j0] / std::sqrt(c2[i0][i0] * c2[j0][j0]);
4325 if (std::fabs(r1[i][j] - c[i][j]) > tol) {
4326 BOOST_ERROR("failed to recover correlation matrix "
4327 "from "
4328 "Euler state process (i,j)=("
4329 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
4330 << "), input correlation is " << c[i][j] << ", output is "
4331 << r1[i][j] << ", difference " << (c[i][j] - r1[i][j])
4332 << ", tolerance " << tol << " test configuration is "
4333 << currencies[ii] << " currencies and " << cpiindexes[kk]
4334 << " cpi indexes and " << creditnames[jj] << " credit names"
4335 << " and " << eqs[ee] << " equities");
4336 }
4337 if (k1 == k2) {
4338 if (std::fabs(r2[i][j] - c[i][j]) > tol) {
4339 BOOST_ERROR("failed to recover correlation "
4340 "matrix "
4341 "from "
4342 "exact state process (i,j)=("
4343 << i << "," << j << "), (i0,j0)=(" << i0 << "," << j0
4344 << "), input correlation is " << c[i][j] << ", output is "
4345 << r2[i][j] << ", difference " << (c[i][j] - r2[i][j])
4346 << ", tolerance " << tol << " test configuration is "
4347 << currencies[ii] << " currencies and " << cpiindexes[kk]
4348 << " cpi indexes and " << creditnames[jj] << " credit names"
4349 << " and " << eqs[ee] << " equities");
4350 }
4351 }
4352 }
4353 }
4354 }
4355 }
4356 } // for equity
4357 } // for creditnames
4358 } // for cpiindexes
4359 } // for currencies
4360} // testIrFxInfCrEqCorrelationRecovery
4361
4362BOOST_AUTO_TEST_CASE(testCpiCalibrationByAlpha) {
4363
4364 BOOST_TEST_MESSAGE("Testing calibration to ZC CPI Floors (using alpha) and repricing via MC...");
4365
4366 // set up IR-INF model, calibrate to given premiums and check
4367 // the result with a MC simulation
4368
4369 SavedSettings backup;
4370 Date refDate(30, July, 2015);
4371 Settings::instance().evaluationDate() = Date(30, July, 2015);
4372
4373 // IR
4374 Handle<YieldTermStructure> eurYts(QuantLib::ext::make_shared<FlatForward>(refDate, 0.01, Actual365Fixed()));
4375 QuantLib::ext::shared_ptr<Parametrization> ireur_p =
4376 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), eurYts, 0.01, 0.01);
4377
4378 // INF
4379 Real baseCPI = 100.0;
4380 std::vector<Date> infDates;
4381 std::vector<Real> infRates;
4382 infDates.push_back(Date(30, April, 2015));
4383 infDates.push_back(Date(30, July, 2015));
4384 infRates.push_back(0.0075);
4385 infRates.push_back(0.0075);
4386 Handle<ZeroInflationTermStructure> infEurTs(QuantLib::ext::make_shared<ZeroInflationCurve>(
4387 refDate, TARGET(), Actual365Fixed(), 3 * Months, Monthly, infDates, infRates));
4388 infEurTs->enableExtrapolation();
4389 Handle<ZeroInflationIndex> infIndex(QuantLib::ext::make_shared<EUHICPXT>(false, infEurTs));
4390
4391 infIndex->addFixing(Date(1, April, 2015), 100);
4392
4393 Real premium[] = { 0.0044, 0.0085, 0.0127, 0.0160, 0.0186 };
4394
4395 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > cpiHelpers;
4396 Array volStepTimes(4), noTimes(0);
4397 Array infVols(5, 0.01), infRev(1, 1.5); // !!
4398
4399 Time T;
4400 for (Size i = 1; i <= 5; ++i) {
4401 Date maturity = refDate + i * Years;
4402 QuantLib::ext::shared_ptr<CpiCapFloorHelper> h(new CpiCapFloorHelper(Option::Put, baseCPI, maturity, TARGET(),
4403 ModifiedFollowing, TARGET(), ModifiedFollowing,
4404 0.01, infIndex, 3 * Months, premium[i - 1]));
4405 Real t = inflationYearFraction(Monthly, false, Actual365Fixed(), infEurTs->baseDate(),
4406 h->instrument()->fixingDate());
4407 cpiHelpers.push_back(h);
4408 if (i <= 4)
4409 volStepTimes[i - 1] = t;
4410 T = t;
4411 }
4412
4413 QuantLib::ext::shared_ptr<InfDkPiecewiseConstantParametrization> infeur_p =
4414 QuantLib::ext::make_shared<InfDkPiecewiseConstantParametrization>(EURCurrency(), infEurTs, volStepTimes, infVols,
4415 noTimes, infRev);
4416
4417 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
4418 parametrizations.push_back(ireur_p);
4419 parametrizations.push_back(infeur_p);
4420
4421 QuantLib::ext::shared_ptr<CrossAssetModel> model =
4422 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, Matrix(), SalvagingAlgorithm::None);
4423
4424 model->setCorrelation(CrossAssetModel::AssetType::IR, 0, CrossAssetModel::AssetType::INF, 0, 0.33);
4425
4426 // pricing engine
4427 QuantLib::ext::shared_ptr<AnalyticDkCpiCapFloorEngine> engine =
4428 QuantLib::ext::make_shared<AnalyticDkCpiCapFloorEngine>(model, 0, baseCPI);
4429
4430 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4431 cpiHelpers[i]->setPricingEngine(engine);
4432 }
4433
4434 // calibration
4435 LevenbergMarquardt lm;
4436 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
4437 model->calibrateInfDkVolatilitiesIterative(0, cpiHelpers, lm, ec);
4438
4439 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4440 BOOST_TEST_MESSAGE("i=" << i << " modelvol=" << model->infdk(0)->parameterValues(0)[i] << " market="
4441 << cpiHelpers[i]->marketValue() << " model=" << cpiHelpers[i]->modelValue()
4442 << " diff=" << (cpiHelpers[i]->marketValue() - cpiHelpers[i]->modelValue()));
4443 }
4444
4445 // reprice last ZC floor with Monte Carlo
4446 Size n = 50000; // number of paths
4447 Size seed = 18; // rng seed
4448 Size steps = 1; // number of discretization steps
4449
4450 QuantLib::ext::shared_ptr<StochasticProcess> process = model->stateProcess();
4451 LowDiscrepancy::rsg_type sg = LowDiscrepancy::make_sequence_generator(process->factors() * steps, seed);
4452 TimeGrid grid(T, steps);
4453 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
4454 tmp->resetCache(grid.size() - 1);
4455 }
4456 MultiPathGenerator<LowDiscrepancy::rsg_type> pg(process, grid, sg, false);
4457
4458 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > floor;
4459
4460 Real K = std::pow(1.0 + 0.01, T);
4461
4462 for (Size i = 0; i < n; ++i) {
4463 Sample<MultiPath> path = pg.next();
4464 Size l = path.value[0].length() - 1;
4465 Real irz = path.value[0][l];
4466 Real infz = path.value[1][l];
4467 Real infy = path.value[2][l];
4468 Real I = model->infdkI(0, T, T, infz, infy).first;
4469 floor(std::max(-(I - K), 0.0) / model->numeraire(0, T, irz));
4470 }
4471
4472 BOOST_TEST_MESSAGE("mc floor 5y = " << mean(floor) << " +- ");
4473
4474 // check model calibration
4475 Real tol = 1.0E-12;
4476 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4477 if (std::abs(cpiHelpers[i]->modelValue() - cpiHelpers[i]->marketValue()) > tol) {
4478 BOOST_ERROR("Model calibration for ZC CPI Floor #"
4479 << i << " failed, market premium is " << cpiHelpers[i]->marketValue() << ", model value is "
4480 << cpiHelpers[i]->modelValue() << ", difference is "
4481 << (cpiHelpers[i]->marketValue() - cpiHelpers[i]->modelValue()) << ", tolerance is " << tol);
4482 }
4483 }
4484 // check repricing with MC
4485 tol = 1.0E-5;
4486 Real mcPrice = mean(floor);
4487 if (std::abs(mcPrice - cpiHelpers[4]->modelValue()) > tol) {
4488 BOOST_ERROR("Failed to reprice 5y ZC CPI Floor with MC ("
4489 << mcPrice << "), analytical model price is " << cpiHelpers[4]->modelValue() << ", difference is "
4490 << mcPrice - cpiHelpers[4]->modelValue() << ", tolerance is " << tol);
4491 }
4492} // testCpiCalibrationByAlpha
4493
4494BOOST_AUTO_TEST_CASE(testCpiCalibrationByH) {
4495
4496 BOOST_TEST_MESSAGE("Testing calibration to ZC CPI Floors (using H) and repricing via MC...");
4497
4498 // set up IR-INF model, calibrate to given premiums and check
4499 // the result with a MC simulation
4500
4501 SavedSettings backup;
4502 Date refDate(30, July, 2015);
4503 Settings::instance().evaluationDate() = Date(30, July, 2015);
4504
4505 // IR
4506 Handle<YieldTermStructure> eurYts(QuantLib::ext::make_shared<FlatForward>(refDate, 0.01, Actual365Fixed()));
4507 QuantLib::ext::shared_ptr<Parametrization> ireur_p =
4508 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), eurYts, 0.01, 0.01);
4509
4510 // INF
4511 Real baseCPI = 100.0;
4512 std::vector<Date> infDates;
4513 std::vector<Real> infRates;
4514 infDates.push_back(Date(30, April, 2015));
4515 infDates.push_back(Date(30, July, 2015));
4516 infRates.push_back(0.0075);
4517 infRates.push_back(0.0075);
4518 Handle<ZeroInflationTermStructure> infEurTs(QuantLib::ext::make_shared<ZeroInflationCurve>(
4519 refDate, TARGET(), Actual365Fixed(), 3 * Months, Monthly, infDates, infRates));
4520 infEurTs->enableExtrapolation();
4521 Handle<ZeroInflationIndex> infIndex(QuantLib::ext::make_shared<EUHICPXT>(false, infEurTs));
4522 infIndex->addFixing(Date(1, April, 2015), 100);
4523 Size nMat = 14;
4524 Real premium[] = { 0.000555, 0.000813, 0.000928, 0.00127, 0.001616, 0.0019, 0.0023,
4525 0.0026, 0.0029, 0.0032, 0.0032, 0.0033, 0.0038, 0.0067 };
4526 Period maturity[] = { 1 * Years, 2 * Years, 3 * Years, 4 * Years, 5 * Years, 6 * Years, 7 * Years,
4527 8 * Years, 9 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years, 30 * Years };
4528
4529 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > cpiHelpers;
4530 Array volStepTimes(13), noTimes(0);
4531 Array infVols(14, 0.0030), infRev(14, 1.0); // init vol and rev !!
4532 Real strike = 0.00; // strike !!
4533
4534 Time T = Null<Time>();
4535 for (Size i = 1; i <= nMat; ++i) {
4536 Date mat = refDate + maturity[i - 1];
4537 QuantLib::ext::shared_ptr<CpiCapFloorHelper> h(new CpiCapFloorHelper(Option::Put, baseCPI, mat, TARGET(),
4538 ModifiedFollowing, TARGET(), ModifiedFollowing,
4539 strike, infIndex, 3 * Months, premium[i - 1]));
4540 Real t = inflationYearFraction(Monthly, false, Actual365Fixed(), infEurTs->baseDate(),
4541 h->instrument()->fixingDate());
4542 cpiHelpers.push_back(h);
4543 if (i <= nMat - 1)
4544 volStepTimes[i - 1] = t;
4545 T = t;
4546 }
4547
4548 QuantLib::ext::shared_ptr<InfDkPiecewiseLinearParametrization> infeur_p =
4549 QuantLib::ext::make_shared<InfDkPiecewiseLinearParametrization>(EURCurrency(), infEurTs, volStepTimes, infVols,
4550 volStepTimes, infRev);
4551
4552 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
4553 parametrizations.push_back(ireur_p);
4554 parametrizations.push_back(infeur_p);
4555
4556 QuantLib::ext::shared_ptr<CrossAssetModel> model =
4557 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, Matrix(), SalvagingAlgorithm::None);
4558
4559 model->setCorrelation(CrossAssetModel::AssetType::IR, 0, CrossAssetModel::AssetType::INF, 0, 0.33);
4560
4561 // pricing engine
4562 QuantLib::ext::shared_ptr<AnalyticDkCpiCapFloorEngine> engine =
4563 QuantLib::ext::make_shared<AnalyticDkCpiCapFloorEngine>(model, 0, baseCPI);
4564
4565 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4566 cpiHelpers[i]->setPricingEngine(engine);
4567 }
4568
4569 // calibration
4570 LevenbergMarquardt lm;
4571 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
4572 // model->calibrateInfDkVolatilitiesIterative(0, cpiHelpers, lm, ec);
4573 model->calibrateInfDkReversionsIterative(0, cpiHelpers, lm, ec);
4574
4575 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4576 BOOST_TEST_MESSAGE("i=" << i << " modelvol=" << model->infdk(0)->parameterValues(0)[i] << " modelrev="
4577 << model->infdk(0)->parameterValues(1)[i] << " market=" << cpiHelpers[i]->marketValue()
4578 << " model=" << cpiHelpers[i]->modelValue()
4579 << " diff=" << (cpiHelpers[i]->marketValue() - cpiHelpers[i]->modelValue()));
4580 }
4581
4582 // reprice last ZC floor with Monte Carlo
4583 Size n = 100000; // number of paths
4584 Size seed = 18; // rng seed
4585 Size steps = 1; // number of discretization steps
4586
4587 QuantLib::ext::shared_ptr<StochasticProcess> process = model->stateProcess();
4588 LowDiscrepancy::rsg_type sg = LowDiscrepancy::make_sequence_generator(process->factors() * steps, seed);
4589 TimeGrid grid(T, steps);
4590 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
4591 tmp->resetCache(grid.size() - 1);
4592 }
4593 MultiPathGenerator<LowDiscrepancy::rsg_type> pg(process, grid, sg, false);
4594
4595 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > floor;
4596
4597 Real K = std::pow(1.0 + strike, T);
4598
4599 for (Size i = 0; i < n; ++i) {
4600 Sample<MultiPath> path = pg.next();
4601 Size l = path.value[0].length() - 1;
4602 Real irz = path.value[0][l];
4603 Real infz = path.value[1][l];
4604 Real infy = path.value[2][l];
4605 Real I = model->infdkI(0, T, T, infz, infy).first;
4606 floor(std::max(-(I - K), 0.0) / model->numeraire(0, T, irz));
4607 }
4608
4609 BOOST_TEST_MESSAGE("mc (low disc) floor last = " << mean(floor) << " +- " << error_of<tag::mean>(floor));
4610
4611 // check model calibration
4612 Real tol = 1.0E-12;
4613 for (Size i = 0; i < cpiHelpers.size(); ++i) {
4614 if (std::abs(cpiHelpers[i]->modelValue() - cpiHelpers[i]->marketValue()) > tol) {
4615 BOOST_ERROR("Model calibration for ZC CPI Floor #"
4616 << i << " failed, market premium is " << cpiHelpers[i]->marketValue() << ", model value is "
4617 << cpiHelpers[i]->modelValue() << ", difference is "
4618 << (cpiHelpers[i]->marketValue() - cpiHelpers[i]->modelValue()) << ", tolerance is " << tol);
4619 }
4620 }
4621 // check repricing with MC
4622 tol = 2.0E-4;
4623 Real mcPrice = mean(floor);
4624 if (std::abs(mcPrice - cpiHelpers[nMat - 1]->modelValue()) > tol) {
4625 BOOST_ERROR("Failed to reprice last ZC CPI Floor with MC ("
4626 << mcPrice << "), analytical model price is " << cpiHelpers[nMat - 1]->modelValue()
4627 << ", difference is " << mcPrice - cpiHelpers[nMat - 1]->modelValue() << ", tolerance is " << tol);
4628 }
4629} // testCpiCalibrationByH
4630
4631BOOST_AUTO_TEST_CASE(testCrCalibration) {
4632
4633 BOOST_TEST_MESSAGE("Testing calibration to CDS Options and repricing via MC...");
4634
4635 // set up IR-CR model, calibrate to given CDS Options and check
4636 // the result with a MC simulation
4637
4638 SavedSettings backup;
4639 Date refDate(30, July, 2015);
4640 Settings::instance().evaluationDate() = Date(30, July, 2015);
4641
4642 // IR (zero vol)
4643 Handle<YieldTermStructure> eurYts(QuantLib::ext::make_shared<FlatForward>(refDate, 0.01, Actual365Fixed()));
4644 QuantLib::ext::shared_ptr<Parametrization> ireur_p =
4645 QuantLib::ext::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), eurYts, 0.00, 0.01);
4646
4647 // CR
4648 Handle<DefaultProbabilityTermStructure> prob(QuantLib::ext::make_shared<FlatHazardRate>(refDate, 0.01, Actual365Fixed()));
4649
4650 Size nMat = 10;
4651 Real impliedVols[] = { 0.10, 0.12, 0.14, 0.16, 0.18, 0.2, 0.21, 0.215, 0.22, 0.225 };
4652 Period maturity[] = { 1 * Years, 2 * Years, 3 * Years, 4 * Years, 5 * Years,
4653 6 * Years, 7 * Years, 8 * Years, 9 * Years, 10 * Years };
4654
4655 std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper> > cdsoHelpers;
4656 Array volStepTimes(nMat - 1), noTimes(0);
4657 Array crVols(nMat, 0.0030), crRev(nMat, 0.01); // init vol and rev
4658
4659 Time T = Null<Time>();
4660 Date lastMat;
4661 for (Size i = 1; i <= nMat; ++i) {
4662 Date mat = refDate + maturity[i - 1];
4663 Schedule schedule(mat, mat + 10 * Years, 3 * Months, TARGET(), Following, Following, DateGeneration::CDS,
4664 false);
4665 // ensure that cds starts after option expiry
4666 if (schedule.date(0) < mat)
4667 schedule = Schedule(schedule.date(1), mat + 10 * Years, 3 * Months, TARGET(), Following, Following,
4668 DateGeneration::CDS, false);
4669 QL_REQUIRE(schedule.date(0) >= mat,
4670 "CDS start (" << schedule.date(0) << ") should be on or after option maturity (" << mat << ")");
4671 QuantLib::ext::shared_ptr<CdsOptionHelper> h(
4672 new CdsOptionHelper(mat, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(impliedVols[i - 1])),
4673 Protection::Buyer, schedule, Following, Actual360(), prob, 0.4, eurYts));
4674 Real t = eurYts->timeFromReference(mat);
4675 cdsoHelpers.push_back(h);
4676 if (i <= nMat - 1)
4677 volStepTimes[i - 1] = t;
4678 T = t;
4679 lastMat = mat;
4680 }
4681
4682 QuantLib::ext::shared_ptr<CrLgm1fPiecewiseConstantParametrization> creur_p =
4683 QuantLib::ext::make_shared<CrLgm1fPiecewiseConstantParametrization>(EURCurrency(), prob, volStepTimes, crVols,
4684 volStepTimes, crRev);
4685
4686 std::vector<QuantLib::ext::shared_ptr<Parametrization> > parametrizations;
4687 parametrizations.push_back(ireur_p);
4688 parametrizations.push_back(creur_p);
4689
4690 QuantLib::ext::shared_ptr<CrossAssetModel> model =
4691 QuantLib::ext::make_shared<CrossAssetModel>(parametrizations, Matrix(), SalvagingAlgorithm::None);
4692
4693 model->setCorrelation(CrossAssetModel::AssetType::IR, 0, CrossAssetModel::AssetType::CR, 0, 0.33);
4694
4695 // pricing engine
4696 QuantLib::ext::shared_ptr<AnalyticLgmCdsOptionEngine> engine =
4697 QuantLib::ext::make_shared<AnalyticLgmCdsOptionEngine>(model, 0, 0, 0.4);
4698
4699 for (Size i = 0; i < cdsoHelpers.size(); ++i) {
4700 cdsoHelpers[i]->setPricingEngine(engine);
4701 }
4702
4703 // calibration
4704 LevenbergMarquardt lm;
4705 EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);
4706 model->calibrateCrLgm1fVolatilitiesIterative(0, cdsoHelpers, lm, ec);
4707
4708 for (Size i = 0; i < cdsoHelpers.size(); ++i) {
4709 BOOST_TEST_MESSAGE("i=" << i << " modelvol=" << model->crlgm1f(0)->parameterValues(0)[i]
4710 << " modelrev=" << model->crlgm1f(0)->parameterValues(1)[i] << " market="
4711 << cdsoHelpers[i]->marketValue() << " model=" << cdsoHelpers[i]->modelValue()
4712 << " diff=" << (cdsoHelpers[i]->marketValue() - cdsoHelpers[i]->modelValue()));
4713 }
4714
4715 // check model calibration
4716 Real tol = 1.0E-12;
4717 for (Size i = 0; i < cdsoHelpers.size(); ++i) {
4718 if (std::abs(cdsoHelpers[i]->modelValue() - cdsoHelpers[i]->marketValue()) > tol) {
4719 BOOST_ERROR("Model calibration for CDSO #"
4720 << i << " failed, market premium is " << cdsoHelpers[i]->marketValue() << ", model value is "
4721 << cdsoHelpers[i]->modelValue() << ", difference is "
4722 << (cdsoHelpers[i]->marketValue() - cdsoHelpers[i]->modelValue()) << ", tolerance is " << tol);
4723 }
4724 }
4725
4726 Real lastModelValue = cdsoHelpers[nMat - 1]->modelValue();
4727
4728 // reprice last CDSO with Monte Carlo
4729 // note that the IR vol is zero (same as assumption in CDSO analytic engine)
4730 Size n = 10000; // number of paths
4731 Size seed = 18; // rng seed
4732 Size steps = 1; // number of discretization steps
4733
4734 QuantLib::ext::shared_ptr<StochasticProcess> process = model->stateProcess();
4735 LowDiscrepancy::rsg_type sg = LowDiscrepancy::make_sequence_generator(process->factors() * steps, seed);
4736 TimeGrid grid(T, steps);
4737 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
4738 tmp->resetCache(grid.size() - 1);
4739 }
4740 MultiPathGenerator<LowDiscrepancy::rsg_type> pg(process, grid, sg, false);
4741
4742 accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > cdso;
4743
4744 QuantLib::ext::shared_ptr<CreditDefaultSwap> underlying =
4745 QuantLib::ext::static_pointer_cast<CdsOptionHelper>(cdsoHelpers.back())->underlying();
4746 Real K = underlying->fairSpreadClean();
4747 BOOST_TEST_MESSAGE("Last CDSO fair spread is " << K);
4748
4749 Settings::instance().evaluationDate() = lastMat;
4750 QuantLib::ext::shared_ptr<LgmImpliedDefaultTermStructure> probMc =
4751 QuantLib::ext::make_shared<LgmImpliedDefaultTermStructure>(model, 0, 0);
4752 QuantLib::ext::shared_ptr<LgmImpliedYieldTermStructure> ytsMc =
4753 QuantLib::ext::make_shared<LgmImpliedYieldTermStructure>(model->lgm(0));
4754 QuantLib::ext::shared_ptr<QuantExt::MidPointCdsEngine> dynamicEngine = QuantLib::ext::make_shared<QuantExt::MidPointCdsEngine>(
4755 Handle<DefaultProbabilityTermStructure>(probMc), 0.4, Handle<YieldTermStructure>(ytsMc));
4756 underlying->setPricingEngine(dynamicEngine);
4757
4758 for (Size i = 0; i < n; ++i) {
4759 Sample<MultiPath> path = pg.next();
4760 Size l = path.value[0].length() - 1;
4761 Real irz = path.value[0][l];
4762 Real crz = path.value[1][l];
4763 Real cry = path.value[2][l];
4764 probMc->move(lastMat, crz, cry);
4765 ytsMc->move(lastMat, irz);
4766 Real surv = model->crlgm1fS(0, 0, T, T, crz, cry).first;
4767 Real npv = surv * std::max(underlying->NPV(), 0.0) / model->numeraire(0, T, irz);
4768 cdso(npv);
4769 }
4770
4771 BOOST_TEST_MESSAGE("mc (low disc) cdso last = " << mean(cdso) << " +- " << error_of<tag::mean>(cdso));
4772
4773 // check repricing with MC
4774 tol = 3.0E-4;
4775 Real mcPrice = mean(cdso);
4776 if (std::abs(mcPrice - lastModelValue) > tol) {
4777 BOOST_ERROR("Failed to reprice last CDSO with MC (" << mcPrice << "), analytical model price is "
4778 << lastModelValue << ", difference is "
4779 << mcPrice - lastModelValue << ", tolerance is " << tol);
4780 }
4781} // testCrCalibration
4782
4783BOOST_AUTO_TEST_SUITE_END()
4784
4785BOOST_AUTO_TEST_SUITE_END()
analytic cc lgm fx option engine
analytic dk cpi cap floor engine
analytic lgm cds option engine
analytic engine for european swaptions in the LGM model
analytic cross-asset lgm eq option engine
Black credit default swap option engine.
cds option calibration helper
constant CIR ++ parametrization
Interpolated price curve.
Definition: pricecurve.hpp:50
Instantiation of MultiPathGenerator with standard PseudoRandom traits.
const Sample< MultiPath > & next() const override
const Sample< MultiPath > & next() const override
Instantiation using SobolBrownianGenerator from models/marketmodels/browniangenerators.
Schwartz (1997) one-factor model of the commodity price termstructure.
Schwartz commodity model parametrization.
COM state process for the one-factor Schwartz model.
CPI Cap Floor calibration helper.
Credit Linear Gaussian Markov 1 factor parametrization.
analytics for the cross asset model
basic functions for analytics in the cross asset model
cross asset model
dynamic black volatility term structure
dynamic black volatility term structure
crossasset model state process
Cross currency swap engine.
deposit engine
Engine to value a commodity forward contract.
discounting currency swap engine
Engine to value an Equity Forward contract.
Engine to value an FX Forward off two yield curves.
Swap engine employing assumptions to speed up calculation.
year on year inflation term structure implied by a Dodgson Kainth (DK) model
zero inflation term structure implied by a Dodgson Kainth (DK) model
Constant equity model parametrization.
EQ Black Scholes parametrization.
piecewise constant model parametrization
Constant FX model parametrization.
FX Black Scholes parametrization.
piecewise constant model parametrization
calibration helper for Black-Scholes options
adaptor class that extracts one irlgm1f component
Inflation Dodgson Kainth parametrization.
constant model parametrization
Interest Rate Linear Gaussian Markov 1 factor parametrization.
adaptor to emulate piecewise constant Hull White parameters
piecewise constant model parametrization
piecewise linear model parametrization
ir LGM 1f model state process
zero inflation term structure implied by a Jarrow Yildrim (JY) model
lgm model class
default probability structure implied by a LGM model
yield term structure implied by a LGM model
calibrated model class with linkable parameters
base class for multi path generators
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
CompiledFormula exp(CompiledFormula x)
Real inflationGrowth(const QuantLib::ext::shared_ptr< CrossAssetModel > &model, Size index, Time S, Time T, Real irState, Real rrState, bool indexIsInterpolated)
Overnight Indexed Cross Currency Basis Swap Engine.
base class for model parametrizations
Single payment discounting engine.
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
helper classes for piecewise constant parametrizations
Interpolated price curve.
parameter giving access to calibration machinery
std::vector< Size > steps
BOOST_DATA_TEST_CASE(testIrFxInfCrComMartingaleProperty, bdata::make(infEurFlags) *bdata::make(infGbpFlags) *bdata::make(flatVolsFlags) *bdata::make(driftFreeState), infEurIsDk, infGbpIsDk, flatVols, driftFreeState)
vector< bool > driftFreeState
BOOST_AUTO_TEST_CASE(testBermudanLgm1fGsr)
vector< bool > flatVolsFlags
vector< bool > infEurFlags
vector< bool > infGbpFlags
Fixture that can be used at top level.
helper macros and methods for tests
#define LENGTH(a)
Definition: utilities.hpp:27