152 {
153
154 Date
asof = Settings::instance().evaluationDate();
155
156 LOG(
"Writing cashflow report for " <<
asof);
167 .
addColumn(
"AccrualStartDate", Date(), 4)
173 .
addColumn(
"DiscountFactor",
double(), 10)
175 .
addColumn(
"FXRate(Local-Base)",
double(), 10)
176 .
addColumn(
"PresentValue(Base)",
double(), 10)
180 .
addColumn(
"FloorVolatility",
double(), 6)
182 .
addColumn(
"EffectiveFloorVolatility",
double(), 6)
183 .
addColumn(
"EffectiveCapVolatility",
double(), 6);
184
185 std::map<std::string, QuantLib::ext::shared_ptr<Trade>> trades = portfolio->trades();
186
187 for (auto [tradeId, trade]: trades) {
188
189 try {
190
191
192
193 if (!trade->hasCashflows()) {
194 WLOG(
"cashflow for " << trade->tradeType() <<
" " << trade->id() <<
" skipped");
195 continue;
196 }
197
198
199
200 auto addResults = trade->instrument()->additionalResults();
201
202 auto cashFlowResults = addResults.find("cashFlowResults");
203
204
205 auto lower = addResults.lower_bound("cashFlowResults");
206 auto upper = addResults.lower_bound("cashFlowResults_a");
207
208 std::map<Size, Size> cashflowNumber;
209
210 const Real multiplier = trade->instrument()->multiplier() * trade->instrument()->multiplier2();
211
212 if (lower != addResults.end()) {
213
214 for (auto cashFlowResults = lower; cashFlowResults != upper; ++cashFlowResults) {
215
216
217
218 QL_REQUIRE(cashFlowResults->second.type() == typeid(std::vector<CashFlowResults>),
219 "internal error: cashflowResults type does not match CashFlowResults: '"
220 << cashFlowResults->second.type().name() << "'");
221 std::vector<CashFlowResults> cfResults =
222 boost::any_cast<std::vector<CashFlowResults>>(cashFlowResults->second);
223
224 for (auto const& cf : cfResults) {
225 string ccy = "";
226 if (!cf.currency.empty()) {
227 ccy = cf.currency;
228 } else if (trade->legCurrencies().size() > cf.legNumber) {
229 ccy = trade->legCurrencies()[cf.legNumber];
230 } else {
231 ccy = trade->npvCurrency();
232 }
233
234 Real effectiveAmount = Null<Real>();
235 Real discountFactor = Null<Real>();
236 Real presentValue = Null<Real>();
237 Real presentValueBase = Null<Real>();
238 Real fxRateLocalBase = Null<Real>();
239 Real floorStrike = Null<Real>();
240 Real capStrike = Null<Real>();
241 Real floorVolatility = Null<Real>();
242 Real capVolatility = Null<Real>();
243 Real effectiveFloorVolatility = Null<Real>();
244 Real effectiveCapVolatility = Null<Real>();
245
246 if (cf.amount != Null<Real>())
247 effectiveAmount = cf.amount * multiplier;
248 if (cf.discountFactor != Null<Real>())
249 discountFactor = cf.discountFactor;
250 else if (!cf.currency.empty() && cf.payDate != Null<Date>() && market) {
251 discountFactor = cf.payDate <
asof
252 ? 0.0
253 : market->discountCurve(cf.currency, configuration)->discount(cf.payDate);
254 }
255 if (cf.presentValue != Null<Real>()) {
256 presentValue = cf.presentValue * multiplier;
257 } else if (effectiveAmount != Null<Real>() && discountFactor != Null<Real>()) {
258 presentValue = effectiveAmount * discountFactor;
259 }
260 if (cf.fxRateLocalBase != Null<Real>()) {
261 fxRateLocalBase = cf.fxRateLocalBase;
262 } else if (market) {
263 try {
264 fxRateLocalBase = market->fxRate(ccy + baseCurrency)->value();
265 } catch (...) {
266 }
267 }
268 if (cf.presentValueBase != Null<Real>()) {
269 presentValueBase = cf.presentValueBase;
270 } else if (presentValue != Null<Real>() && fxRateLocalBase != Null<Real>()) {
271 presentValueBase = presentValue * fxRateLocalBase;
272 }
273 if (cf.floorStrike != Null<Real>())
274 floorStrike = cf.floorStrike;
275 if (cf.capStrike != Null<Real>())
276 capStrike = cf.capStrike;
277 if (cf.floorVolatility != Null<Real>())
278 floorVolatility = cf.floorVolatility;
279 if (cf.capVolatility != Null<Real>())
280 capVolatility = cf.capVolatility;
281 if (cf.effectiveFloorVolatility != Null<Real>())
282 floorVolatility = cf.effectiveFloorVolatility;
283 if (cf.effectiveCapVolatility != Null<Real>())
284 capVolatility = cf.effectiveCapVolatility;
285
286
287
288
289
290
293 .
add(trade->tradeType())
294 .
add(++cashflowNumber[cf.legNumber])
298 .
add(effectiveAmount)
301 .
add(cf.accrualPeriod)
302 .
add(cf.accrualStartDate)
303 .
add(cf.accrualEndDate)
304 .
add(cf.accruedAmount * (cf.accruedAmount == Null<Real>() ? 1.0 : multiplier))
307 .
add(cf.notional * (cf.notional == Null<Real>() ? 1.0 : multiplier))
310 .
add(fxRateLocalBase)
311 .
add(presentValueBase)
315 .
add(floorVolatility)
317 .
add(effectiveFloorVolatility)
318 .
add(effectiveCapVolatility);
319 }
320 }
321 }
322 if (trade->legs().size() >= 1 && cashFlowResults == addResults.end()) {
323
324
325 auto maxLegNoIter = std::max_element(cashflowNumber.begin(), cashflowNumber.end());
326 Size addResultsLegs = 0;
327 if (maxLegNoIter != cashflowNumber.end())
328 addResultsLegs = maxLegNoIter->first + 1;
329
330 const vector<Leg>& legs = trade->legs();
331 for (size_t i = 0; i < legs.size(); i++) {
332 const QuantLib::Leg& leg = legs[i];
333 bool payer = trade->legPayers()[i];
334 string ccy = trade->legCurrencies()[i];
335 Handle<YieldTermStructure> discountCurve;
336 if (market)
337 discountCurve = market->discountCurve(ccy, configuration);
338 for (size_t j = 0; j < leg.size(); j++) {
339 QuantLib::ext::shared_ptr<QuantLib::CashFlow> ptrFlow = leg[j];
340 Date payDate = ptrFlow->date();
341 if (!ptrFlow->hasOccurred(
asof) || includePastCashflows) {
342 Real amount = ptrFlow->amount();
343 string flowType = "";
344 if (payer)
345 amount *= -1.0;
346 std::string ccy = trade->legCurrencies()[i];
347
348 QuantLib::ext::shared_ptr<QuantLib::Coupon> ptrCoupon =
349 QuantLib::ext::dynamic_pointer_cast<QuantLib::Coupon>(ptrFlow);
350 QuantLib::ext::shared_ptr<QuantExt::CommodityCashFlow> ptrCommCf =
351 QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(ptrFlow);
352
353 Real coupon;
354 Real accrual;
355 Real notional;
356 Date accrualStartDate, accrualEndDate;
357 Real accruedAmount;
358
359 if (ptrCoupon) {
360 coupon = ptrCoupon->rate();
361 accrual = ptrCoupon->accrualPeriod();
362 notional = ptrCoupon->nominal();
363 accrualStartDate = ptrCoupon->accrualStartDate();
364 accrualEndDate = ptrCoupon->accrualEndDate();
365 accruedAmount = ptrCoupon->accruedAmount(
asof);
366 if (payer)
367 accruedAmount *= -1.0;
368 flowType = "Interest";
369 } else if (ptrCommCf) {
370 coupon = Null<Real>();
371 accrual = Null<Real>();
372 notional =
373 ptrCommCf->periodQuantity();
374 accrualStartDate = accrualEndDate = Null<Date>();
375 accruedAmount = Null<Real>();
376 flowType = "Notional (units)";
377 } else {
378 coupon = Null<Real>();
379 accrual = Null<Real>();
380 notional = Null<Real>();
381 accrualStartDate = accrualEndDate = Null<Date>();
382 accruedAmount = Null<Real>();
383 flowType = "Notional";
384 }
385
386 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<QuantLib::Coupon>(ptrFlow)) {
388 }
389
390 QuantLib::ext::shared_ptr<QuantLib::FloatingRateCoupon> ptrFloat =
391 QuantLib::ext::dynamic_pointer_cast<QuantLib::FloatingRateCoupon>(ptrFlow);
392 QuantLib::ext::shared_ptr<QuantLib::InflationCoupon> ptrInfl =
393 QuantLib::ext::dynamic_pointer_cast<QuantLib::InflationCoupon>(ptrFlow);
394 QuantLib::ext::shared_ptr<QuantLib::IndexedCashFlow> ptrIndCf =
395 QuantLib::ext::dynamic_pointer_cast<QuantLib::IndexedCashFlow>(ptrFlow);
396 QuantLib::ext::shared_ptr<QuantExt::FXLinkedCashFlow> ptrFxlCf =
397 QuantLib::ext::dynamic_pointer_cast<QuantExt::FXLinkedCashFlow>(ptrFlow);
398 QuantLib::ext::shared_ptr<QuantExt::EquityCoupon> ptrEqCp =
399 QuantLib::ext::dynamic_pointer_cast<QuantExt::EquityCoupon>(ptrFlow);
400
402 Real fixingValue = Null<Real>();
403 if (ptrFloat) {
405 if (fixingDate >
asof)
406 flowType = "InterestProjected";
407
408 try {
409 fixingValue = ptrFloat->index()->fixing(fixingDate);
410 } catch (...) {
411 }
412
413 if (auto c = QuantLib::ext::dynamic_pointer_cast<QuantLib::IborCoupon>(ptrFloat)) {
414 fixingValue = (c->rate() - c->spread()) / c->gearing();
415 }
416
417 if (auto c = QuantLib::ext::dynamic_pointer_cast<QuantLib::CappedFlooredIborCoupon>(
418 ptrFloat)) {
419 fixingValue = (c->underlying()->rate() - c->underlying()->spread()) /
420 c->underlying()->gearing();
421 }
422
423 if (auto sc =
424 QuantLib::ext::dynamic_pointer_cast<QuantLib::StrippedCappedFlooredCoupon>(
425 ptrFloat)) {
426 if (auto c = QuantLib::ext::dynamic_pointer_cast<QuantLib::CappedFlooredIborCoupon>(
427 sc->underlying())) {
428 fixingValue = (c->underlying()->rate() - c->underlying()->spread()) /
429 c->underlying()->gearing();
430 }
431 }
432
433
434
435
436 if (auto on = QuantLib::ext::dynamic_pointer_cast<QuantExt::AverageONIndexedCoupon>(
437 ptrFloat)) {
438 fixingValue = (on->rate() - on->spread()) / on->gearing();
439 } else if (auto on =
440 QuantLib::ext::dynamic_pointer_cast<QuantExt::OvernightIndexedCoupon>(
441 ptrFloat)) {
442 fixingValue = (on->rate() - on->effectiveSpread()) / on->gearing();
443 } else if (auto c = QuantLib::ext::dynamic_pointer_cast<QuantLib::AverageBMACoupon>(
444 ptrFloat)) {
445 fixingValue = (c->rate() - c->spread()) / c->gearing();
446 } else if (auto c = QuantLib::ext::dynamic_pointer_cast<
448 fixingValue = (c->underlying()->rate() - c->underlying()->spread()) /
449 c->underlying()->gearing();
450 } else if (auto c = QuantLib::ext::dynamic_pointer_cast<
452 fixingValue = (c->underlying()->rate() - c->underlying()->effectiveSpread()) /
453 c->underlying()->gearing();
454 } else if (auto c = QuantLib::ext::dynamic_pointer_cast<
456 fixingValue = (c->underlying()->rate() - c->underlying()->spread()) /
457 c->underlying()->gearing();
458 } else if (auto sp = QuantLib::ext::dynamic_pointer_cast<QuantExt::SubPeriodsCoupon1>(
459 ptrFloat)) {
460 fixingValue = (sp->rate() - sp->spread()) / sp->gearing();
461 }
462 } else if (ptrInfl) {
464 fixingValue = ptrInfl->indexFixing();
465 flowType = "Inflation";
466 } else if (ptrIndCf) {
468 fixingValue = ptrIndCf->indexFixing();
469 flowType = "Index";
470 } else if (ptrFxlCf) {
472 fixingValue = ptrFxlCf->fxRate();
473 } else if (ptrEqCp) {
475 fixingValue = ptrEqCp->equityCurve()->fixing(fixingDate);
476 } else if (ptrCommCf) {
478 fixingValue = ptrCommCf->fixing();
479 } else {
481 fixingValue = Null<Real>();
482 }
483
484 Real effectiveAmount = Null<Real>();
485 Real discountFactor = Null<Real>();
486 Real presentValue = Null<Real>();
487 Real presentValueBase = Null<Real>();
488 Real fxRateLocalBase = Null<Real>();
489 Real floorStrike = Null<Real>();
490 Real capStrike = Null<Real>();
491 Real floorVolatility = Null<Real>();
492 Real capVolatility = Null<Real>();
493 Real effectiveFloorVolatility = Null<Real>();
494 Real effectiveCapVolatility = Null<Real>();
495
496 if (amount != Null<Real>())
497 effectiveAmount = amount * multiplier;
498
499 if (market) {
500 discountFactor = ptrFlow->hasOccurred(
asof) ? 0.0 : discountCurve->discount(payDate);
501 if (effectiveAmount != Null<Real>())
502 presentValue = discountFactor * effectiveAmount;
503 try {
504 fxRateLocalBase = market->fxRate(ccy + baseCurrency)->value();
505 presentValueBase = presentValue * fxRateLocalBase;
506 } catch (...) {
507 }
508
509
510
511
512
513 QuantLib::ext::shared_ptr<CashFlow> c = ptrFlow;
514 if (auto tmp =
515 QuantLib::ext::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(ptrFlow)) {
516 c = tmp->underlying();
517 }
518 Date volFixingDate;
519 std::string qlIndexName;
520 bool usesCapVol = false, usesSwaptionVol = false;
521 Period swaptionTenor;
522 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CappedFlooredCoupon>(c)) {
523 floorStrike = tmp->effectiveFloor();
524 capStrike = tmp->effectiveCap();
525 volFixingDate = tmp->fixingDate();
526 qlIndexName = tmp->index()->name();
527 if (auto cms = QuantLib::ext::dynamic_pointer_cast<CmsCoupon>(tmp->underlying())) {
528 swaptionTenor = cms->swapIndex()->tenor();
529 qlIndexName = cms->swapIndex()->iborIndex()->name();
530 usesSwaptionVol = true;
531 }else if(auto cms = boost::dynamic_pointer_cast<DurationAdjustedCmsCoupon>(tmp->underlying())) {
532 swaptionTenor = cms->swapIndex()->tenor();
533 qlIndexName = cms->swapIndex()->iborIndex()->name();
534 usesSwaptionVol = true;
535 }else if (auto ibor = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(tmp->underlying())) {
536 qlIndexName = ibor->index()->name();
537 usesCapVol = true;
538 }
539 } else if (auto tmp =
540 QuantLib::ext::dynamic_pointer_cast<CappedFlooredOvernightIndexedCoupon>(
541 c)) {
542 floorStrike = tmp->effectiveFloor();
543 capStrike = tmp->effectiveCap();
544 volFixingDate = tmp->underlying()->fixingDates().front();
545 qlIndexName = tmp->index()->name();
546 usesCapVol = true;
547 if (floorStrike != Null<Real>())
548 effectiveFloorVolatility = tmp->effectiveFloorletVolatility();
549 if (capStrike != Null<Real>())
550 effectiveCapVolatility = tmp->effectiveCapletVolatility();
551 } else if (auto tmp =
552 QuantLib::ext::dynamic_pointer_cast<CappedFlooredAverageONIndexedCoupon>(
553 c)) {
554 floorStrike = tmp->effectiveFloor();
555 capStrike = tmp->effectiveCap();
556 volFixingDate = tmp->underlying()->fixingDates().front();
557 qlIndexName = tmp->index()->name();
558 usesCapVol = true;
559 if (floorStrike != Null<Real>())
560 effectiveFloorVolatility = tmp->effectiveFloorletVolatility();
561 if (capStrike != Null<Real>())
562 effectiveCapVolatility = tmp->effectiveCapletVolatility();
563 } else if (auto tmp =
564 QuantLib::ext::dynamic_pointer_cast<CappedFlooredAverageBMACoupon>(c)) {
565 floorStrike = tmp->effectiveFloor();
566 capStrike = tmp->effectiveCap();
567 volFixingDate = tmp->underlying()->fixingDates().front();
568 qlIndexName = tmp->index()->name();
569 usesCapVol = true;
570 if (floorStrike != Null<Real>())
571 effectiveFloorVolatility = tmp->effectiveFloorletVolatility();
572 if (capStrike != Null<Real>())
573 effectiveCapVolatility = tmp->effectiveCapletVolatility();
574 }
575
576
577
578 if (volFixingDate != Date() && fixingDate > market->asofDate()) {
579 volFixingDate = std::max(volFixingDate, market->asofDate() + 1);
580 if (floorStrike != Null<Real>()) {
581 if (usesSwaptionVol) {
582 floorVolatility =
583 market
584 ->swaptionVol(IndexNameTranslator::instance().oreName(qlIndexName),
585 configuration)
586 ->volatility(volFixingDate, swaptionTenor, floorStrike);
587 } else if (usesCapVol && floorVolatility == Null<Real>()) {
588 floorVolatility =
589 market
590 ->capFloorVol(IndexNameTranslator::instance().oreName(qlIndexName),
591 configuration)
592 ->volatility(volFixingDate, floorStrike);
593 }
594 }
595 if (capStrike != Null<Real>()) {
596 if (usesSwaptionVol) {
597 capVolatility =
598 market
599 ->swaptionVol(IndexNameTranslator::instance().oreName(qlIndexName),
600 configuration)
601 ->volatility(volFixingDate, swaptionTenor, capStrike);
602 } else if (usesCapVol && capVolatility == Null<Real>()) {
603 capVolatility =
604 market
605 ->capFloorVol(IndexNameTranslator::instance().oreName(qlIndexName),
606 configuration)
607 ->volatility(volFixingDate, capStrike);
608 }
609 }
610 }
611 }
612
615 .
add(trade->tradeType())
617 .
add(i + addResultsLegs)
620 .
add(effectiveAmount)
624 .
add(accrualStartDate)
626 .
add(accruedAmount * (accruedAmount == Null<Real>() ? 1.0 : multiplier))
629 .
add(notional * (notional == Null<Real>() ? 1.0 : multiplier))
632 .
add(fxRateLocalBase)
633 .
add(presentValueBase)
637 .
add(floorVolatility)
639 .
add(effectiveFloorVolatility)
640 .
add(effectiveCapVolatility);
641 }
642 }
643 }
644 }
645
646 } catch (std::exception& e) {
647 StructuredTradeErrorMessage(trade->id(), trade->tradeType(), "Error during cashflow report generation",
648 e.what())
649 .log();
650 }
651 }
653 LOG(
"Cashflow report written");
654}
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
boost::shared_ptr< Coupon > unpackIndexedCoupon(const boost::shared_ptr< Coupon > &c)