31#include <ql/cashflows/averagebmacoupon.hpp>
32#include <ql/cashflows/capflooredcoupon.hpp>
33#include <ql/cashflows/cmscoupon.hpp>
34#include <ql/cashflows/fixedratecoupon.hpp>
35#include <ql/cashflows/floatingratecoupon.hpp>
36#include <ql/cashflows/iborcoupon.hpp>
37#include <ql/cashflows/simplecashflow.hpp>
38#include <ql/experimental/coupons/strippedcapflooredcoupon.hpp>
39#include <ql/indexes/swapindex.hpp>
44 const Handle<CrossAssetModel>& model,
const SequenceType calibrationPathGenerator,
45 const SequenceType pricingPathGenerator,
const Size calibrationSamples,
const Size pricingSamples,
46 const Size calibrationSeed,
const Size pricingSeed,
const Size polynomOrder,
47 const LsmBasisSystem::PolynomialType polynomType,
const SobolBrownianGenerator::Ordering ordering,
48 SobolRsg::DirectionIntegers directionIntegers,
const std::vector<Handle<YieldTermStructure>>& discountCurves,
49 const std::vector<Date>& simulationDates,
const std::vector<Size>& externalModelIndices,
const bool minimalObsDate,
50 const RegressorModel regressorModel,
const Real regressionVarianceCutoff)
51 : model_(model), calibrationPathGenerator_(calibrationPathGenerator), pricingPathGenerator_(pricingPathGenerator),
52 calibrationSamples_(calibrationSamples), pricingSamples_(pricingSamples), calibrationSeed_(calibrationSeed),
53 pricingSeed_(pricingSeed), polynomOrder_(polynomOrder), polynomType_(polynomType), ordering_(ordering),
54 directionIntegers_(directionIntegers), discountCurves_(discountCurves), simulationDates_(simulationDates),
55 externalModelIndices_(externalModelIndices), minimalObsDate_(minimalObsDate), regressorModel_(regressorModel),
56 regressionVarianceCutoff_(regressionVarianceCutoff) {
62 "McMultiLegBaseEngine: " <<
discountCurves_.size() <<
" discount curves given, but model has "
68 return model_->irlgm1f(0)->termStructure()->timeFromReference(d);
72 const Currency& payCcy,
bool payer,
73 Size legNo, Size cfNo)
const {
84 if (
auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(flow)) {
85 QL_REQUIRE(cpn->accrualStartDate() < flow->date(),
86 "McMultiLegBaseEngine::createCashflowInfo(): coupon leg "
87 << legNo <<
" cashflow " << cfNo <<
" has accrual start date (" << cpn->accrualStartDate()
88 <<
") >= pay date (" << flow->date()
89 <<
"), which breaks an assumption in the engine. This situation is unexpected.");
96 if (QuantLib::ext::dynamic_pointer_cast<SimpleCashFlow>(flow) !=
nullptr) {
97 info.
amountCalculator = [flow](
const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
104 if (
auto fxl = QuantLib::ext::dynamic_pointer_cast<FXLinkedCashFlow>(flow)) {
105 Date fxLinkedFixingDate = fxl->fxFixingDate();
106 Size fxLinkedSourceCcyIdx =
model_->ccyIndex(fxl->fxIndex()->sourceCurrency());
107 Size fxLinkedTargetCcyIdx =
model_->ccyIndex(fxl->fxIndex()->targetCurrency());
108 if (fxLinkedFixingDate >
today_) {
109 Real fxSimTime =
time(fxLinkedFixingDate);
112 if (fxLinkedSourceCcyIdx > 0) {
116 if (fxLinkedTargetCcyIdx > 0) {
121 info.
amountCalculator = [
this, fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixingDate,
122 fxl](
const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
123 if (fxLinkedFixingDate <=
today_)
127 if (fxLinkedSourceCcyIdx > 0)
128 fxSource =
exp(*states.at(0).at(fxIdx++));
129 if (fxLinkedTargetCcyIdx > 0)
130 fxTarget =
exp(*states.at(0).at(fxIdx));
131 return RandomVariable(n, fxl->foreignAmount()) * fxSource / fxTarget;
138 bool isFxLinked =
false;
139 bool isFxIndexed =
false;
140 Size fxLinkedSourceCcyIdx = Null<Size>();
141 Size fxLinkedTargetCcyIdx = Null<Size>();
142 Real fxLinkedFixedFxRate = Null<Real>();
143 Real fxLinkedSimTime = Null<Real>();
144 Real fxLinkedForeignNominal = Null<Real>();
145 std::vector<Size> fxLinkedModelIndices;
148 if (
auto indexCpn = QuantLib::ext::dynamic_pointer_cast<IndexedCoupon>(flow)) {
149 if (
auto fxIndex = QuantLib::ext::dynamic_pointer_cast<FxIndex>(indexCpn->index())) {
151 auto fixingDate = indexCpn->fixingDate();
152 fxLinkedSourceCcyIdx =
model_->ccyIndex(fxIndex->sourceCurrency());
153 fxLinkedTargetCcyIdx =
model_->ccyIndex(fxIndex->targetCurrency());
154 if (fixingDate <=
today_) {
155 fxLinkedFixedFxRate = fxIndex->fixing(fixingDate);
157 fxLinkedSimTime =
time(fixingDate);
158 if (fxLinkedSourceCcyIdx > 0) {
159 fxLinkedModelIndices.push_back(
162 if (fxLinkedTargetCcyIdx > 0) {
163 fxLinkedModelIndices.push_back(
167 flow = indexCpn->underlying();
169 }
else if (
auto fxl = QuantLib::ext::dynamic_pointer_cast<FloatingRateFXLinkedNotionalCoupon>(flow)) {
171 auto fixingDate = fxl->fxFixingDate();
172 fxLinkedSourceCcyIdx =
model_->ccyIndex(fxl->fxIndex()->sourceCurrency());
173 fxLinkedTargetCcyIdx =
model_->ccyIndex(fxl->fxIndex()->targetCurrency());
174 if (fixingDate <=
today_) {
175 fxLinkedFixedFxRate = fxl->fxIndex()->fixing(fixingDate);
177 fxLinkedSimTime =
time(fixingDate);
178 if (fxLinkedSourceCcyIdx > 0) {
181 if (fxLinkedTargetCcyIdx > 0) {
185 flow = fxl->underlying();
186 fxLinkedForeignNominal = fxl->foreignAmount();
189 bool isCapFloored =
false;
190 bool isNakedOption =
false;
191 Real effCap = Null<Real>(), effFloor = Null<Real>();
192 if (
auto stripped = QuantLib::ext::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(flow)) {
193 isNakedOption =
true;
194 flow = stripped->underlying();
197 if (
auto cf = QuantLib::ext::dynamic_pointer_cast<CappedFlooredCoupon>(flow)) {
199 effCap = cf->effectiveCap();
200 effFloor = cf->effectiveFloor();
201 flow = cf->underlying();
206 if (QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(flow) !=
nullptr) {
208 if (fxLinkedSimTime != Null<Real>()) {
213 info.
amountCalculator = [flow, isFxLinked, isFxIndexed, fxLinkedFixedFxRate,
214 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx](
const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
216 if (isFxLinked || isFxIndexed) {
217 if (fxLinkedFixedFxRate != Null<Real>()) {
222 if (fxLinkedSourceCcyIdx > 0)
223 fxSource =
exp(*states.at(0).at(fxIdx++));
224 if (fxLinkedTargetCcyIdx > 0)
225 fxTarget =
exp(*states.at(0).at(fxIdx));
226 fxFixing = fxSource / fxTarget;
234 if (
auto ibor = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(flow)) {
236 ibor->fixingDate() <=
today_ ? (ibor->rate() - ibor->spread()) / ibor->gearing() : Null<Real>();
237 Size indexCcyIdx =
model_->ccyIndex(ibor->index()->currency());
238 Real simTime =
time(ibor->fixingDate());
239 if (ibor->fixingDate() >
today_) {
244 if (fxLinkedSimTime != Null<Real>()) {
249 info.
amountCalculator = [
this, indexCcyIdx, ibor, simTime, fixedRate, isFxLinked, fxLinkedForeignNominal,
250 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isCapFloored,
251 isNakedOption, effFloor, effCap, isFxIndexed](
const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
254 :
lgmVectorised_[indexCcyIdx].fixing(ibor->index(), ibor->fixingDate(), simTime,
255 *states.at(0).at(0));
257 if (isFxLinked || isFxIndexed) {
258 if (fxLinkedFixedFxRate != Null<Real>()) {
263 if (fxLinkedSourceCcyIdx > 0)
264 fxSource =
exp(*states.at(1).at(fxIdx++));
265 if (fxLinkedTargetCcyIdx > 0)
266 fxTarget =
exp(*states.at(1).at(fxIdx));
267 fxFixing = fxSource / fxTarget;
278 if (effFloor != Null<Real>())
281 if (effCap != Null<Real>())
284 RandomVariable(n, isNakedOption && effFloor == Null<Real>() ? -1.0 : 1.0);
285 effectiveRate = swapletRate + floorletRate - capletRate;
289 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : ibor->nominal()) * ibor->accrualPeriod()) *
290 effectiveRate * fxFixing;
296 if (
auto cms = QuantLib::ext::dynamic_pointer_cast<CmsCoupon>(flow)) {
297 Real fixedRate = cms->fixingDate() <=
today_ ? (cms->rate() - cms->spread()) / cms->gearing() : Null<Real>();
298 Size indexCcyIdx =
model_->ccyIndex(cms->index()->currency());
299 Real simTime =
time(cms->fixingDate());
300 if (cms->fixingDate() >
today_) {
305 if (fxLinkedSimTime != Null<Real>()) {
310 info.
amountCalculator = [
this, indexCcyIdx, cms, simTime, fixedRate, isFxLinked, fxLinkedForeignNominal,
311 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isCapFloored,
312 isNakedOption, effFloor,
313 effCap, isFxIndexed](
const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
315 fixedRate != Null<Real>()
317 :
lgmVectorised_[indexCcyIdx].fixing(cms->index(), cms->fixingDate(), simTime, *states.at(0).at(0));
319 if (isFxLinked || isFxIndexed) {
320 if (fxLinkedFixedFxRate != Null<Real>()) {
325 if (fxLinkedSourceCcyIdx > 0)
326 fxSource =
exp(*states.at(1).at(fxIdx++));
327 if (fxLinkedTargetCcyIdx > 0)
328 fxTarget =
exp(*states.at(1).at(fxIdx));
329 fxFixing = fxSource / fxTarget;
340 if (effFloor != Null<Real>())
343 if (effCap != Null<Real>())
346 RandomVariable(n, isNakedOption && effFloor == Null<Real>() ? -1.0 : 1.0);
347 effectiveRate = swapletRate + floorletRate - capletRate;
352 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cms->nominal()) * cms->accrualPeriod()) *
353 effectiveRate * fxFixing;
359 if (
auto on = QuantLib::ext::dynamic_pointer_cast<OvernightIndexedCoupon>(flow)) {
360 Real simTime = std::max(0.0,
time(on->valueDates().front()));
361 Size indexCcyIdx =
model_->ccyIndex(on->index()->currency());
365 if (fxLinkedSimTime != Null<Real>()) {
370 info.
amountCalculator = [
this, indexCcyIdx, on, simTime, isFxLinked, fxLinkedForeignNominal,
371 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
372 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
374 on->overnightIndex(), on->fixingDates(), on->valueDates(), on->dt(), on->rateCutoff(),
375 on->includeSpread(), on->spread(), on->gearing(), on->lookback(), Null<Real>(), Null<Real>(),
false,
376 false, simTime, *states.at(0).at(0));
378 if (isFxLinked || isFxIndexed) {
379 if (fxLinkedFixedFxRate != Null<Real>()) {
384 if (fxLinkedSourceCcyIdx > 0)
385 fxSource =
exp(*states.at(1).at(fxIdx++));
386 if (fxLinkedTargetCcyIdx > 0)
387 fxTarget =
exp(*states.at(1).at(fxIdx));
388 fxFixing = fxSource / fxTarget;
392 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : on->nominal()) * on->accrualPeriod()) *
393 effectiveRate * fxFixing;
399 if (
auto cfon = QuantLib::ext::dynamic_pointer_cast<CappedFlooredOvernightIndexedCoupon>(flow)) {
400 Real simTime = std::max(0.0,
time(cfon->underlying()->valueDates().front()));
401 Size indexCcyIdx =
model_->ccyIndex(cfon->underlying()->index()->currency());
405 if (fxLinkedSimTime != Null<Real>()) {
410 info.
amountCalculator = [
this, indexCcyIdx, cfon, simTime, isFxLinked, fxLinkedForeignNominal,
411 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
412 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
414 cfon->underlying()->overnightIndex(), cfon->underlying()->fixingDates(),
415 cfon->underlying()->valueDates(), cfon->underlying()->dt(), cfon->underlying()->rateCutoff(),
416 cfon->underlying()->includeSpread(), cfon->underlying()->spread(), cfon->underlying()->gearing(),
417 cfon->underlying()->lookback(), cfon->cap(), cfon->floor(), cfon->localCapFloor(), cfon->nakedOption(),
418 simTime, *states.at(0).at(0));
420 if (isFxLinked || isFxIndexed) {
421 if (fxLinkedFixedFxRate != Null<Real>()) {
426 if (fxLinkedSourceCcyIdx > 0)
427 fxSource =
exp(*states.at(1).at(fxIdx++));
428 if (fxLinkedTargetCcyIdx > 0)
429 fxTarget =
exp(*states.at(1).at(fxIdx));
430 fxFixing = fxSource / fxTarget;
433 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfon->nominal()) * cfon->accrualPeriod()) *
434 effectiveRate * fxFixing;
440 if (
auto av = QuantLib::ext::dynamic_pointer_cast<AverageONIndexedCoupon>(flow)) {
441 Real simTime = std::max(0.0,
time(av->valueDates().front()));
442 Size indexCcyIdx =
model_->ccyIndex(av->index()->currency());
446 if (fxLinkedSimTime != Null<Real>()) {
451 info.
amountCalculator = [
this, indexCcyIdx, av, simTime, isFxLinked, fxLinkedForeignNominal,
452 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
453 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
455 av->overnightIndex(), av->fixingDates(), av->valueDates(), av->dt(), av->rateCutoff(),
false,
456 av->spread(), av->gearing(), av->lookback(), Null<Real>(), Null<Real>(),
false,
false, simTime,
457 *states.at(0).at(0));
459 if (isFxLinked || isFxIndexed) {
460 if (fxLinkedFixedFxRate != Null<Real>()) {
465 if (fxLinkedSourceCcyIdx > 0)
466 fxSource =
exp(*states.at(1).at(fxIdx++));
467 if (fxLinkedTargetCcyIdx > 0)
468 fxTarget =
exp(*states.at(1).at(fxIdx));
469 fxFixing = fxSource / fxTarget;
472 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : av->nominal()) * av->accrualPeriod()) *
473 effectiveRate * fxFixing;
479 if (
auto cfav = QuantLib::ext::dynamic_pointer_cast<CappedFlooredAverageONIndexedCoupon>(flow)) {
480 Real simTime = std::max(0.0,
time(cfav->underlying()->valueDates().front()));
481 Size indexCcyIdx =
model_->ccyIndex(cfav->underlying()->index()->currency());
485 if (fxLinkedSimTime != Null<Real>()) {
490 info.
amountCalculator = [
this, indexCcyIdx, cfav, simTime, isFxLinked, fxLinkedForeignNominal,
491 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
492 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
494 cfav->underlying()->overnightIndex(), cfav->underlying()->fixingDates(),
495 cfav->underlying()->valueDates(), cfav->underlying()->dt(), cfav->underlying()->rateCutoff(),
496 cfav->includeSpread(), cfav->underlying()->spread(), cfav->underlying()->gearing(),
497 cfav->underlying()->lookback(), cfav->cap(), cfav->floor(), cfav->localCapFloor(), cfav->nakedOption(),
498 simTime, *states.at(0).at(0));
500 if (isFxLinked || isFxIndexed) {
501 if (fxLinkedFixedFxRate != Null<Real>()) {
506 if (fxLinkedSourceCcyIdx > 0)
507 fxSource =
exp(*states.at(1).at(fxIdx++));
508 if (fxLinkedTargetCcyIdx > 0)
509 fxTarget =
exp(*states.at(1).at(fxIdx));
510 fxFixing = fxSource / fxTarget;
513 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfav->nominal()) * cfav->accrualPeriod()) *
514 effectiveRate * fxFixing;
520 if (
auto bma = QuantLib::ext::dynamic_pointer_cast<AverageBMACoupon>(flow)) {
521 Real simTime = std::max(0.0,
time(bma->fixingDates().front()));
522 Size indexCcyIdx =
model_->ccyIndex(bma->index()->currency());
526 if (fxLinkedSimTime != Null<Real>()) {
530 info.
amountCalculator = [
this, indexCcyIdx, bma, simTime, isFxLinked, fxLinkedForeignNominal,
531 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
532 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
534 QuantLib::ext::dynamic_pointer_cast<BMAIndex>(bma->index()), bma->fixingDates(), bma->accrualStartDate(),
535 bma->accrualEndDate(),
false, bma->spread(), bma->gearing(), Null<Real>(), Null<Real>(),
false, simTime,
536 *states.at(0).at(0));
538 if (isFxLinked || isFxIndexed) {
539 if (fxLinkedFixedFxRate != Null<Real>()) {
544 if (fxLinkedSourceCcyIdx > 0)
545 fxSource =
exp(*states.at(1).at(fxIdx++));
546 if (fxLinkedTargetCcyIdx > 0)
547 fxTarget =
exp(*states.at(1).at(fxIdx));
548 fxFixing = fxSource / fxTarget;
551 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : bma->nominal()) * bma->accrualPeriod()) *
552 effectiveRate * fxFixing;
558 if (
auto cfbma = QuantLib::ext::dynamic_pointer_cast<CappedFlooredAverageBMACoupon>(flow)) {
559 Real simTime = std::max(0.0,
time(cfbma->underlying()->fixingDates().front()));
560 Size indexCcyIdx =
model_->ccyIndex(cfbma->underlying()->index()->currency());
564 if (fxLinkedSimTime != Null<Real>()) {
568 info.
amountCalculator = [
this, indexCcyIdx, cfbma, simTime, isFxLinked, fxLinkedForeignNominal,
569 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
570 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
572 QuantLib::ext::dynamic_pointer_cast<BMAIndex>(cfbma->underlying()->index()), cfbma->underlying()->fixingDates(),
573 cfbma->underlying()->accrualStartDate(), cfbma->underlying()->accrualEndDate(), cfbma->includeSpread(),
574 cfbma->underlying()->spread(), cfbma->underlying()->gearing(), cfbma->cap(), cfbma->floor(),
575 cfbma->nakedOption(), simTime, *states.at(0).at(0));
577 if (isFxLinked || isFxIndexed) {
578 if (fxLinkedFixedFxRate != Null<Real>()) {
583 if (fxLinkedSourceCcyIdx > 0)
584 fxSource =
exp(*states.at(1).at(fxIdx++));
585 if (fxLinkedTargetCcyIdx > 0)
586 fxTarget =
exp(*states.at(1).at(fxIdx));
587 fxFixing = fxSource / fxTarget;
590 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfbma->underlying()->nominal()) *
591 cfbma->underlying()->accrualPeriod()) *
592 effectiveRate * fxFixing;
598 if (
auto sub = QuantLib::ext::dynamic_pointer_cast<SubPeriodsCoupon1>(flow)) {
599 Real simTime = std::max(0.0,
time(sub->fixingDates().front()));
600 Size indexCcyIdx =
model_->ccyIndex(sub->index()->currency());
604 if (fxLinkedSimTime != Null<Real>()) {
609 info.
amountCalculator = [
this, indexCcyIdx, sub, simTime, isFxLinked, fxLinkedForeignNominal,
610 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixedFxRate, isFxIndexed](
611 const Size n,
const std::vector<std::vector<const RandomVariable*>>& states) {
613 simTime, *states.at(0).at(0));
615 if (isFxLinked || isFxIndexed) {
616 if (fxLinkedFixedFxRate != Null<Real>()) {
621 if (fxLinkedSourceCcyIdx > 0)
622 fxSource =
exp(*states.at(1).at(fxIdx++));
623 if (fxLinkedTargetCcyIdx > 0)
624 fxTarget =
exp(*states.at(1).at(fxIdx));
625 fxFixing = fxSource / fxTarget;
629 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : sub->nominal()) * sub->accrualPeriod()) *
630 effectiveRate * fxFixing;
636 QL_FAIL(
"McMultiLegBaseEngine::createCashflowInfo(): unhandled coupon leg " << legNo <<
" cashflow " << cfNo);
640 auto it = times.find(t);
641 QL_REQUIRE(it != times.end(),
"McMultiLegBaseEngine::cashflowPathValue(): time ("
643 <<
") not found in simulation times. This is an internal error. Contact dev.");
644 return std::distance(times.begin(), it);
648 const std::vector<std::vector<RandomVariable>>& pathValues,
649 const std::set<Real>& simulationTimes)
const {
651 Size n = pathValues[0][0].
size();
654 std::vector<RandomVariable> initialValues(
model_->stateProcess()->initialValues().size());
655 for (Size i = 0; i <
model_->stateProcess()->initialValues().size(); ++i)
658 std::vector<std::vector<const RandomVariable*>> states(cf.
simulationTimes.size());
660 std::vector<const RandomVariable*> tmp(cf.
modelIndices[i].size());
668 tmp[j] = &pathValues[simTimesIdx][cf.
modelIndices[i][j]];
688 McEngineStats::instance().other_timer.resume();
692 QL_REQUIRE(
currency_.size() ==
leg_.size(),
"McMultiLegBaseEngine: number of legs ("
693 <<
leg_.size() <<
") does not match currencies ("
695 QL_REQUIRE(
payer_.size() ==
leg_.size(),
"McMultiLegBaseEngine: number of legs ("
696 <<
leg_.size() <<
") does not match payer flag (" <<
payer_.size()
701 today_ =
model_->irlgm1f(0)->termStructure()->referenceDate();
713 std::vector<CashflowInfo> cashflowInfo;
716 for (
auto const& leg :
leg_) {
718 bool payer =
payer_[legNo];
720 for (
auto const& cashflow : leg) {
725 cashflowInfo.push_back(
createCashflowInfo(cashflow, currency, payer, legNo, cashflowNo));
734 std::set<Real> exerciseTimes;
735 std::set<Real> xvaTimes;
739 QL_REQUIRE(
exercise_->type() != Exercise::American,
740 "McMultiLegBaseEngine::calculate(): exercise style American is not supported yet.");
742 for (
auto const& d :
exercise_->dates()) {
745 exerciseTimes.insert(
time(d));
750 xvaTimes.insert(
time(d));
755 std::set<Real> cashflowGenTimes;
757 for (
auto const& info : cashflowInfo) {
758 cashflowGenTimes.insert(info.simulationTimes.begin(), info.simulationTimes.end());
759 cashflowGenTimes.insert(info.payTime);
762 cashflowGenTimes.erase(0.0);
766 std::set<Real> exerciseXvaTimes;
767 std::set<Real> simulationTimes;
769 exerciseXvaTimes.insert(exerciseTimes.begin(), exerciseTimes.end());
770 exerciseXvaTimes.insert(xvaTimes.begin(), xvaTimes.end());
772 simulationTimes.insert(cashflowGenTimes.begin(), cashflowGenTimes.end());
773 simulationTimes.insert(exerciseTimes.begin(), exerciseTimes.end());
774 simulationTimes.insert(xvaTimes.begin(), xvaTimes.end());
776 McEngineStats::instance().other_timer.stop();
780 McEngineStats::instance().path_timer.resume();
782 QL_REQUIRE(!simulationTimes.empty(),
783 "McMultiLegBaseEngine::calculate(): no simulation times, this is not expected.");
784 std::vector<std::vector<RandomVariable>> pathValues(
785 simulationTimes.size(),
787 std::vector<std::vector<const RandomVariable*>> pathValuesRef(
788 simulationTimes.size(), std::vector<const RandomVariable*>(
model_->stateProcess()->size()));
790 for (Size i = 0; i < pathValues.size(); ++i) {
791 for (Size j = 0; j < pathValues[i].size(); ++j) {
792 pathValues[i][j].expand();
793 pathValuesRef[i][j] = &pathValues[i][j];
797 TimeGrid timeGrid(simulationTimes.begin(), simulationTimes.end());
799 QuantLib::ext::shared_ptr<StochasticProcess> process =
model_->stateProcess();
800 if (
model_->dimension() == 1) {
802 auto tmp = QuantLib::ext::make_shared<IrLgm1fStateProcess>(
model_->irlgm1f(0));
803 tmp->resetCache(timeGrid.size() - 1);
805 }
else if (
auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
807 tmp->resetCache(timeGrid.size() - 1);
814 const MultiPath& path = pathGenerator->next().value;
815 for (Size j = 0; j < simulationTimes.size(); ++j) {
816 for (Size k = 0; k <
model_->stateProcess()->size(); ++k) {
817 pathValues[j][k].data()[i] = path[k][j + 1];
822 McEngineStats::instance().path_timer.stop();
824 McEngineStats::instance().calc_timer.resume();
828 std::vector<RegressionModel> regModelUndDirty(exerciseXvaTimes.size());
829 std::vector<RegressionModel> regModelUndExInto(exerciseXvaTimes.size());
830 std::vector<RegressionModel> regModelContinuationValue(exerciseXvaTimes.size());
831 std::vector<RegressionModel> regModelOption(exerciseXvaTimes.size());
833 enum class CfStatus { open, cached, done };
834 std::vector<CfStatus> cfStatus(cashflowInfo.size(), CfStatus::open);
840 std::vector<RandomVariable> amountCache(cashflowInfo.size());
842 Size counter = exerciseXvaTimes.size() - 1;
844 for (
auto t = exerciseXvaTimes.rbegin(); t != exerciseXvaTimes.rend(); ++t) {
846 bool isExerciseTime = exerciseTimes.find(*t) != exerciseTimes.end();
847 bool isXvaTime = xvaTimes.find(*t) != xvaTimes.end();
849 for (Size i = 0; i < cashflowInfo.size(); ++i) {
854 if (cfStatus[i] == CfStatus::open) {
855 if (cashflowInfo[i].exIntoCriterionTime > *t) {
857 pathValueUndDirty += tmp;
858 pathValueUndExInto += tmp;
859 cfStatus[i] = CfStatus::done;
862 pathValueUndDirty += tmp;
863 amountCache[i] = tmp;
864 cfStatus[i] = CfStatus::cached;
866 }
else if (cfStatus[i] == CfStatus::cached) {
867 if (cashflowInfo[i].exIntoCriterionTime > *t) {
868 pathValueUndExInto += amountCache[i];
869 cfStatus[i] = CfStatus::done;
870 amountCache[i].
clear();
877 *t, cashflowInfo, [&cfStatus](std::size_t i) {
return cfStatus[i] == CfStatus::done; }, **
model_,
883 if (isExerciseTime) {
884 auto exerciseValue = regModelUndExInto[counter].apply(
model_->stateProcess()->initialValues(),
885 pathValuesRef, simulationTimes);
887 *t, cashflowInfo, [&cfStatus](std::size_t i) {
return cfStatus[i] == CfStatus::done; }, **
model_,
892 auto continuationValue = regModelContinuationValue[counter].apply(
model_->stateProcess()->initialValues(),
893 pathValuesRef, simulationTimes);
896 pathValueUndExInto, pathValueOption);
898 *t, cashflowInfo, [&cfStatus](std::size_t i) {
return cfStatus[i] == CfStatus::done; }, **
model_,
905 *t, cashflowInfo, [&cfStatus](std::size_t i) {
return cfStatus[i] != CfStatus::open; }, **
model_,
913 *t, cashflowInfo, [&cfStatus](std::size_t i) {
return cfStatus[i] == CfStatus::done; }, **
model_,
923 for (Size i = 0; i < cashflowInfo.size(); ++i) {
924 if (cfStatus[i] == CfStatus::open)
925 pathValueUndDirty +=
cashflowPathValue(cashflowInfo[i], pathValues, simulationTimes);
935 McEngineStats::instance().calc_timer.stop();
939 amcCalculator_ = QuantLib::ext::make_shared<MultiLegBaseAmcCalculator>(
941 regModelUndExInto, regModelContinuationValue, regModelOption,
resultValue_,
942 model_->stateProcess()->initialValues(),
model_->irlgm1f(0)->currency());
948 const std::vector<Size>& externalModelIndices,
const Settlement::Type settlement,
949 const std::set<Real>& exerciseXvaTimes,
const std::set<Real>& exerciseTimes,
const std::set<Real>& xvaTimes,
950 const std::vector<McMultiLegBaseEngine::RegressionModel>& regModelUndDirty,
951 const std::vector<McMultiLegBaseEngine::RegressionModel>& regModelUndExInto,
952 const std::vector<McMultiLegBaseEngine::RegressionModel>& regModelContinuationValue,
953 const std::vector<McMultiLegBaseEngine::RegressionModel>& regModelOption,
const Real resultValue,
954 const Array& initialState,
const Currency& baseCurrency)
955 : externalModelIndices_(externalModelIndices), settlement_(settlement), exerciseXvaTimes_(exerciseXvaTimes),
956 exerciseTimes_(exerciseTimes), xvaTimes_(xvaTimes), regModelUndDirty_(regModelUndDirty),
957 regModelUndExInto_(regModelUndExInto), regModelContinuationValue_(regModelContinuationValue),
958 regModelOption_(regModelOption), resultValue_(resultValue), initialState_(initialState),
959 baseCurrency_(baseCurrency) {}
962 const std::vector<QuantLib::Real>& pathTimes, std::vector<std::vector<QuantExt::RandomVariable>>& paths,
963 const std::vector<size_t>& relevantPathIndex,
const std::vector<size_t>& relevantTimeIndex) {
967 QL_REQUIRE(!paths.empty(),
968 "MultiLegBaseAmcCalculator::simulatePath(): no future path times, this is not allowed.");
969 QL_REQUIRE(pathTimes.size() == paths.size(),
970 "MultiLegBaseAmcCalculator::simulatePath(): inconsistent pathTimes size ("
971 << pathTimes.size() <<
") and paths size (" << paths.size() <<
") - internal error.");
972 QL_REQUIRE(relevantPathIndex.size() == xvaTimes_.size(),
973 "MultiLegBaseAmcCalculator::simulatePath() inconsistent relevant path indexes ("
974 << relevantPathIndex.size() <<
") and xvaTimes (" << xvaTimes_.size() <<
") - internal error.");
976 bool stickyCloseOutRun =
false;
978 for (
size_t i = 0; i < relevantPathIndex.size(); ++i) {
979 if (relevantPathIndex[i] != relevantTimeIndex[i]) {
980 stickyCloseOutRun =
true;
987 std::vector<std::vector<const RandomVariable*>> effPaths(
991 for (Size i = 0; i < relevantPathIndex.size(); ++i) {
992 size_t pathIdx = relevantPathIndex[i];
1001 Size samples = paths.front().front().size();
1002 std::vector<RandomVariable> result(xvaTimes_.size() + 1,
RandomVariable(paths.front().front().size(), 0.0));
1010 if (exerciseTimes_.empty()) {
1012 for (
auto t : xvaTimes_) {
1013 Size ind = std::distance(exerciseXvaTimes_.begin(), exerciseXvaTimes_.find(t));
1014 QL_REQUIRE(ind < exerciseXvaTimes_.size(),
1015 "MultiLegBaseAmcCalculator::simulatePath(): internal error, xva time "
1016 << t <<
" not found in exerciseXvaTimes vector.");
1017 result[++counter] = regModelUndDirty_[ind].apply(initialState_, effPaths, xvaTimes_);
1025 if (!stickyCloseOutRun) {
1027 exercised_ = std::vector<Filter>(exerciseTimes_.size() + 1,
Filter(samples,
false));
1030 for (
auto t : exerciseTimes_) {
1033 Size ind = std::distance(exerciseXvaTimes_.begin(), exerciseXvaTimes_.find(t));
1034 QL_REQUIRE(ind != exerciseXvaTimes_.size(),
1035 "MultiLegBaseAmcCalculator::simulatePath(): internal error, exercise time "
1036 << t <<
" not found in exerciseXvaTimes vector.");
1040 RandomVariable exerciseValue = regModelUndExInto_[ind].apply(initialState_, effPaths, xvaTimes_);
1042 regModelContinuationValue_[ind].apply(initialState_, effPaths, xvaTimes_);
1044 exercised_[counter + 1] = !exercised_[counter] && exerciseValue > continuationValue &&
1054 Size xvaCounter = 0;
1055 Size exerciseCounter = 0;
1057 Filter cashExerciseValueWasAccountedForOnXvaTime(samples,
false);
1058 Filter wasExercised(samples,
false);
1060 for (
auto t : exerciseXvaTimes_) {
1062 if (
auto it = exerciseTimes_.find(t); it != exerciseTimes_.end()) {
1064 wasExercised = wasExercised || exercised_[exerciseCounter];
1067 if (xvaTimes_.find(t) != xvaTimes_.end()) {
1069 RandomVariable optionValue = regModelOption_[counter].apply(initialState_, effPaths, xvaTimes_);
1085 exercised_[exerciseCounter], regModelUndExInto_[counter].apply(initialState_, effPaths, xvaTimes_),
1086 regModelUndDirty_[counter].apply(initialState_, effPaths, xvaTimes_));
1088 if (settlement_ == Settlement::Type::Cash) {
1089 exercisedValue =
applyInverseFilter(exercisedValue, cashExerciseValueWasAccountedForOnXvaTime);
1090 cashExerciseValueWasAccountedForOnXvaTime = cashExerciseValueWasAccountedForOnXvaTime || wasExercised;
1093 result[xvaCounter + 1] =
1105 const std::vector<CashflowInfo>& cashflowInfo,
1106 const std::function<
bool(std::size_t)>& cashflowRelevant,
1109 const Real regressionVarianceCutoff)
1114 for (Size m = 0; m < model.
dimension(); ++m) {
1122 std::set<Size> modelFxIndices;
1128 for (Size i = 0; i < cashflowInfo.size(); ++i) {
1129 if (!cashflowRelevant(i))
1132 for (Size j = 0; j < cashflowInfo[i].simulationTimes.size(); ++j) {
1135 if (QuantLib::close_enough(t, 0.0))
1137 for (
auto& m : cashflowInfo[i].modelIndices[j]) {
1139 if (modelFxIndices.find(m) != modelFxIndices.end())
1148 const LsmBasisSystem::PolynomialType polynomType,
1150 const std::vector<std::vector<const RandomVariable*>>& paths,
1151 const std::set<Real>& pathTimes,
const Filter& filter) {
1155 QL_REQUIRE(!isTrained_,
"McMultiLegBaseEngine::RegressionModel::train(): internal error: model is already trained, "
1156 "train() should not be called twice on the same model instance.");
1160 std::vector<const RandomVariable*> regressor;
1161 for (
auto const& [t, modelIdx] : regressorTimesModelIndices_) {
1162 auto pt = pathTimes.find(t);
1163 QL_REQUIRE(pt != pathTimes.end(),
1164 "McMultiLegBaseEngine::RegressionModel::train(): internal error: did not find regressor time "
1165 << t <<
" in pathTimes.");
1166 regressor.push_back(paths[std::distance(pathTimes.begin(), pt)][modelIdx]);
1171 std::vector<RandomVariable> transformedRegressor;
1175 regressor =
vec2vecptr(transformedRegressor);
1180 if (!regressor.empty()) {
1196 "McMultiLegBaseEngine::RegressionModel::train(): internal error: regressand is not identically "
1197 "zero, but no regressor was built.");
1207 const std::vector<std::vector<const RandomVariable*>>& paths,
1208 const std::set<Real>& pathTimes)
const {
1212 QL_REQUIRE(isTrained_,
"McMultiLegBaseEngine::RegressionMdeol::apply(): internal error: model is not trained.");
1216 QL_REQUIRE(!paths.empty() && !paths.front().empty(),
1217 "McMultiLegBaseEngine::RegressionMdeol::apply(): paths are empty or have empty first component");
1218 Size samples = paths.front().front()->size();
1222 if (regressionCoeffs_.empty())
1227 std::vector<RandomVariable> initialStateValues(initialState.size());
1228 std::vector<const RandomVariable*> initialStatePointer(initialState.size());
1229 for (Size j = 0; j < initialState.size(); ++j) {
1230 initialStateValues[j] =
RandomVariable(samples, initialState[j]);
1231 initialStatePointer[j] = &initialStateValues[j];
1236 std::vector<const RandomVariable*> regressor(regressorTimesModelIndices_.size());
1237 std::vector<RandomVariable> tmp(regressorTimesModelIndices_.size());
1240 for (
auto const& [t, modelIdx] : regressorTimesModelIndices_) {
1241 auto pt = pathTimes.find(t);
1242 if (pt != pathTimes.end()) {
1246 regressor[i] = paths[std::distance(pathTimes.begin(), pt)][modelIdx];
1253 auto t2 = std::lower_bound(pathTimes.begin(), pathTimes.end(), t);
1257 if (t2 == pathTimes.end()) {
1258 regressor[i] = paths[pathTimes.size() - 1][modelIdx];
1266 const RandomVariable* s2 = paths[std::distance(pathTimes.begin(), t2)][modelIdx];
1270 if (t2 == pathTimes.begin()) {
1272 s1 = initialStatePointer[modelIdx];
1274 time1 = *std::next(t2, -1);
1275 s1 = paths[std::distance(pathTimes.begin(), std::next(t2, -1))][modelIdx];
1282 tmp[i] = alpha1 * *s1 + alpha2 * *s2;
1283 regressor[i] = &tmp[i];
1290 std::vector<RandomVariable> transformedRegressor;
1291 if(!coordinateTransform_.empty()) {
1293 regressor =
vec2vecptr(transformedRegressor);
coupon paying the weighted average of the daily overnight rate
coupon paying a capped / floored average bma rate
Size pIdx(const AssetType t, const Size i, const Size offset=0) const
Size components(const AssetType t) const
Size stateVariables(const AssetType t, const Size i) const
std::vector< QuantExt::RandomVariable > simulatePath(const std::vector< QuantLib::Real > &pathTimes, std::vector< std::vector< QuantExt::RandomVariable > > &paths, const std::vector< size_t > &relevantPathIndex, const std::vector< size_t > &relevantTimeIndex) override
MultiLegBaseAmcCalculator(const std::vector< Size > &externalModelIndices, const Settlement::Type settlement, const std::set< Real > &exerciseXvaTimes, const std::set< Real > &exerciseTimes, const std::set< Real > &xvaTimes, const std::vector< McMultiLegBaseEngine::RegressionModel > ®ModelUndDirty, const std::vector< McMultiLegBaseEngine::RegressionModel > ®ModelUndExInto, const std::vector< McMultiLegBaseEngine::RegressionModel > ®ModelContinuationValue, const std::vector< McMultiLegBaseEngine::RegressionModel > ®ModelOption, const Real resultValue, const Array &initialState, const Currency &baseCurrency)
RegressionModel()=default
std::set< std::pair< Real, Size > > regressorTimesModelIndices_
void train(const Size polynomOrder, const LsmBasisSystem::PolynomialType polynomType, const RandomVariable ®ressand, const std::vector< std::vector< const RandomVariable * > > &paths, const std::set< Real > &pathTimes, const Filter &filter=Filter())
RandomVariable apply(const Array &initialState, const std::vector< std::vector< const RandomVariable * > > &paths, const std::set< Real > &pathTimes) const
RegressorModel regressorModel_
std::vector< LgmVectorised > lgmVectorised_
SequenceType calibrationPathGenerator_
Settlement::Type optionSettlement_
Real regressionVarianceCutoff_
std::vector< Currency > currency_
QuantLib::ext::shared_ptr< Exercise > exercise_
RandomVariable cashflowPathValue(const CashflowInfo &cf, const std::vector< std::vector< RandomVariable > > &pathValues, const std::set< Real > &simulationTimes) const
SobolBrownianGenerator::Ordering ordering_
SobolRsg::DirectionIntegers directionIntegers_
Real resultUnderlyingNpv_
LsmBasisSystem::PolynomialType polynomType_
std::vector< Date > simulationDates_
static constexpr Real tinyTime
QuantLib::ext::shared_ptr< AmcCalculator > amcCalculator_
CashflowInfo createCashflowInfo(QuantLib::ext::shared_ptr< CashFlow > flow, const Currency &payCcy, bool payer, Size legNo, Size cfNo) const
McMultiLegBaseEngine(const Handle< CrossAssetModel > &model, const SequenceType calibrationPathGenerator, const SequenceType pricingPathGenerator, const Size calibrationSamples, const Size pricingSamples, const Size calibrationSeed, const Size pricingSeed, const Size polynomOrder, const LsmBasisSystem::PolynomialType polynomType, const SobolBrownianGenerator::Ordering ordering, const SobolRsg::DirectionIntegers directionIntegers, const std::vector< Handle< YieldTermStructure > > &discountCurves=std::vector< Handle< YieldTermStructure > >(), const std::vector< Date > &simulationDates=std::vector< Date >(), const std::vector< Size > &externalModelIndices=std::vector< Size >(), const bool minimalObsDate=true, const RegressorModel regressorModel=RegressorModel::Simple, const Real regressionVarianceCutoff=Null< Real >())
Size timeIndex(const Time t, const std::set< Real > &simulationTimes) const
Real time(const Date &d) const
std::vector< Handle< YieldTermStructure > > discountCurves_
Handle< CrossAssetModel > model_
QuantLib::ext::shared_ptr< AmcCalculator > amcCalculator() const
std::vector< bool > payer_
bool includeSettlementDateFlows_
std::vector< Size > externalModelIndices_
Coupon paying a fixed rate but with an FX linked notional.
Coupon paying a Libor-type index but with an FX linked notional.
coupon with an indexed notional
ir LGM 1f model state process
base MC engine for multileg (option) instruments
std::vector< std::function< RandomVariable(const std::vector< const RandomVariable * > &)> > multiPathBasisSystem(Size dim, Size order, QuantLib::LsmBasisSystem::PolynomialType type, Size basisSystemSizeBound)
CompiledFormula exp(CompiledFormula x)
QuantLib::ext::shared_ptr< MultiPathGeneratorBase > makeMultiPathGenerator(const SequenceType s, const QuantLib::ext::shared_ptr< StochasticProcess > &process, const TimeGrid &timeGrid, const BigNatural seed, const SobolBrownianGenerator::Ordering ordering, const SobolRsg::DirectionIntegers directionIntegers)
Make function for path generators.
bool close_enough_all(const RandomVariable &x, const RandomVariable &y)
RandomVariable conditionalResult(const Filter &f, RandomVariable x, const RandomVariable &y)
std::vector< const RandomVariable * > vec2vecptr(const std::vector< RandomVariable > &values)
RandomVariable expectation(const RandomVariable &r)
RandomVariable conditionalExpectation(const std::vector< const RandomVariable * > ®ressor, const std::vector< std::function< RandomVariable(const std::vector< const RandomVariable * > &)> > &basisFn, const Array &coefficients)
RandomVariable applyInverseFilter(RandomVariable x, const Filter &f)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
Matrix pcaCoordinateTransform(const std::vector< const RandomVariable * > ®ressor, const Real varianceCutoff)
Array regressionCoefficients(RandomVariable r, std::vector< const RandomVariable * > regressor, const std::vector< std::function< RandomVariable(const std::vector< const RandomVariable * > &)> > &basisFn, const Filter &filter, const RandomVariableRegressionMethod regressionMethod, const std::string &debugLabel)
std::vector< RandomVariable > applyCoordinateTransform(const std::vector< const RandomVariable * > ®ressor, const Matrix &transform)
coupon paying the compounded daily overnight rate, copy of QL class, added includeSpread flag
ql utility class for random variables
std::vector< Real > simulationTimes
std::vector< std::vector< Size > > modelIndices
std::function< RandomVariable(const Size n, const std::vector< std::vector< const RandomVariable * > > &)> amountCalculator
Real at(const Size i) const
Coupon with a number of sub-periods.