Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
mcmultilegbaseengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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
30
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>
40
41namespace QuantExt {
42
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) {
57
58 if (discountCurves_.empty())
60 else {
61 QL_REQUIRE(discountCurves_.size() == model_->components(CrossAssetModel::AssetType::IR),
62 "McMultiLegBaseEngine: " << discountCurves_.size() << " discount curves given, but model has "
63 << model_->components(CrossAssetModel::AssetType::IR) << " IR components.");
64 }
65}
66
67Real McMultiLegBaseEngine::time(const Date& d) const {
68 return model_->irlgm1f(0)->termStructure()->timeFromReference(d);
69}
70
72 const Currency& payCcy, bool payer,
73 Size legNo, Size cfNo) const {
74 CashflowInfo info;
75
76 // set some common info: pay time, pay ccy index in the model, payer, exercise into decision time
77
78 info.legNo = legNo;
79 info.cfNo = cfNo;
80 info.payTime = time(flow->date());
81 info.payCcyIndex = model_->ccyIndex(payCcy);
82 info.payer = payer;
83
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.");
90 info.exIntoCriterionTime = time(cpn->accrualStartDate()) + tinyTime;
91 } else {
92 info.exIntoCriterionTime = info.payTime;
93 }
94
95 // Handle SimpleCashflow
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) {
98 return RandomVariable(n, flow->amount());
99 };
100 return info;
101 }
102
103 // handle fx linked fixed cashflow
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);
110 info.simulationTimes.push_back(fxSimTime);
111 info.modelIndices.push_back({});
112 if (fxLinkedSourceCcyIdx > 0) {
113 info.modelIndices.front().push_back(
114 model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedSourceCcyIdx - 1));
115 }
116 if (fxLinkedTargetCcyIdx > 0) {
117 info.modelIndices.front().push_back(
118 model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedTargetCcyIdx - 1));
119 }
120 }
121 info.amountCalculator = [this, fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx, fxLinkedFixingDate,
122 fxl](const Size n, const std::vector<std::vector<const RandomVariable*>>& states) {
123 if (fxLinkedFixingDate <= today_)
124 return RandomVariable(n, fxl->amount());
125 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
126 Size fxIdx = 0;
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;
132 };
133
134 return info;
135 }
136
137 // handle some wrapped coupon types: extract the wrapper info and continue with underlying flow
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>(); // if fx fixing date > today
144 Real fxLinkedForeignNominal = Null<Real>();
145 std::vector<Size> fxLinkedModelIndices;
146
147 // A Coupon could be wrapped in a FxLinkedCoupon or IndexedCoupon but not both at the same time
148 if (auto indexCpn = QuantLib::ext::dynamic_pointer_cast<IndexedCoupon>(flow)) {
149 if (auto fxIndex = QuantLib::ext::dynamic_pointer_cast<FxIndex>(indexCpn->index())) {
150 isFxIndexed = true;
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);
156 } else {
157 fxLinkedSimTime = time(fixingDate);
158 if (fxLinkedSourceCcyIdx > 0) {
159 fxLinkedModelIndices.push_back(
160 model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedSourceCcyIdx - 1));
161 }
162 if (fxLinkedTargetCcyIdx > 0) {
163 fxLinkedModelIndices.push_back(
164 model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedTargetCcyIdx - 1));
165 }
166 }
167 flow = indexCpn->underlying();
168 }
169 } else if (auto fxl = QuantLib::ext::dynamic_pointer_cast<FloatingRateFXLinkedNotionalCoupon>(flow)) {
170 isFxLinked = true;
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);
176 } else {
177 fxLinkedSimTime = time(fixingDate);
178 if (fxLinkedSourceCcyIdx > 0) {
179 fxLinkedModelIndices.push_back(model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedSourceCcyIdx - 1));
180 }
181 if (fxLinkedTargetCcyIdx > 0) {
182 fxLinkedModelIndices.push_back(model_->pIdx(CrossAssetModel::AssetType::FX, fxLinkedTargetCcyIdx - 1));
183 }
184 }
185 flow = fxl->underlying();
186 fxLinkedForeignNominal = fxl->foreignAmount();
187 }
188
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(); // this is a CappedFlooredCoupon, handled below
195 }
196
197 if (auto cf = QuantLib::ext::dynamic_pointer_cast<CappedFlooredCoupon>(flow)) {
198 isCapFloored = true;
199 effCap = cf->effectiveCap();
200 effFloor = cf->effectiveFloor();
201 flow = cf->underlying();
202 }
203
204 // handle the coupon types
205
206 if (QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(flow) != nullptr) {
207
208 if (fxLinkedSimTime != Null<Real>()) {
209 info.simulationTimes.push_back(fxLinkedSimTime);
210 info.modelIndices.push_back(fxLinkedModelIndices);
211 }
212
213 info.amountCalculator = [flow, isFxLinked, isFxIndexed, fxLinkedFixedFxRate,
214 fxLinkedSourceCcyIdx, fxLinkedTargetCcyIdx](const Size n, const std::vector<std::vector<const RandomVariable*>>& states) {
215 RandomVariable fxFixing(n, 1.0);
216 if (isFxLinked || isFxIndexed) {
217 if (fxLinkedFixedFxRate != Null<Real>()) {
218 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
219 } else {
220 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
221 Size fxIdx = 0;
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;
227 }
228 }
229 return fxFixing * RandomVariable(n, flow->amount());
230 };
231 return info;
232 }
233
234 if (auto ibor = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(flow)) {
235 Real fixedRate =
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_) {
240 info.simulationTimes.push_back(simTime);
241 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
242 }
243
244 if (fxLinkedSimTime != Null<Real>()) {
245 info.simulationTimes.push_back(fxLinkedSimTime);
246 info.modelIndices.push_back(fxLinkedModelIndices);
247 }
248
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) {
252 RandomVariable fixing = fixedRate != Null<Real>()
253 ? RandomVariable(n, fixedRate)
254 : lgmVectorised_[indexCcyIdx].fixing(ibor->index(), ibor->fixingDate(), simTime,
255 *states.at(0).at(0));
256 RandomVariable fxFixing(n, 1.0);
257 if (isFxLinked || isFxIndexed) {
258 if (fxLinkedFixedFxRate != Null<Real>()) {
259 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
260 } else {
261 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
262 Size fxIdx = 0;
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;
268 }
269 }
270
271 RandomVariable effectiveRate;
272 if (isCapFloored) {
273 RandomVariable swapletRate(n, 0.0);
274 RandomVariable floorletRate(n, 0.0);
275 RandomVariable capletRate(n, 0.0);
276 if (!isNakedOption)
277 swapletRate = RandomVariable(n, ibor->gearing()) * fixing + RandomVariable(n, ibor->spread());
278 if (effFloor != Null<Real>())
279 floorletRate = RandomVariable(n, ibor->gearing()) *
280 max(RandomVariable(n, effFloor) - fixing, RandomVariable(n, 0.0));
281 if (effCap != Null<Real>())
282 capletRate = RandomVariable(n, ibor->gearing()) *
283 max(fixing - RandomVariable(n, effCap), RandomVariable(n, 0.0)) *
284 RandomVariable(n, isNakedOption && effFloor == Null<Real>() ? -1.0 : 1.0);
285 effectiveRate = swapletRate + floorletRate - capletRate;
286 } else {
287 effectiveRate = RandomVariable(n, ibor->gearing()) * fixing + RandomVariable(n, ibor->spread());
288 }
289 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : ibor->nominal()) * ibor->accrualPeriod()) *
290 effectiveRate * fxFixing;
291 };
292
293 return info;
294 }
295
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_) {
301 info.simulationTimes.push_back(simTime);
302 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
303 }
304
305 if (fxLinkedSimTime != Null<Real>()) {
306 info.simulationTimes.push_back(fxLinkedSimTime);
307 info.modelIndices.push_back(fxLinkedModelIndices);
308 }
309
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) {
314 RandomVariable fixing =
315 fixedRate != Null<Real>()
316 ? RandomVariable(n, fixedRate)
317 : lgmVectorised_[indexCcyIdx].fixing(cms->index(), cms->fixingDate(), simTime, *states.at(0).at(0));
318 RandomVariable fxFixing(n, 1.0);
319 if (isFxLinked || isFxIndexed) {
320 if (fxLinkedFixedFxRate != Null<Real>()) {
321 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
322 } else {
323 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
324 Size fxIdx = 0;
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;
330 }
331 }
332
333 RandomVariable effectiveRate;
334 if (isCapFloored) {
335 RandomVariable swapletRate(n, 0.0);
336 RandomVariable floorletRate(n, 0.0);
337 RandomVariable capletRate(n, 0.0);
338 if (!isNakedOption)
339 swapletRate = RandomVariable(n, cms->gearing()) * fixing + RandomVariable(n, cms->spread());
340 if (effFloor != Null<Real>())
341 floorletRate = RandomVariable(n, cms->gearing()) *
342 max(RandomVariable(n, effFloor) - fixing, RandomVariable(n, 0.0));
343 if (effCap != Null<Real>())
344 capletRate = RandomVariable(n, cms->gearing()) *
345 max(fixing - RandomVariable(n, effCap), RandomVariable(n, 0.0)) *
346 RandomVariable(n, isNakedOption && effFloor == Null<Real>() ? -1.0 : 1.0);
347 effectiveRate = swapletRate + floorletRate - capletRate;
348 } else {
349 effectiveRate = RandomVariable(n, cms->gearing()) * fixing + RandomVariable(n, cms->spread());
350 }
351
352 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cms->nominal()) * cms->accrualPeriod()) *
353 effectiveRate * fxFixing;
354 };
355
356 return info;
357 }
358
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());
362 info.simulationTimes.push_back(simTime);
363 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
364
365 if (fxLinkedSimTime != Null<Real>()) {
366 info.simulationTimes.push_back(fxLinkedSimTime);
367 info.modelIndices.push_back(fxLinkedModelIndices);
368 }
369
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) {
373 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].compoundedOnRate(
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));
377 RandomVariable fxFixing(n, 1.0);
378 if (isFxLinked || isFxIndexed) {
379 if (fxLinkedFixedFxRate != Null<Real>()) {
380 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
381 } else {
382 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
383 Size fxIdx = 0;
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;
389 }
390 }
391
392 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : on->nominal()) * on->accrualPeriod()) *
393 effectiveRate * fxFixing;
394 };
395
396 return info;
397 }
398
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());
402 info.simulationTimes.push_back(simTime);
403 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
404
405 if (fxLinkedSimTime != Null<Real>()) {
406 info.simulationTimes.push_back(fxLinkedSimTime);
407 info.modelIndices.push_back(fxLinkedModelIndices);
408 }
409
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) {
413 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].compoundedOnRate(
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));
419 RandomVariable fxFixing(n, 1.0);
420 if (isFxLinked || isFxIndexed) {
421 if (fxLinkedFixedFxRate != Null<Real>()) {
422 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
423 } else {
424 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
425 Size fxIdx = 0;
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;
431 }
432 }
433 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfon->nominal()) * cfon->accrualPeriod()) *
434 effectiveRate * fxFixing;
435 };
436
437 return info;
438 }
439
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());
443 info.simulationTimes.push_back(simTime);
444 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
445
446 if (fxLinkedSimTime != Null<Real>()) {
447 info.simulationTimes.push_back(fxLinkedSimTime);
448 info.modelIndices.push_back(fxLinkedModelIndices);
449 }
450
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) {
454 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].averagedOnRate(
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));
458 RandomVariable fxFixing(n, 1.0);
459 if (isFxLinked || isFxIndexed) {
460 if (fxLinkedFixedFxRate != Null<Real>()) {
461 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
462 } else {
463 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
464 Size fxIdx = 0;
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;
470 }
471 }
472 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : av->nominal()) * av->accrualPeriod()) *
473 effectiveRate * fxFixing;
474 };
475
476 return info;
477 }
478
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());
482 info.simulationTimes.push_back(simTime);
483 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
484
485 if (fxLinkedSimTime != Null<Real>()) {
486 info.simulationTimes.push_back(fxLinkedSimTime);
487 info.modelIndices.push_back(fxLinkedModelIndices);
488 }
489
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) {
493 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].averagedOnRate(
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));
499 RandomVariable fxFixing(n, 1.0);
500 if (isFxLinked || isFxIndexed) {
501 if (fxLinkedFixedFxRate != Null<Real>()) {
502 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
503 } else {
504 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
505 Size fxIdx = 0;
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;
511 }
512 }
513 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfav->nominal()) * cfav->accrualPeriod()) *
514 effectiveRate * fxFixing;
515 };
516
517 return info;
518 }
519
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());
523 info.simulationTimes.push_back(simTime);
524 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
525
526 if (fxLinkedSimTime != Null<Real>()) {
527 info.simulationTimes.push_back(fxLinkedSimTime);
528 info.modelIndices.push_back(fxLinkedModelIndices);
529 }
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) {
533 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].averagedBmaRate(
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));
537 RandomVariable fxFixing(n, 1.0);
538 if (isFxLinked || isFxIndexed) {
539 if (fxLinkedFixedFxRate != Null<Real>()) {
540 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
541 } else {
542 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
543 Size fxIdx = 0;
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;
549 }
550 }
551 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : bma->nominal()) * bma->accrualPeriod()) *
552 effectiveRate * fxFixing;
553 };
554
555 return info;
556 }
557
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());
561 info.simulationTimes.push_back(simTime);
562 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
563
564 if (fxLinkedSimTime != Null<Real>()) {
565 info.simulationTimes.push_back(fxLinkedSimTime);
566 info.modelIndices.push_back(fxLinkedModelIndices);
567 }
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) {
571 RandomVariable effectiveRate = lgmVectorised_[indexCcyIdx].averagedBmaRate(
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));
576 RandomVariable fxFixing(n, 1.0);
577 if (isFxLinked || isFxIndexed) {
578 if (fxLinkedFixedFxRate != Null<Real>()) {
579 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
580 } else {
581 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
582 Size fxIdx = 0;
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;
588 }
589 }
590 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : cfbma->underlying()->nominal()) *
591 cfbma->underlying()->accrualPeriod()) *
592 effectiveRate * fxFixing;
593 };
594
595 return info;
596 }
597
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());
601 info.simulationTimes.push_back(simTime);
602 info.modelIndices.push_back({model_->pIdx(CrossAssetModel::AssetType::IR, indexCcyIdx)});
603
604 if (fxLinkedSimTime != Null<Real>()) {
605 info.simulationTimes.push_back(fxLinkedSimTime);
606 info.modelIndices.push_back(fxLinkedModelIndices);
607 }
608
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) {
612 RandomVariable fixing = lgmVectorised_[indexCcyIdx].subPeriodsRate(sub->index(), sub->fixingDates(),
613 simTime, *states.at(0).at(0));
614 RandomVariable fxFixing(n, 1.0);
615 if (isFxLinked || isFxIndexed) {
616 if (fxLinkedFixedFxRate != Null<Real>()) {
617 fxFixing = RandomVariable(n, fxLinkedFixedFxRate);
618 } else {
619 RandomVariable fxSource(n, 1.0), fxTarget(n, 1.0);
620 Size fxIdx = 0;
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;
626 }
627 }
628 RandomVariable effectiveRate = RandomVariable(n, sub->gearing()) * fixing + RandomVariable(n, sub->spread());
629 return RandomVariable(n, (isFxLinked ? fxLinkedForeignNominal : sub->nominal()) * sub->accrualPeriod()) *
630 effectiveRate * fxFixing;
631 };
632
633 return info;
634 }
635
636 QL_FAIL("McMultiLegBaseEngine::createCashflowInfo(): unhandled coupon leg " << legNo << " cashflow " << cfNo);
637} // createCashflowInfo()
638
639Size McMultiLegBaseEngine::timeIndex(const Time t, const std::set<Real>& times) const {
640 auto it = times.find(t);
641 QL_REQUIRE(it != times.end(), "McMultiLegBaseEngine::cashflowPathValue(): time ("
642 << t
643 << ") not found in simulation times. This is an internal error. Contact dev.");
644 return std::distance(times.begin(), it);
645}
646
648 const std::vector<std::vector<RandomVariable>>& pathValues,
649 const std::set<Real>& simulationTimes) const {
650
651 Size n = pathValues[0][0].size();
652 auto simTimesPayIdx = timeIndex(cf.payTime, simulationTimes);
653
654 std::vector<RandomVariable> initialValues(model_->stateProcess()->initialValues().size());
655 for (Size i = 0; i < model_->stateProcess()->initialValues().size(); ++i)
656 initialValues[i] = RandomVariable(n, model_->stateProcess()->initialValues()[i]);
657
658 std::vector<std::vector<const RandomVariable*>> states(cf.simulationTimes.size());
659 for (Size i = 0; i < cf.simulationTimes.size(); ++i) {
660 std::vector<const RandomVariable*> tmp(cf.modelIndices[i].size());
661 if (cf.simulationTimes[i] == 0.0) {
662 for (Size j = 0; j < cf.modelIndices[i].size(); ++j) {
663 tmp[j] = &initialValues[cf.modelIndices[i][j]];
664 }
665 } else {
666 auto simTimesIdx = timeIndex(cf.simulationTimes[i], simulationTimes);
667 for (Size j = 0; j < cf.modelIndices[i].size(); ++j) {
668 tmp[j] = &pathValues[simTimesIdx][cf.modelIndices[i][j]];
669 }
670 }
671 states[i] = tmp;
672 }
673
674 auto amount = cf.amountCalculator(n, states) /
675 lgmVectorised_[0].numeraire(
676 cf.payTime, pathValues[simTimesPayIdx][model_->pIdx(CrossAssetModel::AssetType::IR, 0)],
677 discountCurves_[0]);
678
679 if (cf.payCcyIndex > 0) {
680 amount *= exp(pathValues[simTimesPayIdx][model_->pIdx(CrossAssetModel::AssetType::FX, cf.payCcyIndex - 1)]);
681 }
682
683 return amount * RandomVariable(n, cf.payer ? -1.0 : 1.0);
684}
685
687
688 McEngineStats::instance().other_timer.resume();
689
690 // check data set by derived engines
691
692 QL_REQUIRE(currency_.size() == leg_.size(), "McMultiLegBaseEngine: number of legs ("
693 << leg_.size() << ") does not match currencies ("
694 << currency_.size() << ")");
695 QL_REQUIRE(payer_.size() == leg_.size(), "McMultiLegBaseEngine: number of legs ("
696 << leg_.size() << ") does not match payer flag (" << payer_.size()
697 << ")");
698
699 // set today's date
700
701 today_ = model_->irlgm1f(0)->termStructure()->referenceDate();
702
703 // set up lgm vectorized instances for each currency
704
705 if (lgmVectorised_.empty()) {
706 for (Size i = 0; i < model_->components(CrossAssetModel::AssetType::IR); ++i) {
707 lgmVectorised_.push_back(LgmVectorised(model_->irlgm1f(i)));
708 }
709 }
710
711 // populate the info to generate the (alive) cashflow amounts
712
713 std::vector<CashflowInfo> cashflowInfo;
714
715 Size legNo = 0;
716 for (auto const& leg : leg_) {
717 Currency currency = currency_[legNo];
718 bool payer = payer_[legNo];
719 Size cashflowNo = 0;
720 for (auto const& cashflow : leg) {
721 // we can skip cashflows that are paid
722 if (cashflow->date() < today_ || (!includeSettlementDateFlows_ && cashflow->date() == today_))
723 continue;
724 // for an alive cashflow, populate the data
725 cashflowInfo.push_back(createCashflowInfo(cashflow, currency, payer, legNo, cashflowNo));
726 // increment counter
727 ++cashflowNo;
728 }
729 ++legNo;
730 }
731
732 /* build exercise times and xva times */
733
734 std::set<Real> exerciseTimes;
735 std::set<Real> xvaTimes;
736
737 if (exercise_ != nullptr) {
738
739 QL_REQUIRE(exercise_->type() != Exercise::American,
740 "McMultiLegBaseEngine::calculate(): exercise style American is not supported yet.");
741
742 for (auto const& d : exercise_->dates()) {
743 if (d <= today_)
744 continue;
745 exerciseTimes.insert(time(d));
746 }
747 }
748
749 for (auto const& d : simulationDates_) {
750 xvaTimes.insert(time(d));
751 }
752
753 /* build cashflow generation times */
754
755 std::set<Real> cashflowGenTimes;
756
757 for (auto const& info : cashflowInfo) {
758 cashflowGenTimes.insert(info.simulationTimes.begin(), info.simulationTimes.end());
759 cashflowGenTimes.insert(info.payTime);
760 }
761
762 cashflowGenTimes.erase(0.0); // handled separately, if it is set by a cashflow
763
764 /* build combined time sets */
765
766 std::set<Real> exerciseXvaTimes; // = exercise + xva times
767 std::set<Real> simulationTimes; // = cashflowGen + exercise + xva times
768
769 exerciseXvaTimes.insert(exerciseTimes.begin(), exerciseTimes.end());
770 exerciseXvaTimes.insert(xvaTimes.begin(), xvaTimes.end());
771
772 simulationTimes.insert(cashflowGenTimes.begin(), cashflowGenTimes.end());
773 simulationTimes.insert(exerciseTimes.begin(), exerciseTimes.end());
774 simulationTimes.insert(xvaTimes.begin(), xvaTimes.end());
775
776 McEngineStats::instance().other_timer.stop();
777
778 // simulate the paths for the calibration
779
780 McEngineStats::instance().path_timer.resume();
781
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(),
786 std::vector<RandomVariable>(model_->stateProcess()->size(), RandomVariable(calibrationSamples_)));
787 std::vector<std::vector<const RandomVariable*>> pathValuesRef(
788 simulationTimes.size(), std::vector<const RandomVariable*>(model_->stateProcess()->size()));
789
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];
794 }
795 }
796
797 TimeGrid timeGrid(simulationTimes.begin(), simulationTimes.end());
798
799 QuantLib::ext::shared_ptr<StochasticProcess> process = model_->stateProcess();
800 if (model_->dimension() == 1) {
801 // use lgm process if possible for better performance
802 auto tmp = QuantLib::ext::make_shared<IrLgm1fStateProcess>(model_->irlgm1f(0));
803 tmp->resetCache(timeGrid.size() - 1);
804 process = tmp;
805 } else if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
806 // enable cache
807 tmp->resetCache(timeGrid.size() - 1);
808 }
809
810 auto pathGenerator = makeMultiPathGenerator(calibrationPathGenerator_, process, timeGrid, calibrationSeed_,
812
813 for (Size i = 0; i < calibrationSamples_; ++i) {
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];
818 }
819 }
820 }
821
822 McEngineStats::instance().path_timer.stop();
823
824 McEngineStats::instance().calc_timer.resume();
825
826 // for each xva and exercise time collect the relevant cashflow amounts and train a model on them
827
828 std::vector<RegressionModel> regModelUndDirty(exerciseXvaTimes.size()); // available on xva times
829 std::vector<RegressionModel> regModelUndExInto(exerciseXvaTimes.size()); // available on xva and ex times
830 std::vector<RegressionModel> regModelContinuationValue(exerciseXvaTimes.size()); // available on ex times
831 std::vector<RegressionModel> regModelOption(exerciseXvaTimes.size()); // available on xva and ex times
832
833 enum class CfStatus { open, cached, done };
834 std::vector<CfStatus> cfStatus(cashflowInfo.size(), CfStatus::open);
835
836 RandomVariable pathValueUndDirty(calibrationSamples_);
837 RandomVariable pathValueUndExInto(calibrationSamples_);
838 RandomVariable pathValueOption(calibrationSamples_);
839
840 std::vector<RandomVariable> amountCache(cashflowInfo.size());
841
842 Size counter = exerciseXvaTimes.size() - 1;
843
844 for (auto t = exerciseXvaTimes.rbegin(); t != exerciseXvaTimes.rend(); ++t) {
845
846 bool isExerciseTime = exerciseTimes.find(*t) != exerciseTimes.end();
847 bool isXvaTime = xvaTimes.find(*t) != xvaTimes.end();
848
849 for (Size i = 0; i < cashflowInfo.size(); ++i) {
850
851 /* we assume here that exIntoCriterionTime > t implies payTime > t, this must be ensured by the
852 createCashflowInfo method */
853
854 if (cfStatus[i] == CfStatus::open) {
855 if (cashflowInfo[i].exIntoCriterionTime > *t) {
856 auto tmp = cashflowPathValue(cashflowInfo[i], pathValues, simulationTimes);
857 pathValueUndDirty += tmp;
858 pathValueUndExInto += tmp;
859 cfStatus[i] = CfStatus::done;
860 } else if (cashflowInfo[i].payTime > *t - (includeSettlementDateFlows_ ? tinyTime : 0.0)) {
861 auto tmp = cashflowPathValue(cashflowInfo[i], pathValues, simulationTimes);
862 pathValueUndDirty += tmp;
863 amountCache[i] = tmp;
864 cfStatus[i] = CfStatus::cached;
865 }
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();
871 }
872 }
873 }
874
875 if (exercise_ != nullptr) {
876 regModelUndExInto[counter] = RegressionModel(
877 *t, cashflowInfo, [&cfStatus](std::size_t i) { return cfStatus[i] == CfStatus::done; }, **model_,
879 regModelUndExInto[counter].train(polynomOrder_, polynomType_, pathValueUndExInto, pathValuesRef,
880 simulationTimes);
881 }
882
883 if (isExerciseTime) {
884 auto exerciseValue = regModelUndExInto[counter].apply(model_->stateProcess()->initialValues(),
885 pathValuesRef, simulationTimes);
886 regModelContinuationValue[counter] = RegressionModel(
887 *t, cashflowInfo, [&cfStatus](std::size_t i) { return cfStatus[i] == CfStatus::done; }, **model_,
889 regModelContinuationValue[counter].train(polynomOrder_, polynomType_, pathValueOption, pathValuesRef,
890 simulationTimes,
891 exerciseValue > RandomVariable(calibrationSamples_, 0.0));
892 auto continuationValue = regModelContinuationValue[counter].apply(model_->stateProcess()->initialValues(),
893 pathValuesRef, simulationTimes);
894 pathValueOption = conditionalResult(exerciseValue > continuationValue &&
895 exerciseValue > RandomVariable(calibrationSamples_, 0.0),
896 pathValueUndExInto, pathValueOption);
897 regModelOption[counter] = RegressionModel(
898 *t, cashflowInfo, [&cfStatus](std::size_t i) { return cfStatus[i] == CfStatus::done; }, **model_,
900 regModelOption[counter].train(polynomOrder_, polynomType_, pathValueOption, pathValuesRef, simulationTimes);
901 }
902
903 if (isXvaTime) {
904 regModelUndDirty[counter] = RegressionModel(
905 *t, cashflowInfo, [&cfStatus](std::size_t i) { return cfStatus[i] != CfStatus::open; }, **model_,
907 regModelUndDirty[counter].train(polynomOrder_, polynomType_, pathValueUndDirty, pathValuesRef,
908 simulationTimes);
909 }
910
911 if (exercise_ != nullptr) {
912 regModelOption[counter] = RegressionModel(
913 *t, cashflowInfo, [&cfStatus](std::size_t i) { return cfStatus[i] == CfStatus::done; }, **model_,
915 regModelOption[counter].train(polynomOrder_, polynomType_, pathValueOption, pathValuesRef, simulationTimes);
916 }
917
918 --counter;
919 }
920
921 // add the remaining live cashflows to get the underlying value
922
923 for (Size i = 0; i < cashflowInfo.size(); ++i) {
924 if (cfStatus[i] == CfStatus::open)
925 pathValueUndDirty += cashflowPathValue(cashflowInfo[i], pathValues, simulationTimes);
926 }
927
928 // set the result value (= underlying value if no exercise is given, otherwise option value)
929
930 resultUnderlyingNpv_ = expectation(pathValueUndDirty).at(0) * model_->numeraire(0, 0.0, 0.0, discountCurves_[0]);
931 resultValue_ = exercise_ == nullptr
933 : expectation(pathValueOption).at(0) * model_->numeraire(0, 0.0, 0.0, discountCurves_[0]);
934
935 McEngineStats::instance().calc_timer.stop();
936
937 // construct the amc calculator
938
939 amcCalculator_ = QuantLib::ext::make_shared<MultiLegBaseAmcCalculator>(
940 externalModelIndices_, optionSettlement_, exerciseXvaTimes, exerciseTimes, xvaTimes, regModelUndDirty,
941 regModelUndExInto, regModelContinuationValue, regModelOption, resultValue_,
942 model_->stateProcess()->initialValues(), model_->irlgm1f(0)->currency());
943}
944
945QuantLib::ext::shared_ptr<AmcCalculator> McMultiLegBaseEngine::amcCalculator() const { return amcCalculator_; }
946
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) {}
960
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) {
964
965 // check input path consistency
966
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.");
975
976 bool stickyCloseOutRun = false;
977
978 for (size_t i = 0; i < relevantPathIndex.size(); ++i) {
979 if (relevantPathIndex[i] != relevantTimeIndex[i]) {
980 stickyCloseOutRun = true;
981 break;
982 }
983 }
984
985 /* put together the relevant simulation times on the input paths and check for consistency with xva times,
986 also put together the effective paths by filtering on relevant simulation times and model indices */
987 std::vector<std::vector<const RandomVariable*>> effPaths(
988 xvaTimes_.size(), std::vector<const RandomVariable*>(externalModelIndices_.size()));
989
990 Size timeIndex = 0;
991 for (Size i = 0; i < relevantPathIndex.size(); ++i) {
992 size_t pathIdx = relevantPathIndex[i];
993 for (Size j = 0; j < externalModelIndices_.size(); ++j) {
994 effPaths[timeIndex][j] = &paths[pathIdx][externalModelIndices_[j]];
995 }
996 ++timeIndex;
997 }
998
999 // init result vector
1000
1001 Size samples = paths.front().front().size();
1002 std::vector<RandomVariable> result(xvaTimes_.size() + 1, RandomVariable(paths.front().front().size(), 0.0));
1003
1004 // simulate the path: result at first time index is simply the reference date npv
1005
1006 result[0] = RandomVariable(samples, resultValue_);
1007
1008 // if we don't have an exercise, we return the dirty npv of the underlying at all times
1009
1010 if (exerciseTimes_.empty()) {
1011 Size counter = 0;
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_);
1018 }
1019 return result;
1020 }
1021
1022 /* if we have an exercise we need to determine the exercise indicators except for a sticky run
1023 where we reuse the last saved indicators */
1024
1025 if (!stickyCloseOutRun) {
1026
1027 exercised_ = std::vector<Filter>(exerciseTimes_.size() + 1, Filter(samples, false));
1028 Size counter = 0;
1029
1030 for (auto t : exerciseTimes_) {
1031
1032 // find the time in the exerciseXvaTimes vector
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.");
1037
1038 // make the exercise decision
1039
1040 RandomVariable exerciseValue = regModelUndExInto_[ind].apply(initialState_, effPaths, xvaTimes_);
1041 RandomVariable continuationValue =
1042 regModelContinuationValue_[ind].apply(initialState_, effPaths, xvaTimes_);
1043
1044 exercised_[counter + 1] = !exercised_[counter] && exerciseValue > continuationValue &&
1045 exerciseValue > RandomVariable(samples, 0.0);
1046
1047 ++counter;
1048 }
1049 }
1050
1051 // now we can populate the result using the exercise indicators
1052
1053 Size counter = 0;
1054 Size xvaCounter = 0;
1055 Size exerciseCounter = 0;
1056
1057 Filter cashExerciseValueWasAccountedForOnXvaTime(samples, false);
1058 Filter wasExercised(samples, false);
1059
1060 for (auto t : exerciseXvaTimes_) {
1061
1062 if (auto it = exerciseTimes_.find(t); it != exerciseTimes_.end()) {
1063 ++exerciseCounter;
1064 wasExercised = wasExercised || exercised_[exerciseCounter];
1065 }
1066
1067 if (xvaTimes_.find(t) != xvaTimes_.end()) {
1068
1069 RandomVariable optionValue = regModelOption_[counter].apply(initialState_, effPaths, xvaTimes_);
1070
1071 /* Exercise value is "undExInto" if we are in the period between the date on which the exercise happend and
1072 the next exercise date after that, otherwise it is the full dirty npv. This assumes that two exercise
1073 dates d1, d2 are not so close together that a coupon
1074
1075 - pays after d1, d2
1076 - but does not belong to the exercise-into underlying for both d1 and d2
1077
1078 This assumption seems reasonable, since we would never exercise on d1 but wait until d2 since the
1079 underlying which we exercise into is the same in both cases.
1080 We don't introduce a hard check for this, but we rather assume that the exercise dates are set up
1081 appropriately adjusted to the coupon periods. The worst that can happen is that the exercised value
1082 uses the full dirty npv at a too early time. */
1083
1084 RandomVariable exercisedValue = conditionalResult(
1085 exercised_[exerciseCounter], regModelUndExInto_[counter].apply(initialState_, effPaths, xvaTimes_),
1086 regModelUndDirty_[counter].apply(initialState_, effPaths, xvaTimes_));
1087
1088 if (settlement_ == Settlement::Type::Cash) {
1089 exercisedValue = applyInverseFilter(exercisedValue, cashExerciseValueWasAccountedForOnXvaTime);
1090 cashExerciseValueWasAccountedForOnXvaTime = cashExerciseValueWasAccountedForOnXvaTime || wasExercised;
1091 }
1092
1093 result[xvaCounter + 1] =
1094 max(RandomVariable(samples, 0.0), conditionalResult(wasExercised, exercisedValue, optionValue));
1095 ++xvaCounter;
1096 }
1097
1098 ++counter;
1099 }
1100
1101 return result;
1102}
1103
1105 const std::vector<CashflowInfo>& cashflowInfo,
1106 const std::function<bool(std::size_t)>& cashflowRelevant,
1107 const CrossAssetModel& model,
1108 const McMultiLegBaseEngine::RegressorModel regressorModel,
1109 const Real regressionVarianceCutoff)
1110 : observationTime_(observationTime), regressionVarianceCutoff_(regressionVarianceCutoff) {
1111
1112 // we always include the full model state as of the observation time
1113
1114 for (Size m = 0; m < model.dimension(); ++m) {
1115 regressorTimesModelIndices_.insert(std::make_pair(observationTime, m));
1116 }
1117
1118 // for LaggedFX we add past fx states
1119
1120 if (regressorModel == McMultiLegBaseEngine::RegressorModel::LaggedFX) {
1121
1122 std::set<Size> modelFxIndices;
1123 for (Size i = 1; i < model.components(CrossAssetModel::AssetType::IR); ++i) {
1124 for (Size j = 0; j < model.stateVariables(CrossAssetModel::AssetType::FX, i - 1); ++j)
1125 modelFxIndices.insert(model.pIdx(CrossAssetModel::AssetType::FX, i - 1, j));
1126 }
1127
1128 for (Size i = 0; i < cashflowInfo.size(); ++i) {
1129 if (!cashflowRelevant(i))
1130 continue;
1131 // add the cashflow simulation indices
1132 for (Size j = 0; j < cashflowInfo[i].simulationTimes.size(); ++j) {
1133 Real t = std::min(observationTime_, cashflowInfo[i].simulationTimes[j]);
1134 // the simulation time might be zero, but then we want to skip the factors
1135 if (QuantLib::close_enough(t, 0.0))
1136 continue;
1137 for (auto& m : cashflowInfo[i].modelIndices[j]) {
1138 // add FX factors
1139 if (modelFxIndices.find(m) != modelFxIndices.end())
1140 regressorTimesModelIndices_.insert(std::make_pair(t, m));
1141 }
1142 }
1143 }
1144 }
1145}
1146
1148 const LsmBasisSystem::PolynomialType polynomType,
1149 const RandomVariable& regressand,
1150 const std::vector<std::vector<const RandomVariable*>>& paths,
1151 const std::set<Real>& pathTimes, const Filter& filter) {
1152
1153 // check if the model is in the correct state
1154
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.");
1157
1158 // build the regressor
1159
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]);
1167 }
1168
1169 // factor reduction to reduce dimensionalitty and handle collinearity
1170
1171 std::vector<RandomVariable> transformedRegressor;
1172 if (regressionVarianceCutoff_ != Null<Real>()) {
1173 coordinateTransform_ = pcaCoordinateTransform(regressor, regressionVarianceCutoff_);
1174 transformedRegressor = applyCoordinateTransform(regressor, coordinateTransform_);
1175 regressor = vec2vecptr(transformedRegressor);
1176 }
1177
1178 // compute regression coefficients
1179
1180 if (!regressor.empty()) {
1181
1182 // get the basis functions
1183
1184 basisFns_ = multiPathBasisSystem(regressor.size(), polynomOrder, polynomType, Null<Size>());
1185
1186 // compute the regression coefficients
1187
1188 regressionCoeffs_ =
1189 regressionCoefficients(regressand, regressor, basisFns_, filter, RandomVariableRegressionMethod::QR);
1190
1191 } else {
1192
1193 // an empty regressor is possible if there are no relevant cashflows, but then the regressand has to be zero too
1194
1195 QL_REQUIRE(close_enough_all(regressand, RandomVariable(regressand.size(), 0.0)),
1196 "McMultiLegBaseEngine::RegressionModel::train(): internal error: regressand is not identically "
1197 "zero, but no regressor was built.");
1198 }
1199
1200 // update state of model
1201
1202 isTrained_ = true;
1203}
1204
1207 const std::vector<std::vector<const RandomVariable*>>& paths,
1208 const std::set<Real>& pathTimes) const {
1209
1210 // check if model is trained
1211
1212 QL_REQUIRE(isTrained_, "McMultiLegBaseEngine::RegressionMdeol::apply(): internal error: model is not trained.");
1213
1214 // determine sample size
1215
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();
1219
1220 // if we do not have regression coefficients, the regressand was zero
1221
1222 if (regressionCoeffs_.empty())
1223 return RandomVariable(samples, 0.0);
1224
1225 // build initial state pointer
1226
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];
1232 }
1233
1234 // build the regressor
1235
1236 std::vector<const RandomVariable*> regressor(regressorTimesModelIndices_.size());
1237 std::vector<RandomVariable> tmp(regressorTimesModelIndices_.size());
1238
1239 Size i = 0;
1240 for (auto const& [t, modelIdx] : regressorTimesModelIndices_) {
1241 auto pt = pathTimes.find(t);
1242 if (pt != pathTimes.end()) {
1243
1244 // the time is a path time, no need to interpolate the path
1245
1246 regressor[i] = paths[std::distance(pathTimes.begin(), pt)][modelIdx];
1247
1248 } else {
1249
1250 // the time is not a path time, we need to interpolate:
1251 // find the sim times and model states before and after the exercise time
1252
1253 auto t2 = std::lower_bound(pathTimes.begin(), pathTimes.end(), t);
1254
1255 // t is after last path time => flat extrapolation
1256
1257 if (t2 == pathTimes.end()) {
1258 regressor[i] = paths[pathTimes.size() - 1][modelIdx];
1259 ++i;
1260 continue;
1261 }
1262
1263 // t is before last path time
1264
1265 Real time2 = *t2;
1266 const RandomVariable* s2 = paths[std::distance(pathTimes.begin(), t2)][modelIdx];
1267
1268 Real time1;
1269 const RandomVariable* s1;
1270 if (t2 == pathTimes.begin()) {
1271 time1 = 0.0;
1272 s1 = initialStatePointer[modelIdx];
1273 } else {
1274 time1 = *std::next(t2, -1);
1275 s1 = paths[std::distance(pathTimes.begin(), std::next(t2, -1))][modelIdx];
1276 }
1277
1278 // compute the interpolated state
1279
1280 RandomVariable alpha1(samples, (time2 - t) / (time2 - time1));
1281 RandomVariable alpha2(samples, (t - time1) / (time2 - time1));
1282 tmp[i] = alpha1 * *s1 + alpha2 * *s2;
1283 regressor[i] = &tmp[i];
1284 }
1285 ++i;
1286 }
1287
1288 // transform regressor if necessary
1289
1290 std::vector<RandomVariable> transformedRegressor;
1291 if(!coordinateTransform_.empty()) {
1292 transformedRegressor = applyCoordinateTransform(regressor, coordinateTransform_);
1293 regressor = vec2vecptr(transformedRegressor);
1294 }
1295
1296 // compute result and return it
1297
1298 return conditionalExpectation(regressor, basisFns_, regressionCoeffs_);
1299}
1300
1301} // namespace QuantExt
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 > &regModelUndDirty, const std::vector< McMultiLegBaseEngine::RegressionModel > &regModelUndExInto, const std::vector< McMultiLegBaseEngine::RegressionModel > &regModelContinuationValue, const std::vector< McMultiLegBaseEngine::RegressionModel > &regModelOption, const Real resultValue, const Array &initialState, const Currency &baseCurrency)
std::set< std::pair< Real, Size > > regressorTimesModelIndices_
void train(const Size polynomOrder, const LsmBasisSystem::PolynomialType polynomType, const RandomVariable &regressand, 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
std::vector< LgmVectorised > lgmVectorised_
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_
LsmBasisSystem::PolynomialType polynomType_
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
Coupon paying a fixed rate but with an FX linked notional.
Coupon paying a Libor-type index but with an FX linked notional.
An FX linked cashflow.
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 * > &regressor, 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 * > &regressor, 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 * > &regressor, 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< 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.