380 {
381
382
383 const bool outputResults = false;
384
385
386
387
388
389 const bool useCachedResults = true;
390
391 BOOST_TEST_MESSAGE("Testing Bermudan swaption exposure profile");
392
393
395 std::vector<Period> tenorGrid;
396
397 if (!testCase.fineGrid) {
398
399 for (Size i = 0; i < 2 * testCase.simYears; ++i) {
400 tenorGrid.push_back(((i + 1) * 6) * Months);
401 }
402 } else {
403
404 for (Size i = 0; i < 12 * testCase.simYears; ++i) {
405 tenorGrid.push_back(((i + 1)) * Months);
406 }
407 }
408
409 Calendar cal;
410 if (testCase.inBaseCcy)
411 cal = TARGET();
412 else
413 cal = JointCalendar(UnitedStates(UnitedStates::Settlement), UnitedKingdom());
414
415 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid, cal, ActualActual(ActualActual::ISDA));
416
417
418 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = ccLgm;
419
420
422 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
423 5 * Years, 7 * Years, 10 * Years, 12 * Years, 15 * Years, 20 * Years,
424 30 * Years, 40 * Years, 50 * Years});
425 simMarketConfig->setSimulateFXVols(false);
426
427 simMarketConfig->baseCcy() = "EUR";
428 simMarketConfig->setDiscountCurveNames({"EUR", "USD"});
429 simMarketConfig->ccys() = {"EUR", "USD"};
430 std::vector<std::string> tmp;
431 for (auto c : simMarketConfig->ccys()) {
432 if (c != simMarketConfig->baseCcy())
433 tmp.push_back(c + simMarketConfig->baseCcy());
434 }
435 simMarketConfig->setFxCcyPairs(tmp);
436
437 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M"});
438 simMarketConfig->interpolation() = "LogLinear";
439 simMarketConfig->setSwapVolExpiries("EUR", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
440 simMarketConfig->setSwapVolTerms("EUR", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
441
444 sgd->seed() = 42;
445 sgd->setGrid(grid);
446
448 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
449 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, market);
450
451 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(market, simMarketConfig);
452 simMarket->scenarioGenerator() = sg;
453
454
455 Date startDate = cal.advance(today, 2 * Days);
456 Date fwdStartDate = cal.advance(startDate, 10 * Years);
457 Date endDate = cal.advance(fwdStartDate, testCase.swapLen * Years);
458 Schedule fixedSchedule(fwdStartDate, endDate, 1 * Years, cal, Following, Following, DateGeneration::Forward, false);
459 Schedule floatingSchedule(fwdStartDate, endDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following, Following,
460 DateGeneration::Forward, false);
461 Schedule fixedScheduleSwap(startDate, endDate, 1 * Years, cal, Following, Following, DateGeneration::Forward,
462 false);
463 Schedule floatingScheduleSwap(startDate, endDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following, Following,
464 DateGeneration::Forward, false);
465 Schedule fixedScheduleSwapShort(startDate, fwdStartDate, 1 * Years, cal, Following, Following,
466 DateGeneration::Forward, false);
467 Schedule floatingScheduleSwapShort(startDate, fwdStartDate, (testCase.inBaseCcy ? 6 : 3) * Months, cal, Following,
468 Following, DateGeneration::Forward, false);
469 QuantLib::ext::shared_ptr<VanillaSwap> underlying, vanillaswap, vanillaswapshort;
470 QuantLib::ext::shared_ptr<NonstandardSwap> underlyingNs, vanillaswapNs, vanillaswapshortNs;
471 if (!testCase.isAmortising) {
472
473 if (testCase.inBaseCcy) {
474 underlying = QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap::Payer, 1.0, fixedSchedule, 0.02, Thirty360(Thirty360::BondBasis),
475 floatingSchedule, *simMarket->iborIndex("EUR-EURIBOR-6M"), 0.0,
476 Actual360());
477 } else {
478 underlying = QuantLib::ext::make_shared<VanillaSwap>(VanillaSwap::Payer, 1.0, fixedSchedule, 0.03, Thirty360(Thirty360::BondBasis),
479 floatingSchedule, *simMarket->iborIndex("USD-LIBOR-3M"), 0.0,
480 Actual360());
481 }
482 } else {
483
484 std::vector<Real> fixNotionals(fixedSchedule.size() - 1), floatNotionals(floatingSchedule.size() - 1);
485 std::vector<Real> fixNotionalsSwap(fixedScheduleSwap.size() - 1),
486 floatNotionalsSwap(floatingScheduleSwap.size() - 1);
487 std::vector<Real> fixNotionalsShort(fixedScheduleSwapShort.size() - 1),
488 floatNotionalsShort(floatingScheduleSwapShort.size() - 1);
489 for (Size i = 0; i < fixNotionals.size(); ++i)
490 fixNotionals[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionals.size());
491 for (Size i = 0; i < floatNotionals.size(); ++i)
492 floatNotionals[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionals.size());
493 for (Size i = 0; i < fixNotionalsSwap.size(); ++i)
494 fixNotionalsSwap[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionalsSwap.size());
495 for (Size i = 0; i < floatNotionalsSwap.size(); ++i)
496 floatNotionalsSwap[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionalsSwap.size());
497 for (Size i = 0; i < fixNotionalsShort.size(); ++i)
498 fixNotionalsShort[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(fixNotionalsShort.size());
499 for (Size i = 0; i < floatNotionalsShort.size(); ++i)
500 floatNotionalsShort[i] = 1.0 - static_cast<Real>(i) / static_cast<Real>(floatNotionalsShort.size());
501
502 if (testCase.inBaseCcy) {
503 underlyingNs = QuantLib::ext::make_shared<NonstandardSwap>(
504 VanillaSwap::Payer, fixNotionals, floatNotionals, fixedSchedule,
505 std::vector<Real>(fixNotionals.size(), 0.02), Thirty360(Thirty360::BondBasis), floatingSchedule,
506 *simMarket->iborIndex("EUR-EURIBOR-6M"), 1.0, 0.0, Actual360());
507 } else {
508 underlyingNs = QuantLib::ext::make_shared<NonstandardSwap>(
509 VanillaSwap::Payer, fixNotionals, floatNotionals, fixedSchedule,
510 std::vector<Real>(fixNotionals.size(), 0.03), Thirty360(Thirty360::BondBasis), floatingSchedule,
511 *simMarket->iborIndex("USD-LIBOR-3M"), 1.0, 0.0, Actual360());
512 }
513 }
514
515
516 QuantLib::ext::shared_ptr<PricingEngine> underlyingEngine = QuantLib::ext::make_shared<QuantLib::DiscountingSwapEngine>(
517 testCase.inBaseCcy ? simMarket->discountCurve("EUR") : simMarket->discountCurve("USD"));
518 if (underlying)
519 underlying->setPricingEngine(underlyingEngine);
520 if (underlyingNs)
521 underlyingNs->setPricingEngine(underlyingEngine);
522 if (vanillaswap) {
523 vanillaswap->setPricingEngine(underlyingEngine);
524 vanillaswapshort->setPricingEngine(underlyingEngine);
525 }
526 if (vanillaswapNs) {
527 vanillaswapNs->setPricingEngine(underlyingEngine);
528 vanillaswapshortNs->setPricingEngine(underlyingEngine);
529 }
530
531
532 std::vector<Date> fixingDates;
533 if (vanillaswap) {
534 for (Size i = 0; i < vanillaswap->leg(1).
size(); ++i) {
535 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(vanillaswap->leg(1)[i]);
536 fixingDates.push_back(c->fixingDate());
537 }
538 } else if (vanillaswapNs) {
539 for (Size i = 0; i < vanillaswapNs->leg(1).
size(); ++i) {
540 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(vanillaswapNs->leg(1)[i]);
541 fixingDates.push_back(c->fixingDate());
542 }
543 } else if (underlying) {
544 for (Size i = 0; i < underlying->leg(1).
size(); ++i) {
545 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(underlying->leg(1)[i]);
546 fixingDates.push_back(c->fixingDate());
547 }
548 } else if (underlyingNs) {
549 for (Size i = 0; i < underlyingNs->leg(1).
size(); ++i) {
550 QuantLib::ext::shared_ptr<IborCoupon> c = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(underlyingNs->leg(1)[i]);
551 fixingDates.push_back(c->fixingDate());
552 }
553 }
554
555
556 std::vector<Date> exerciseDates;
557 for (Size i = 0; i < testCase.numExercises; ++i) {
558 if (!testCase.fineGrid)
559
560 exerciseDates.push_back(grid->dates()[19 + 2 * i]);
561 else
562
563 exerciseDates.push_back(grid->dates()[119 + 12 * i]);
564 }
565
566 QuantLib::ext::shared_ptr<QuantLib::Instrument> tmpUnd;
567 if (testCase.isAmortising)
568 tmpUnd = underlyingNs;
569 else
570 tmpUnd = underlying;
571 std::vector<QuantLib::ext::shared_ptr<QuantLib::Instrument>> undInst(exerciseDates.size(), tmpUnd);
572
573 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<BermudanExercise>(exerciseDates);
574 QuantLib::ext::shared_ptr<Instrument> swaption;
575 Settlement::Type settlementType = testCase.isPhysical ? Settlement::Physical : Settlement::Cash;
576 Settlement::Method settlementMethod =
577 testCase.isPhysical ? Settlement::PhysicalOTC : Settlement::CollateralizedCashPrice;
578 if (testCase.isAmortising)
579 swaption = QuantLib::ext::make_shared<NonstandardSwaption>(underlyingNs, exercise, settlementType, settlementMethod);
580 else
581 swaption = QuantLib::ext::make_shared<Swaption>(underlying, exercise, settlementType, settlementMethod);
582
583 QuantLib::ext::shared_ptr<IrLgm1fParametrization> param;
584
585 Array emptyTimes;
586 Array alphaEur(1), kappaEur(1), alphaUsd(1), kappaUsd(1);
587 alphaEur[0] = lgm_eur->parametrization()->hullWhiteSigma(1.0);
588 kappaEur[0] = lgm_eur->parametrization()->kappa(1.0);
589 alphaUsd[0] = lgm_usd->parametrization()->hullWhiteSigma(1.0);
590 kappaUsd[0] = lgm_usd->parametrization()->kappa(1.0);
591 if (testCase.inBaseCcy)
592 param = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
593 EURCurrency(), simMarket->discountCurve("EUR"), emptyTimes, alphaEur, emptyTimes, kappaEur);
594 else
595 param = QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
596 USDCurrency(), simMarket->discountCurve("USD"), emptyTimes, alphaUsd, emptyTimes, kappaUsd);
597 QuantLib::ext::shared_ptr<LinearGaussMarkovModel> bermmodel = QuantLib::ext::make_shared<LinearGaussMarkovModel>(param);
598
599
600
601 param->shift() = -param->H(testCase.horizonShift);
602
603 QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_eur->parametrization())->shift() =
604 -QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_eur->parametrization())->H(testCase.horizonShift);
605
606 QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_usd->parametrization())->shift() =
607 -QuantLib::ext::static_pointer_cast<IrLgm1fParametrization>(lgm_usd->parametrization())->H(testCase.horizonShift);
608
609
610
611 QuantLib::ext::shared_ptr<PricingEngine> engineGrid;
612 if (testCase.isAmortising)
613 engineGrid = QuantLib::ext::make_shared<NumericLgmNonstandardSwaptionEngine>(bermmodel, Real(testCase.sx), testCase.nx,
614 Real(testCase.sx), testCase.nx);
615 else
616 engineGrid = QuantLib::ext::make_shared<NumericLgmSwaptionEngine>(bermmodel, Real(testCase.sx), testCase.nx,
617 Real(testCase.sx), testCase.nx);
618
619
620 std::vector<Size> externalModelIndices = testCase.inBaseCcy ? std::vector<Size>{0} : std::vector<Size>{1};
621 QuantLib::ext::shared_ptr<PricingEngine> engineMc;
622 if (testCase.isAmortising) {
623 engineMc = QuantLib::ext::make_shared<McLgmNonstandardSwaptionEngine>(
624 testCase.inBaseCcy ? lgm_eur : lgm_usd, MersenneTwisterAntithetic, SobolBrownianBridge,
625 testCase.trainingPaths, 0, 4711, 4712, 6, LsmBasisSystem::Monomial, SobolBrownianGenerator::Steps,
626 SobolRsg::JoeKuoD7, Handle<YieldTermStructure>(), grid->dates(), externalModelIndices);
627 swaption->setPricingEngine(engineMc);
628 } else {
629 engineMc = QuantLib::ext::make_shared<McLgmSwaptionEngine>(
630 testCase.inBaseCcy ? lgm_eur : lgm_usd, MersenneTwisterAntithetic, SobolBrownianBridge,
631 testCase.trainingPaths, 0, 4711, 4712, 6, LsmBasisSystem::Monomial, SobolBrownianGenerator::Steps,
632 SobolRsg::JoeKuoD7, Handle<YieldTermStructure>(), grid->dates(), externalModelIndices);
633 swaption->setPricingEngine(engineMc);
634 }
635
636
637
638 QuantLib::ext::shared_ptr<InstrumentWrapper> wrapperGrid = QuantLib::ext::make_shared<BermudanOptionWrapper>(
639 swaption, vanillaswap ? false : true, exerciseDates, testCase.isPhysical, undInst);
640 wrapperGrid->initialise(grid->dates());
641
642
643 std::vector<Real> swaption_epe_grid(grid->dates().size(), 0.0);
644 std::vector<Real> swaption_epe_amc(grid->dates().size(), 0.0);
645
646
647 swaption->setPricingEngine(engineMc);
648 class TestTrade :
public Trade {
649 public:
650 TestTrade(const string& tradeType, const string& curr, const QuantLib::ext::shared_ptr<InstrumentWrapper>& inst)
652 instrument_ = inst;
653 npvCurrency_ = curr;
654 }
655 void build(
const QuantLib::ext::shared_ptr<EngineFactory>&)
override {}
656 };
657 AMCValuationEngine amcValEngine(model, sgd, QuantLib::ext::shared_ptr<Market>(), std::vector<string>(),
658 std::vector<string>(), 0);
659 auto trade = QuantLib::ext::make_shared<TestTrade>("BermudanSwaption", testCase.inBaseCcy ? "EUR" : "USD",
660 QuantLib::ext::make_shared<VanillaInstrument>(swaption));
661 trade->id() = "DummyTradeId";
662 auto portfolio = QuantLib::ext::make_shared<Portfolio>();
663 portfolio->add(trade);
664 QuantLib::ext::shared_ptr<NPVCube> outputCube = QuantLib::ext::make_shared<DoublePrecisionInMemoryCube>(
665 referenceDate, std::set<string>{"DummyTradeId"}, grid->dates(), testCase.samples);
666 boost::timer::cpu_timer timer;
667 amcValEngine.buildCube(portfolio, outputCube);
668 timer.stop();
669 Real amcTime = timer.elapsed().wall * 1e-9;
670
671
672 for (Size j = 0; j < grid->dates().
size(); ++j) {
673 for (Size i = 0; i < testCase.samples; ++i) {
674 swaption_epe_amc[j] += std::max(outputCube->get(0, j, i, 0), 0.0);
675 }
676 }
677
678 Real fx = 1.0;
679 if (!testCase.inBaseCcy)
680 fx = simMarket->fxRate("USDEUR")->value();
681
682 Real amcNPV = outputCube->getT0(0, 0) / fx;
683
684 timer.start();
685 swaption->setPricingEngine(engineGrid);
686 Real gridNPV = swaption->NPV();
687 timer.stop();
688 Real gridTime0 = timer.elapsed().wall * 1e-9;
689
690 BOOST_CHECK_MESSAGE((std::abs(gridNPV - amcNPV) <= testCase.tolerance),
691 "Can not verify gridNPV (" << gridNPV << ") and amcNPV (" << amcNPV << ")"
692 << ", difference is " << gridNPV - amcNPV << ", tolerance is "
693 << testCase.tolerance);
694
695
696 if (useCachedResults) {
697 for (Size t = 0; t < grid->dates().
size(); ++t) {
698 swaption_epe_grid.at(t) = testCase.cachedResults.at(t).at(1) * static_cast<Real>(testCase.samples);
699 }
700 } else {
701 Real updateTime = 0.0;
702 Real gridTime = 0.0;
703 BOOST_TEST_MESSAGE("running " << testCase.samples << " samples simulation over " << grid->dates().size()
704 << " time steps");
705 QuantLib::ext::shared_ptr<IborIndex> index =
706 *(testCase.inBaseCcy ? simMarket->iborIndex("EUR-EURIBOR-6M") : simMarket->iborIndex("USD-LIBOR-3M"));
707 for (Size i = 0; i < testCase.samples; ++i) {
708 if (i % 100 == 0)
709 BOOST_TEST_MESSAGE("Sample " << i);
710 Size idx = 0, fixIdx = 0;
711 Size gridCnt = testCase.gridEvalEachNth;
712 for (Date date : grid->dates()) {
713 timer.start();
714
715 simMarket->update(date);
716
717 Real v = index->fixing(date);
718 while (fixIdx < fixingDates.size() && fixingDates[fixIdx] <= date) {
719 index->addFixing(fixingDates[fixIdx], v);
720 ++fixIdx;
721 }
722
723
724
725 swaption->update();
726 if (underlying)
727 underlying->update();
728 if (underlyingNs)
729 underlyingNs->update();
730 if (vanillaswap) {
731 vanillaswap->update();
732 vanillaswapshort->update();
733 }
734 if (vanillaswapNs) {
735 vanillaswapNs->update();
736 vanillaswapshortNs->update();
737 }
738 timer.stop();
739 updateTime += timer.elapsed().wall * 1e-9;
740 Real numeraire = simMarket->numeraire();
741 Real fx = 1.0;
742 if (!testCase.inBaseCcy)
743 fx = simMarket->fxRate("USDEUR")->value();
744
745 if (--gridCnt == 0) {
746 timer.start();
747 swaption_epe_grid[idx] += std::max(wrapperGrid->NPV() * fx, 0.0) / numeraire;
748 timer.stop();
749 gridTime += timer.elapsed().wall * 1e-9;
750 gridCnt = testCase.gridEvalEachNth;
751 }
752 idx++;
753 }
754 wrapperGrid->reset();
755 index->clearFixings();
756 }
757 BOOST_TEST_MESSAGE("Simulation time, grid " << gridTime << ", updates " << updateTime);
758 }
759
760
761 if (outputResults) {
762 std::clog << "time swaption_epe_grid swaption_epe_amc" << std::endl;
763 }
764 Size gridCnt = testCase.gridEvalEachNth;
765 Real maxSwaptionErr = 0.0;
766 for (Size i = 0; i < swaption_epe_grid.size(); ++i) {
767 Real t = grid->timeGrid()[i + 1];
768 swaption_epe_grid[i] /= testCase.samples;
769 swaption_epe_amc[i] /= testCase.samples;
770 if (outputResults) {
771 if (useCachedResults) {
772
773 std::clog << t << " " << swaption_epe_grid[i] << " " << swaption_epe_amc[i] << " " << std::endl;
774 } else {
775
776 std::clog << "{" << t << ", " << swaption_epe_grid[i] << std::endl;
777 }
778 }
779 if (--gridCnt == 0) {
780 if (!outputResults) {
781 BOOST_CHECK_MESSAGE((std::abs(swaption_epe_grid[i] - swaption_epe_amc[i]) <= testCase.tolerance),
782 "Can not verify swaption epe at grid point t="
783 << t << ", grid = " << swaption_epe_grid[i] << ", amc = " << swaption_epe_amc[i]
784 << ", difference " << (swaption_epe_grid[i] - swaption_epe_amc[i])
785 << ", tolerance " << testCase.tolerance);
786 }
787 maxSwaptionErr = std::max(maxSwaptionErr, std::abs(swaption_epe_grid[i] - swaption_epe_amc[i]));
788 gridCnt = testCase.gridEvalEachNth;
789 }
790 }
791 BOOST_TEST_MESSAGE("AMC simulation time = " << amcTime << "s, T0 NPV (AMC) = " << amcNPV
792 << ", T0 NPV (Grid) = " << gridNPV << " (" << gridTime0 * 1000.0
793 << " ms), Max Error Swaption = " << maxSwaptionErr);
794
795}
Build a ScenarioGenerator.
Scenario Generator description.
ScenarioSimMarket description.
virtual void build(const QuantLib::ext::shared_ptr< EngineFactory > &)=0
Size size(const ValueType &v)