Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
numericlgmriskparticipationagreementengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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
20
25
26#include <ql/cashflows/capflooredcoupon.hpp>
27#include <ql/cashflows/fixedratecoupon.hpp>
28#include <ql/cashflows/floatingratecoupon.hpp>
29#include <ql/cashflows/simplecashflow.hpp>
30#include <ql/exercise.hpp>
31#include <ql/experimental/coupons/strippedcapflooredcoupon.hpp>
32
33namespace ore {
34namespace data {
35using namespace QuantExt;
36
38 const std::string& baseCcy, const std::map<std::string, Handle<YieldTermStructure>>& discountCurves,
39 const std::map<std::string, Handle<Quote>>& fxSpots, const QuantLib::ext::shared_ptr<LinearGaussMarkovModel>& model,
40 const Real sy, const Size ny, const Real sx, const Size nx,
41 const Handle<DefaultProbabilityTermStructure>& defaultCurve, const Handle<Quote>& recoveryRate,
42 const Size maxGapDays, const Size maxDiscretisationPoints)
43 : RiskParticipationAgreementBaseEngine(baseCcy, discountCurves, fxSpots, defaultCurve, recoveryRate, maxGapDays,
44 maxDiscretisationPoints),
45 LgmConvolutionSolver2(model, sy, ny, sx, nx) {
46 registerWith(LgmConvolutionSolver2::model());
47}
48
49namespace {
50RandomVariable computeIborRate(const RandomVariable& fixing, const Real spread, const Real gearing, const Real floor,
51 const Real cap, const bool nakedOption) {
52 Size n = fixing.size();
53 RandomVariable rate;
54 if (nakedOption) {
55 // compute value of embedded cap / floor
56 RandomVariable floorletRate(n, 0.0), capletRate(n, 0.0);
57 if (floor != -QL_MAX_REAL) {
58 Real effStrike = (floor - spread) / gearing;
59 floorletRate =
60 RandomVariable(n, gearing) * max(RandomVariable(n, effStrike) - fixing, RandomVariable(n, 0.0));
61 }
62 if (cap != QL_MAX_REAL) {
63 Real effStrike = (cap - spread) / gearing;
64 capletRate =
65 RandomVariable(n, gearing) * max(fixing - RandomVariable(n, effStrike), RandomVariable(n, 0.0));
66 }
67 // same logic as in StrippedCapFlooredCoupon, i.e. embedded caps / floors are considered long
68 // if the leg is receiving, otherwise short, and a long collar is a long floor + short cap
69 if (floor != -QL_MAX_REAL && cap != QL_MAX_REAL)
70 rate = floorletRate - capletRate;
71 else
72 rate = floorletRate + capletRate;
73 } else {
74 // straight capped / floored coupon
75 rate = max(min(RandomVariable(n, gearing) * fixing + RandomVariable(n, spread), RandomVariable(n, cap)),
76 RandomVariable(n, floor));
77 }
78 return rate;
79}
80
81class IborCouponAnalyzer {
82public:
83 explicit IborCouponAnalyzer(const QuantLib::ext::shared_ptr<CashFlow>& c) {
84 scf_ = QuantLib::ext::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(c);
85 if (scf_)
86 cf_ = scf_->underlying();
87 else
88 cf_ = QuantLib::ext::dynamic_pointer_cast<CappedFlooredCoupon>(c);
89 QuantLib::ext::shared_ptr<CashFlow> cc = c;
90 if (cf_)
91 cc = cf_->underlying();
92 ibor_ = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(cc);
93 }
94 // might be nulltr if input cf is not a (capped/floored) ibor coupon
95 QuantLib::ext::shared_ptr<IborCoupon> underlying() const { return ibor_; }
96 // QL_MAX_REAL if not a capped/flored coupon or if no cap present
97 Real cap() const {
98 if (cf_ && cf_->cap() != Null<Real>())
99 return cf_->cap();
100 else
101 return QL_MAX_REAL;
102 }
103 // -QL_MAX_REAL if not a capped/flored coupon or if no floor present
104 Real floor() const {
105 if (cf_ && cf_->floor() != Null<Real>())
106 return cf_->floor();
107 else
108 return -QL_MAX_REAL;
109 }
110 // is this a stripped cf coupon?
111 bool nakedOption() const { return scf_ != nullptr; }
112
113private:
114 QuantLib::ext::shared_ptr<CappedFlooredCoupon> cf_;
115 QuantLib::ext::shared_ptr<StrippedCappedFlooredCoupon> scf_;
116 QuantLib::ext::shared_ptr<IborCoupon> ibor_;
117};
118
119class ONCouponAnalyzer {
120public:
121 explicit ONCouponAnalyzer(const QuantLib::ext::shared_ptr<CashFlow>& c) {
122 cfcomp_ = QuantLib::ext::dynamic_pointer_cast<QuantExt::CappedFlooredOvernightIndexedCoupon>(c);
123 cfavg_ = QuantLib::ext::dynamic_pointer_cast<QuantExt::CappedFlooredAverageONIndexedCoupon>(c);
124 if (cfcomp_ != nullptr)
125 comp_ = cfcomp_->underlying();
126 else
127 comp_ = QuantLib::ext::dynamic_pointer_cast<QuantExt::OvernightIndexedCoupon>(c);
128 if (cfavg_ != nullptr)
129 avg_ = cfavg_->underlying();
130 else
131 avg_ = QuantLib::ext::dynamic_pointer_cast<QuantExt::AverageONIndexedCoupon>(c);
132 }
133
134 bool isONCoupon() const { return comp_ != nullptr || avg_ != nullptr; }
135 bool isAveraging() const { return avg_ != nullptr; }
136
137 const std::vector<Date>& fixingDates() const {
138 if (comp_ != nullptr)
139 return comp_->fixingDates();
140 else if (avg_ != nullptr)
141 return avg_->fixingDates();
142 else {
143 QL_FAIL("internal error, requested fixingDates from ONCouponAnalyzer, but no on coupon is given.");
144 }
145 }
146
147 const std::vector<Date>& valueDates() const {
148 if (comp_ != nullptr)
149 return comp_->valueDates();
150 else if (avg_ != nullptr)
151 return avg_->valueDates();
152 else {
153 QL_FAIL("internal error, requested valueDates from ONCouponAnalyzer, but no on coupon is given.");
154 }
155 }
156
157 const std::vector<Real>& dt() const {
158 if (comp_ != nullptr)
159 return comp_->dt();
160 else if (avg_ != nullptr)
161 return avg_->dt();
162 else {
163 QL_FAIL("internal error, requested dt from ONCouponAnalyzer, but no on coupon is given.");
164 }
165 }
166
167 QuantLib::ext::shared_ptr<OvernightIndex> overnightIndex() const {
168 if (comp_ != nullptr)
169 return comp_->overnightIndex();
170 else if (avg_ != nullptr)
171 return avg_->overnightIndex();
172 else {
173 QL_FAIL("internal error, requested overnightIndex from ONCouponAnalyzer, but no on coupon is given.");
174 }
175 }
176
177 Real gearing() const {
178 if (comp_ != nullptr)
179 return comp_->gearing();
180 else if (avg_ != nullptr)
181 return avg_->gearing();
182 else {
183 QL_FAIL("internal error, requested gearing from ONCouponAnalyzer, but no on coupon is given.");
184 }
185 }
186
187 Real spread() const {
188 if (comp_ != nullptr)
189 return comp_->spread();
190 else if (avg_ != nullptr)
191 return avg_->spread();
192 else {
193 QL_FAIL("internal error, requested spread from ONCouponAnalyzer, but no on coupon is given.");
194 }
195 }
196
197 Real nominal() const {
198 if (comp_ != nullptr)
199 return comp_->nominal();
200 else if (avg_ != nullptr)
201 return avg_->nominal();
202 else {
203 QL_FAIL("internal error, requested nominal from ONCouponAnalyzer, but no on coupon is given.");
204 }
205 }
206
207 Real cap() const {
208 if (cfcomp_ != nullptr && cfcomp_->cap() != Null<Real>())
209 return cfcomp_->cap();
210 else if (cfavg_ != nullptr && cfavg_->cap() != Null<Real>())
211 return cfavg_->cap();
212 else
213 return QL_MAX_REAL;
214 }
215
216 Real floor() const {
217 if (cfcomp_ != nullptr && cfcomp_->floor() != Null<Real>())
218 return cfcomp_->floor();
219 else if (cfavg_ != nullptr && cfavg_->floor() != Null<Real>())
220 return cfavg_->floor();
221 else
222 return -QL_MAX_REAL;
223 }
224
225 bool localCapFloor() const {
226 if (cfcomp_ != nullptr)
227 return cfcomp_->localCapFloor();
228 else if (cfavg_ != nullptr)
229 return cfavg_->localCapFloor();
230 else
231 return false;
232 }
233
234 bool nakedOption() const {
235 if (cfcomp_ != nullptr)
236 return cfcomp_->nakedOption();
237 else if (cfavg_ != nullptr)
238 return cfavg_->nakedOption();
239 else
240 return false;
241 }
242
243 Size rateCutoff() const {
244 if (comp_ != nullptr)
245 return comp_->rateCutoff();
246 else if (avg_ != nullptr)
247 return avg_->rateCutoff();
248 else {
249 QL_FAIL("internal error, requested rateCutoff from ONCouponAnalyzer, but no on coupon is given.");
250 }
251 }
252
253 bool includeSpread() const {
254 if (comp_ != nullptr)
255 return comp_->includeSpread();
256 else if (cfavg_ != nullptr)
257 return cfavg_->includeSpread();
258 return false;
259 }
260
261 const Period& lookback() const {
262 if (comp_ != nullptr)
263 return comp_->lookback();
264 else if (avg_ != nullptr)
265 return avg_->lookback();
266 else {
267 QL_FAIL("internal error, requested lookback from ONCouponAnalyzer, but no on coupon is given.");
268 }
269 }
270
271 Real accrualPeriod() const {
272 if (comp_ != nullptr)
273 return comp_->accrualPeriod();
274 else if (avg_ != nullptr)
275 return avg_->accrualPeriod();
276 else {
277 QL_FAIL("internal error, requested accrualPeriod from ONCouponAnalyzer, but no on coupon is given.");
278 }
279 }
280
281 DayCounter dayCounter() const {
282 if (comp_ != nullptr)
283 return comp_->dayCounter();
284 else if (avg_ != nullptr)
285 return avg_->dayCounter();
286 else {
287 QL_FAIL("internal error, requested dayCounter from ONCouponAnalyzer, but no on coupon is given.");
288 }
289 }
290
291 const Date& accrualStartDate() const {
292 if (comp_ != nullptr)
293 return comp_->accrualStartDate();
294 else if (avg_ != nullptr)
295 return avg_->accrualStartDate();
296 else {
297 QL_FAIL("internal error, requested accrualStartDate from ONCouponAnalyzer, but no on coupon is given.");
298 }
299 }
300
301private:
302 QuantLib::ext::shared_ptr<QuantExt::OvernightIndexedCoupon> comp_;
303 QuantLib::ext::shared_ptr<QuantExt::AverageONIndexedCoupon> avg_;
304 QuantLib::ext::shared_ptr<QuantExt::CappedFlooredOvernightIndexedCoupon> cfcomp_;
305 QuantLib::ext::shared_ptr<QuantExt::CappedFlooredAverageONIndexedCoupon> cfavg_;
306};
307
308Size getEventIndex(const std::vector<Date>& eventDates, const Date& d) {
309 auto ed = std::find(eventDates.begin(), eventDates.end(), d);
310 QL_REQUIRE(ed != eventDates.end(), "internal error, can not find event date for " << d);
311 return std::distance(eventDates.begin(), ed);
312}
313
314int getLatestRelevantCallIndex(const Date& accrualStart, const std::vector<Date>& callDates,
315 const std::vector<Date>& eventDates) {
316 auto it = std::upper_bound(callDates.begin(), callDates.end(), accrualStart);
317 if (it == callDates.end())
318 return std::numeric_limits<int>::max();
319 else if (it == callDates.begin())
320 return -1;
321 else
322 return static_cast<int>(getEventIndex(eventDates, *std::next(it, -1)));
323}
324
325RandomVariable& getRv(std::map<int, RandomVariable>& map, const int index, const Size size) {
326 return map.insert(std::make_pair(index, RandomVariable(size, 0.0))).first->second;
327}
328
329} // namespace
330
332
333 // check underlying legs are in the same ccy
334
335 for (auto const& ccy : arguments_.underlyingCcys) {
336 QL_REQUIRE(
337 ccy == arguments_.underlyingCcys.front(),
338 "NumericLgmRiskParticipationAgreementEngine::protectionLegNpv(): underlying ccys must all be the same, got "
339 << ccy << ", " << arguments_.underlyingCcys.front());
340 }
341
342 // the option dates will be the mid points of the grid intervals
343
344 std::vector<Date> optionDates;
345 for (Size i = 0; i < gridDates_.size() - 1; ++i) {
346 optionDates.push_back(gridDates_[i] + (gridDates_[i + 1] - gridDates_[i]) / 2);
347 }
348
349 // collect simulation dates for coupons:
350 // - Ibor: these are future fixing dates resp. payment dates of an already fixed Ibor coupon
351 // - OIS : max( today , first fixing date )
352 // - Fixed: pay date
353
354 std::vector<Date> couponDates;
355 for (auto const& l : arguments_.underlying) {
356 for (auto const& c : l) {
357 // already paid => skip
358 if (c->date() <= referenceDate_)
359 continue;
360 IborCouponAnalyzer iborCouponAnalyzer(c);
361 ONCouponAnalyzer onCouponAnalyzer(c);
362 if (iborCouponAnalyzer.underlying() != nullptr) {
363 if (iborCouponAnalyzer.underlying()->fixingDate() >= referenceDate_) {
364 // Ibor Coupon with future fixing date
365 couponDates.push_back(iborCouponAnalyzer.underlying()->fixingDate());
366 } else {
367 // Ibor Coupon with past fixing date
368 couponDates.push_back(c->date());
369 }
370 } else if (onCouponAnalyzer.isONCoupon()) {
371 if (onCouponAnalyzer.fixingDates().empty())
372 continue;
373 couponDates.push_back(std::max(onCouponAnalyzer.fixingDates().front(), referenceDate_));
374 } else if (QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(c) != nullptr ||
375 QuantLib::ext::dynamic_pointer_cast<SimpleCashFlow>(c) != nullptr) {
376 couponDates.push_back(c->date());
377 } else {
378 QL_FAIL("NumericLgmRiskParticipationAgreementEngine: unsupported coupon type when constructing event "
379 "dates, only (capped/floored) Ibor, OIS, Fixed, SimpleCashFlow supported");
380 }
381 }
382 }
383
384 // collect future call dates and associated rebates (if applicable)
385
386 std::vector<Date> callDates;
387 std::vector<Real> callRebates;
388 std::vector<Date> callRebatePayDates;
389 if (arguments_.exercise) {
390 Size idx = 0;
391 for (auto const& d : arguments_.exercise->dates()) {
392 if (d > referenceDate_) {
393 callDates.push_back(d);
394 if (auto r = QuantLib::ext::dynamic_pointer_cast<QuantExt::RebatedExercise>(arguments_.exercise)) {
395 callRebates.push_back(r->rebate(idx));
396 callRebatePayDates.push_back(r->rebatePaymentDate(idx));
397 } else {
398 callRebates.push_back(0);
399 callRebatePayDates.push_back(d);
400 }
401 }
402 ++idx;
403 }
404 }
405
406 // build event dates = optionDates + couponDates + exercise dates for callables / swaptions
407
408 std::set<Date> uniqueDates;
409 uniqueDates.insert(optionDates.begin(), optionDates.end());
410 uniqueDates.insert(couponDates.begin(), couponDates.end());
411 uniqueDates.insert(callDates.begin(), callDates.end());
412
413 std::vector<Date> eventDates(uniqueDates.begin(), uniqueDates.end());
414
415 // build event times
416
417 std::vector<Real> eventTimes;
418 for (auto const& d : eventDates) {
419 eventTimes.push_back(discountCurves_[baseCcy_]->timeFromReference(d));
420 }
421
422 // build objects and collect information needed for rollback
423
424 std::vector<std::vector<Real>> fixedCoupons(eventDates.size());
425 std::vector<std::vector<int>> fixedCouponsLatestRelevantCallEventIndex(eventDates.size());
426
427 std::vector<std::vector<QuantLib::ext::shared_ptr<InterestRateIndex>>> floatingIndices(eventDates.size());
428 std::vector<std::vector<Real>> floatingGearings(eventDates.size());
429 std::vector<std::vector<Real>> floatingSpreads(eventDates.size());
430 std::vector<std::vector<Real>> floatingCaps(eventDates.size());
431 std::vector<std::vector<Real>> floatingFloors(eventDates.size());
432 std::vector<std::vector<Real>> floatingMultipliers(eventDates.size());
433 std::vector<std::vector<Real>> payTimes(eventDates.size());
434 std::vector<std::vector<bool>> nakedOption(eventDates.size());
435 std::vector<std::vector<bool>> onIsAveraging(eventDates.size());
436 std::vector<std::vector<std::vector<Date>>> onFixingDates(eventDates.size());
437 std::vector<std::vector<std::vector<Date>>> onValueDates(eventDates.size());
438 std::vector<std::vector<std::vector<Real>>> onDt(eventDates.size());
439 std::vector<std::vector<Size>> onRateCutoff(eventDates.size());
440 std::vector<std::vector<bool>> onIncludeSpread(eventDates.size());
441 std::vector<std::vector<Period>> onLookback(eventDates.size());
442 std::vector<std::vector<bool>> onLocalCapFloor(eventDates.size());
443 std::vector<std::vector<int>> floatingCouponsLatestRelevantCallEventIndex(eventDates.size());
444
445 std::vector<Size> optionDateIndex(eventDates.size());
446
447 std::vector<Size> callDateIndex(eventDates.size());
448 std::vector<Real> callRebateAmount(eventDates.size());
449 std::vector<Real> callRebatePayTime(eventDates.size());
450
451 // trapped coupons w.r.t. optionDates, these are coupons that are missing on the event
452 // date although they are relevant, i.e.
453 // - Ibor Coupons with fixingDate < eventDate < paymentDate
454 // - ON Coupons with first fixingDate < eventDate < paymentDate
455 // We memorise the original event index of these coupons, so that we can include them
456 // in the npv as seen from the event date
457 std::vector<std::set<Size>> trappedCouponIndex(eventDates.size());
458
459 // trapped coupons w.r.t. call dates, these are coupons that are missing on the event
460 // date although they are relevant, i.e.
461 // - Ibor Coupons with fixingDate < eventDate <= accrualStart
462 // - ON Coupons with first fixingDate < eventDate <= accrualStart
463 // Again we memorise the original event index of these coupons, so that we can include them
464 // in the exercise into npv as seen from the event date
465 std::vector<std::set<Size>> trappedCouponIndexCall(eventDates.size());
466
467 for (Size i = 0; i < eventDates.size(); ++i) {
468
469 // set option date index (or null if it is not an option date)
470
471 auto od = std::find(optionDates.begin(), optionDates.end(), eventDates[i]);
472 if (od != optionDates.end()) {
473 optionDateIndex[i] = std::distance(optionDates.begin(), od);
474 // on option dates, search for trapped coupons (see above for definition)
475 for (auto const& l : arguments_.underlying) {
476 for (auto const& c : l) {
477 IborCouponAnalyzer iborCouponAnalyzer(c);
478 ONCouponAnalyzer onCouponAnalyzer(c);
479 if (iborCouponAnalyzer.underlying() != nullptr) {
480 if (iborCouponAnalyzer.underlying()->fixingDate() >= referenceDate_ &&
481 iborCouponAnalyzer.underlying()->fixingDate() < eventDates[i] &&
482 eventDates[i] < c->date()) {
483 trappedCouponIndex[i].insert(
484 getEventIndex(eventDates, iborCouponAnalyzer.underlying()->fixingDate()));
485 }
486 } else if (onCouponAnalyzer.isONCoupon()) {
487 if (onCouponAnalyzer.fixingDates().empty())
488 continue;
489 Date d = std::max(onCouponAnalyzer.fixingDates().front(), referenceDate_);
490 if (d < eventDates[i] && eventDates[i] < c->date()) {
491 trappedCouponIndex[i].insert(getEventIndex(eventDates, d));
492 }
493 }
494 // fixed coupons and simple cashflows are not relevant here
495 }
496 }
497 } else {
498 optionDateIndex[i] = Null<Size>();
499 }
500
501 // set exercise date index for callables / swaptions
502
503 auto cd = std::find(callDates.begin(), callDates.end(), eventDates[i]);
504 if (cd != callDates.end()) {
505 callDateIndex[i] = std::distance(callDates.begin(), cd);
506 // on call dates, search for trapped coupons, trapped coupons 2 (see above for definition)
507 for (auto const& l : arguments_.underlying) {
508 for (auto const& c : l) {
509 IborCouponAnalyzer iborCouponAnalyzer(c);
510 ONCouponAnalyzer onCouponAnalyzer(c);
511 if (iborCouponAnalyzer.underlying() != nullptr) {
512 if (iborCouponAnalyzer.underlying()->fixingDate() >= referenceDate_ &&
513 iborCouponAnalyzer.underlying()->fixingDate() < eventDates[i] &&
514 eventDates[i] <= iborCouponAnalyzer.underlying()->accrualStartDate()) {
515 trappedCouponIndexCall[i].insert(
516 getEventIndex(eventDates, iborCouponAnalyzer.underlying()->fixingDate()));
517 }
518 } else if (onCouponAnalyzer.isONCoupon()) {
519 if (onCouponAnalyzer.fixingDates().empty())
520 continue;
521 Date d = std::max(onCouponAnalyzer.fixingDates().front(), referenceDate_);
522 if (d < eventDates[i] && eventDates[i] <= onCouponAnalyzer.accrualStartDate()) {
523 trappedCouponIndexCall[i].insert(getEventIndex(eventDates, d));
524 }
525 }
526 // fixed coupons and simple cashflows are not relevant here
527 }
528 }
529 } else {
530 callDateIndex[i] = Null<Size>();
531 }
532
533 // loop over coupons and fill vectors used in rollback for the current event date
534
535 auto underlyingPayer = arguments_.underlyingPayer.begin();
536 for (auto const& l : arguments_.underlying) {
537 bool isPayer = *(underlyingPayer++);
538 for (auto const& c : l) {
539 // already paid => skip
540 if (c->date() <= referenceDate_)
541 continue;
542 IborCouponAnalyzer iborCouponAnalyzer(c);
543 ONCouponAnalyzer onCouponAnalyzer(c);
544 if (iborCouponAnalyzer.underlying() != nullptr) {
545 if (iborCouponAnalyzer.underlying()->fixingDate() >= referenceDate_ &&
546 iborCouponAnalyzer.underlying()->fixingDate() == eventDates[i]) {
547 // Ibor coupon with future fixing
548 floatingIndices[i].push_back(iborCouponAnalyzer.underlying()->iborIndex());
549 floatingGearings[i].push_back(iborCouponAnalyzer.underlying()->gearing());
550 floatingSpreads[i].push_back(iborCouponAnalyzer.underlying()->spread());
551 floatingMultipliers[i].push_back(iborCouponAnalyzer.underlying()->nominal() *
552 iborCouponAnalyzer.underlying()->accrualPeriod() *
553 (isPayer ? -1.0 : 1.0));
554 floatingCaps[i].push_back(iborCouponAnalyzer.cap());
555 floatingFloors[i].push_back(iborCouponAnalyzer.floor());
556 nakedOption[i].push_back(iborCouponAnalyzer.nakedOption());
557 payTimes[i].push_back(discountCurves_[baseCcy_]->timeFromReference(c->date()));
558 } else if (iborCouponAnalyzer.underlying()->fixingDate() < referenceDate_ &&
559 c->date() == eventDates[i]) {
560 // already fixed Ibor coupon => treat as fixed coupon
561 fixedCoupons[i].push_back((isPayer ? -1.0 : 1.0) * c->amount());
562 fixedCouponsLatestRelevantCallEventIndex[i].push_back(getLatestRelevantCallIndex(
563 iborCouponAnalyzer.underlying()->accrualStartDate(), callDates, eventDates));
564 } else {
565 continue;
566 }
567 // on coupon specifics, not relevant for ibor, but need to populate them as well
568 onFixingDates[i].push_back(std::vector<Date>());
569 onValueDates[i].push_back(std::vector<Date>());
570 onDt[i].push_back(std::vector<Real>());
571 onRateCutoff[i].push_back(0);
572 onIncludeSpread[i].push_back(false);
573 onLookback[i].push_back(0 * Days);
574 onLocalCapFloor[i].push_back(false);
575 onIsAveraging[i].push_back(false);
576 floatingCouponsLatestRelevantCallEventIndex[i].push_back(getLatestRelevantCallIndex(
577 iborCouponAnalyzer.underlying()->accrualStartDate(), callDates, eventDates));
578 } else if (onCouponAnalyzer.isONCoupon()) {
579 if (onCouponAnalyzer.fixingDates().empty())
580 continue;
581 // ON coupon (avg, comp, with or without cf)
582 Date d = std::max(onCouponAnalyzer.fixingDates().front(), referenceDate_);
583 if (d == eventDates[i]) {
584 floatingIndices[i].push_back(onCouponAnalyzer.overnightIndex());
585 floatingGearings[i].push_back(onCouponAnalyzer.gearing());
586 floatingSpreads[i].push_back(onCouponAnalyzer.spread());
587 floatingMultipliers[i].push_back(onCouponAnalyzer.nominal() * onCouponAnalyzer.accrualPeriod() *
588 (isPayer ? -1.0 : 1.0));
589 floatingCaps[i].push_back(onCouponAnalyzer.cap());
590 floatingFloors[i].push_back(onCouponAnalyzer.floor());
591 nakedOption[i].push_back(onCouponAnalyzer.nakedOption());
592 onLocalCapFloor[i].push_back(onCouponAnalyzer.localCapFloor());
593 onFixingDates[i].push_back(onCouponAnalyzer.fixingDates());
594 onValueDates[i].push_back(onCouponAnalyzer.valueDates());
595 onDt[i].push_back(onCouponAnalyzer.dt());
596 onRateCutoff[i].push_back(onCouponAnalyzer.rateCutoff());
597 onIncludeSpread[i].push_back(onCouponAnalyzer.includeSpread());
598 onLookback[i].push_back(onCouponAnalyzer.lookback());
599 onIsAveraging[i].push_back(onCouponAnalyzer.isAveraging());
600 payTimes[i].push_back(discountCurves_[baseCcy_]->timeFromReference(c->date()));
601 floatingCouponsLatestRelevantCallEventIndex[i].push_back(
602 getLatestRelevantCallIndex(onCouponAnalyzer.accrualStartDate(), callDates, eventDates));
603 }
604 } else if (auto cpn = QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(c)) {
605 if (c->date() == eventDates[i]) {
606 // genuine fixed rate coupon
607 fixedCoupons[i].push_back((isPayer ? -1.0 : 1.0) * cpn->amount());
608 fixedCouponsLatestRelevantCallEventIndex[i].push_back(
609 getLatestRelevantCallIndex(cpn->accrualStartDate(), callDates, eventDates));
610 }
611 } else if (QuantLib::ext::dynamic_pointer_cast<SimpleCashFlow>(c) != nullptr) {
612 if (c->date() == eventDates[i]) {
613 // simple cash flow
614 fixedCoupons[i].push_back((isPayer ? -1.0 : 1.0) * c->amount());
615 fixedCouponsLatestRelevantCallEventIndex[i].push_back(
616 getLatestRelevantCallIndex(c->date(), callDates, eventDates));
617 }
618 } else {
619 QL_FAIL("NumericLgmRiskParticipationAgreementEngine: unsupported coupon type when collecting "
620 "coupon data, only (capped/floored) Ibor, OIS, Fixed, SimpleCashFlow supported");
621 }
622 }
623 }
624
625 } // loop over event dates
626
627 // setup the vectorised LGM model which we use for the calculations below
628
629 LgmVectorised lgm(model()->parametrization());
630
631 // rollback
632
633 std::map<int, RandomVariable> underlyingPv;
634 underlyingPv[0] = RandomVariable(gridSize(), 0.0);
635
636 RandomVariable swaptionPv(gridSize(), 0.0);
637
638 std::vector<Real> optionPv(optionDates.size(), 0.0);
639
640 for (int i = static_cast<int>(eventDates.size()) - 1; i >= 0; --i) {
641
642 // get states for current event dates
643
644 auto states = stateGrid(eventTimes[i]);
645
646 // rollback underlying PV to current event date, if we are not on the latest event date
647
648 if (i < static_cast<int>(eventDates.size()) - 1) {
649 for (auto& u : underlyingPv) {
650 u.second = rollback(u.second, eventTimes[i + 1], eventTimes[i]);
651 }
652 }
653
654 // move relevant PV components to index 0
655
656 for (auto& u : underlyingPv) {
657 if (u.first != 0 && i <= u.first) {
658 underlyingPv[0] += u.second;
659 u.second = RandomVariable(gridSize(), 0.0);
660 }
661 }
662 // rollback swaption PV
663
664 if (i < static_cast<int>(eventDates.size()) - 1) {
665 swaptionPv = rollback(swaptionPv, eventTimes[i + 1], eventTimes[i]);
666 }
667
668 // loop over floating coupons with fixingDate == eventDate and add them to the underlyingPv
669
670 for (Size k = 0; k < floatingIndices[i].size(); ++k) {
671 RandomVariable rate;
672 if (auto on = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(floatingIndices[i][k])) {
673 if (onIsAveraging[i][k]) {
674 rate =
675 lgm.averagedOnRate(on, onFixingDates[i][k], onValueDates[i][k], onDt[i][k], onRateCutoff[i][k],
676 onIncludeSpread[i][k], floatingSpreads[i][k], floatingGearings[i][k],
677 onLookback[i][k], floatingCaps[i][k], floatingFloors[i][k],
678 onLocalCapFloor[i][k], nakedOption[i][k], eventTimes[i], states);
679 } else {
680 rate = lgm.compoundedOnRate(on, onFixingDates[i][k], onValueDates[i][k], onDt[i][k],
681 onRateCutoff[i][k], onIncludeSpread[i][k], floatingSpreads[i][k],
682 floatingGearings[i][k], onLookback[i][k], floatingCaps[i][k],
683 floatingFloors[i][k], onLocalCapFloor[i][k], nakedOption[i][k],
684 eventTimes[i], states);
685 }
686 } else if (auto ibor = QuantLib::ext::dynamic_pointer_cast<IborIndex>(floatingIndices[i][k])) {
687 rate = computeIborRate(lgm.fixing(ibor, eventDates[i], eventTimes[i], states), floatingSpreads[i][k],
688 floatingGearings[i][k], floatingFloors[i][k], floatingCaps[i][k],
689 nakedOption[i][k]);
690 } else {
691 QL_FAIL("NumericLgmRiskParticipationAgreementEngine: unexpected index, should be IborIndex or "
692 "OvernightIndex");
693 }
694 auto tmp = rate * RandomVariable(gridSize(), floatingMultipliers[i][k]) *
695 lgm.reducedDiscountBond(eventTimes[i], payTimes[i][k], states,
696 discountCurves_[arguments_.underlyingCcys[0]]);
697 getRv(underlyingPv, floatingCouponsLatestRelevantCallEventIndex[i][k], gridSize()) += tmp;
698 }
699
700 // compute trapped coupon pvs
701
702 RandomVariable trappedCouponPv(gridSize(), 0.0), trappedCouponPvCall(gridSize(), 0.0);
703
704 if (optionDateIndex[i] != Null<Size>()) {
705
706 // loop over floating coupons with fixingDate < eventDate < paymentDate, add them to
707 // a temporary pv component for this event date ("trapped coupons")
708 // assume that the unkonwn past fixing is the same as on the event date (modeling assumption)
709
710 for (auto const& t : trappedCouponIndex[i]) {
711 for (Size k = 0; k < floatingIndices[t].size(); ++k) {
712 if (payTimes[t][k] > eventTimes[i] && !QuantLib::close_enough(eventTimes[i], payTimes[t][k])) {
713 RandomVariable rate;
714 if (auto on = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(floatingIndices[t][k])) {
715 if (onIsAveraging[t][k]) {
716 rate = lgm.averagedOnRate(
717 on, onFixingDates[t][k], onValueDates[t][k], onDt[t][k], onRateCutoff[t][k],
718 onIncludeSpread[t][k], floatingSpreads[t][k], floatingGearings[t][k],
719 onLookback[t][k], floatingCaps[t][k], floatingFloors[t][k], onLocalCapFloor[t][k],
720 nakedOption[t][k], eventTimes[i], states);
721 } else {
722 rate = lgm.compoundedOnRate(
723 on, onFixingDates[t][k], onValueDates[t][k], onDt[t][k], onRateCutoff[t][k],
724 onIncludeSpread[t][k], floatingSpreads[t][k], floatingGearings[t][k],
725 onLookback[t][k], floatingCaps[t][k], floatingFloors[t][k], onLocalCapFloor[t][k],
726 nakedOption[t][k], eventTimes[i], states);
727 }
728 } else if (auto ibor = QuantLib::ext::dynamic_pointer_cast<IborIndex>(floatingIndices[t][k])) {
729 rate = computeIborRate(
730 lgm.fixing(ibor, ibor->fixingCalendar().adjust(eventDates[i]), eventTimes[i], states),
731 floatingSpreads[t][k], floatingGearings[t][k], floatingFloors[t][k], floatingCaps[t][k],
732 nakedOption[t][k]);
733 }
734 trappedCouponPv += rate * RandomVariable(gridSize(), floatingMultipliers[t][k]) *
735 lgm.reducedDiscountBond(eventTimes[i], payTimes[t][k], states,
736 discountCurves_[arguments_.underlyingCcys[0]]);
737 }
738 }
739 }
740 }
741
742 if (callDateIndex[i] != Null<Size>()) {
743
744 // same for call dates: compute temp npv for coupons with fixingDate < eventDate <= accrualStartDate
745
746 for (auto const& t : trappedCouponIndexCall[i]) {
747 for (Size k = 0; k < floatingIndices[t].size(); ++k) {
748 if (payTimes[t][k] > eventTimes[i] && !QuantLib::close_enough(eventTimes[i], payTimes[t][k])) {
749 RandomVariable rate;
750 if (auto on = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(floatingIndices[t][k])) {
751 if (onIsAveraging[t][k]) {
752 rate = lgm.averagedOnRate(
753 on, onFixingDates[t][k], onValueDates[t][k], onDt[t][k], onRateCutoff[t][k],
754 onIncludeSpread[t][k], floatingSpreads[t][k], floatingGearings[t][k],
755 onLookback[t][k], floatingCaps[t][k], floatingFloors[t][k], onLocalCapFloor[t][k],
756 nakedOption[t][k], eventTimes[i], states);
757 } else {
758 rate = lgm.compoundedOnRate(
759 on, onFixingDates[t][k], onValueDates[t][k], onDt[t][k], onRateCutoff[t][k],
760 onIncludeSpread[t][k], floatingSpreads[t][k], floatingGearings[t][k],
761 onLookback[t][k], floatingCaps[t][k], floatingFloors[t][k], onLocalCapFloor[t][k],
762 nakedOption[t][k], eventTimes[i], states);
763 }
764 } else if (auto ibor = QuantLib::ext::dynamic_pointer_cast<IborIndex>(floatingIndices[t][k])) {
765 rate = computeIborRate(
766 lgm.fixing(ibor, ibor->fixingCalendar().adjust(eventDates[i]), eventTimes[i], states),
767 floatingSpreads[t][k], floatingGearings[t][k], floatingFloors[t][k], floatingCaps[t][k],
768 nakedOption[t][k]);
769 }
770 trappedCouponPvCall += rate * RandomVariable(gridSize(), floatingMultipliers[t][k]) *
771 lgm.reducedDiscountBond(eventTimes[i], payTimes[t][k], states,
772 discountCurves_[arguments_.underlyingCcys[0]]);
773 }
774 }
775 }
776
777 // on a call date, update swaption value
778
779 Real rt = discountCurves_[baseCcy_]->timeFromReference(callRebatePayDates[callDateIndex[i]]);
780 auto callRebateValue =
781 RandomVariable(gridSize(), callRebates[callDateIndex[i]]) *
782 lgm.reducedDiscountBond(eventTimes[i], rt, states, discountCurves_[arguments_.underlyingCcys[0]]);
783 RandomVariable callMultiplier(gridSize(), arguments_.exerciseIsLong ? 1.0 : -1.0);
784 RandomVariable nakedOptionMultiplier(gridSize(), arguments_.nakedOption ? 1.0 : -1.0);
785
786 swaptionPv = callMultiplier *
787 max(callMultiplier * swaptionPv,
788 callMultiplier * (nakedOptionMultiplier * (underlyingPv[0] + trappedCouponPvCall)) +
789 callRebateValue);
790 }
791
792 // Handle Premium
793 // If PremiumDate > EventDate we include them in swaptionPv
794
795 for (int j = 0; j < arguments_.premium.size(); j++) {
796 Real premiumAmount = 0;
797 if (arguments_.exerciseIsLong) {
798 premiumAmount = - arguments_.premium[j]->amount();
799 } else {
800 premiumAmount = arguments_.premium[j]->amount();
801 }
802
803 if (arguments_.premium[j]->date() > eventDates[i]) {
804 swaptionPv += RandomVariable(gridSize(), premiumAmount) /
805 lgm.numeraire(eventTimes[i], states, discountCurves_[arguments_.underlyingCcys[0]]);
806 }
807 }
808
809 // compute positive pv as of event date
810
811 RandomVariable tmp = swaptionPv;
812 if (!arguments_.nakedOption) {
813 tmp += trappedCouponPv;
814 for (auto const& u : underlyingPv)
815 tmp += u.second;
816 }
817
818 RandomVariable currentPositivePv = max(tmp, RandomVariable(gridSize(), 0.0));
819
820 // if on an option date, compute the option pv (as of t0)
821
822 if (optionDateIndex[i] != Null<Size>()) {
823 optionPv[optionDateIndex[i]] = rollback(currentPositivePv, eventTimes[i], 0.0).at(0);
824 }
825
826 // loop over fixed coupons with payment == eventDate and add them to the underlying pv
827
828 for (Size k = 0; k < fixedCoupons[i].size(); ++k) {
829 RandomVariable tmp = RandomVariable(gridSize(), fixedCoupons[i][k]) /
830 lgm.numeraire(eventTimes[i], states, discountCurves_[arguments_.underlyingCcys[0]]);
831 getRv(underlyingPv, fixedCouponsLatestRelevantCallEventIndex[i][k], gridSize()) += tmp;
832 }
833
834 } // loop over event dates
835
836 // roll back to t = 0
837
838 for (auto& u : underlyingPv) {
839 if (u.first != 0)
840 underlyingPv[0] += u.second;
841 }
842
843 underlyingPv[0] = rollback(underlyingPv[0], eventTimes[0], 0.0);
844
845 swaptionPv = rollback(swaptionPv, eventTimes[0], 0.0);
846
847 // compute a CVA using the option pvs
848
849 QL_REQUIRE(!fxSpots_[arguments_.underlyingCcys[0]].empty(),
850 "NumericLgmRiskParticipationAgreementEngine::protectionLegNpv(): empty fx spot for ccy pair "
851 << arguments_.underlyingCcys[0] + baseCcy_);
852
853 Real cva = 0.0;
854 for (Size i = 0; i < gridDates_.size() - 1; ++i) {
855 Real pd = defaultCurve_->defaultProbability(gridDates_[i], gridDates_[i + 1]);
856 cva += pd * (1.0 - effectiveRecoveryRate_) * optionPv[i] * fxSpots_[arguments_.underlyingCcys[0]]->value();
857 }
858
859 // set additional results (grid and option pvs)
860
861 results_.additionalResults["OptionNpvs"] = optionPv;
862 results_.additionalResults["OptionExerciseDates"] = optionDates;
863 results_.additionalResults["UnderlyingNpv"] = underlyingPv.at(0).at(0);
864 results_.additionalResults["SwaptionNpv"] = swaptionPv.at(0);
865 results_.additionalResults["FXSpot"] = fxSpots_[arguments_.underlyingCcys[0]]->value();
866
867 // return result
868
869 return arguments_.participationRate * cva;
870}
871
872} // namespace data
873} // namespace ore
const Instrument::results * results_
const boost::shared_ptr< LinearGaussMarkovModel > & model() const
RandomVariable rollback(const RandomVariable &v, const Real t1, const Real t0) const
RandomVariable stateGrid(const Real t) const
RandomVariable fixing(const boost::shared_ptr< InterestRateIndex > &index, const Date &fixingDate, const Time t, const RandomVariable &x) const
RandomVariable averagedOnRate(const boost::shared_ptr< OvernightIndex > &index, const std::vector< Date > &fixingDates, const std::vector< Date > &valueDates, const std::vector< Real > &dt, const Natural rateCutoff, const bool includeSpread, const Real spread, const Real gearing, const Period lookback, Real cap, Real floor, const bool localCapFloor, const bool nakedOption, const Time t, const RandomVariable &x) const
RandomVariable numeraire(const Time t, const RandomVariable &x, const Handle< YieldTermStructure > &discountCurve=Handle< YieldTermStructure >()) const
RandomVariable reducedDiscountBond(const Time t, const Time T, const RandomVariable &x, const Handle< YieldTermStructure > &discountCurve=Handle< YieldTermStructure >()) const
RandomVariable compoundedOnRate(const boost::shared_ptr< OvernightIndex > &index, const std::vector< Date > &fixingDates, const std::vector< Date > &valueDates, const std::vector< Real > &dt, const Natural rateCutoff, const bool includeSpread, const Real spread, const Real gearing, const Period lookback, Real cap, Real floor, const bool localCapFloor, const bool nakedOption, const Time t, const RandomVariable &x) const
NumericLgmRiskParticipationAgreementEngine(const std::string &baseCcy, const std::map< std::string, Handle< YieldTermStructure > > &discountCurves, const std::map< std::string, Handle< Quote > > &fxSpots, const QuantLib::ext::shared_ptr< QuantExt::LinearGaussMarkovModel > &model, const Real sy, const Size ny, const Real sx, const Size nx, const Handle< DefaultProbabilityTermStructure > &defaultCurve, const Handle< Quote > &recoveryRate, const Size maxGapDays=Null< Size >(), const Size maxDiscretisationPoints=Null< Size >())
std::map< std::string, Handle< YieldTermStructure > > discountCurves_
@ data
Definition: log.hpp:77
RandomVariable max(RandomVariable x, const RandomVariable &y)
RandomVariable min(RandomVariable x, const RandomVariable &y)
Size size(const ValueType &v)
Definition: value.cpp:145
Serializable Credit Default Swap.
Definition: namespaces.docs:23
QuantLib::ext::shared_ptr< QuantExt::CappedFlooredOvernightIndexedCoupon > cfcomp_
QuantLib::ext::shared_ptr< QuantExt::AverageONIndexedCoupon > avg_
QuantLib::ext::shared_ptr< CappedFlooredCoupon > cf_
QuantLib::ext::shared_ptr< QuantExt::OvernightIndexedCoupon > comp_
QuantLib::ext::shared_ptr< QuantExt::CappedFlooredAverageONIndexedCoupon > cfavg_
QuantLib::ext::shared_ptr< IborCoupon > ibor_
QuantLib::ext::shared_ptr< StrippedCappedFlooredCoupon > scf_
Real at(const Size i) const
Swap::arguments * arguments_