find representative swaption for all specified underlying cashflows a null pointer is returned if there are no live cashflows found
218 {
219
221 "exerciseDate (" << exerciseDate << ") must be greater than reference date ("
223
224
225
226 constexpr static Real h = 1.0E-4;
227
228
229
231 Leg effectiveLeg;
232 Real additionalDeterministicNpv = 0.0;
233 std::vector<bool> effectiveIsPayer;
236 if (!includeCashflow(cf, exerciseDate, criterion))
237 continue;
238 if (auto i = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(cf)) {
239 if (today <= i->
fixingDate() && i->fixingDate() < exerciseDate) {
240
241
242
243
244
245
246 Calendar cal = i->iborIndex()->fixingCalendar();
247 Date newAccrualStart = cal.advance(cal.adjust(exerciseDate), i->fixingDays() * Days);
248 Date newAccrualEnd = std::max(i->accrualEndDate(), newAccrualStart + 1);
249 Real newAccrualTime = i->dayCounter().yearFraction(newAccrualStart, newAccrualEnd);
250 Real oldAccrualTime = i->dayCounter().yearFraction(i->accrualStartDate(), i->accrualEndDate());
251 auto tmp = QuantLib::ext::make_shared<IborCoupon>(
252 i->date(), i->nominal() * oldAccrualTime / newAccrualTime, newAccrualStart, newAccrualEnd,
253 i->fixingDays(), i->iborIndex(), i->gearing(), i->spread(), i->referencePeriodStart(),
254 i->referencePeriodEnd(), i->dayCounter(), false);
255 tmp->setPricer(QuantLib::ext::make_shared<BlackIborCouponPricer>());
256 effectiveLeg.push_back(tmp);
258 } else {
259
260 effectiveLeg.push_back(cf);
262 }
263 } else if (auto o = QuantLib::ext::dynamic_pointer_cast<OvernightIndexedCoupon>(cf)) {
264
265 if (o->fixingDates().empty())
266 continue;
267
268
269
270 if (o->fixingDates().front() >= exerciseDate) {
271 o->setPricer(QuantLib::ext::make_shared<QuantExt::OvernightIndexedCouponPricer>());
272 effectiveLeg.push_back(o);
274 } else {
275
276
277
278
279
280
281
282
283 Real accrualToRatePeriodRatio = 1.0;
284 if (o->rateComputationStartDate() != Null<Date>() && o->rateComputationEndDate() != Null<Date>()) {
285 accrualToRatePeriodRatio =
286 o->dayCounter().yearFraction(o->accrualStartDate(), o->accrualEndDate()) /
287 o->dayCounter().yearFraction(o->rateComputationStartDate(), o->rateComputationEndDate());
288 }
289 Date firstFixingDate = o->fixingDates().front();
290 if (firstFixingDate < today) {
291 Date firstValueDate =
valueDate(firstFixingDate, o);
292 Date lastFixingDateBeforeToday =
293 *std::next(std::lower_bound(o->fixingDates().begin(), o->fixingDates().end(), today), -1);
294 Date lastValueDateBeforeToday =
valueDate(lastFixingDateBeforeToday, o);
295 if (lastValueDateBeforeToday > firstValueDate) {
296 auto tmp = QuantLib::ext::make_shared<OvernightIndexedCoupon>(
297 o->date(), o->nominal() * accrualToRatePeriodRatio, firstValueDate,
298 lastValueDateBeforeToday, o->overnightIndex(), o->gearing(), o->spread(),
299 o->referencePeriodStart(), o->referencePeriodEnd(), o->dayCounter(), false,
300 o->includeSpread(), 0 * Days, o->rateCutoff(), o->fixingDays());
301 tmp->setPricer(QuantLib::ext::make_shared<QuantExt::OvernightIndexedCouponPricer>());
302 additionalDeterministicNpv +=
discountCurve_->discount(tmp->date()) * tmp->amount();
303 }
304 }
305 if (o->fixingDates().back() >= today) {
306 Date firstFixingDateGeqToday =
307 *std::lower_bound(o->fixingDates().begin(), o->fixingDates().end(), today);
308 Date firstValueDateGeqToday =
valueDate(firstFixingDateGeqToday, o);
309 Date lastValueDate =
valueDate(o->fixingDates().back(), o);
310 Date startDate = o->index()->fixingCalendar().adjust(exerciseDate);
311 Date endDate = std::max(lastValueDate, startDate + 1);
312 Real factor = o->dayCounter().yearFraction(firstValueDateGeqToday, lastValueDate) /
313 o->dayCounter().yearFraction(startDate, endDate);
314 auto tmp = QuantLib::ext::make_shared<OvernightIndexedCoupon>(
315 o->date(), o->nominal() * accrualToRatePeriodRatio * factor, startDate, endDate,
316 o->overnightIndex(), o->gearing(), o->spread(), o->referencePeriodStart(),
317 o->referencePeriodEnd(), o->dayCounter(), false, o->includeSpread(), 0 * Days, o->rateCutoff(),
318 o->fixingDays());
319 tmp->setPricer(QuantLib::ext::make_shared<OvernightIndexedCouponPricer>());
320 effectiveLeg.push_back(tmp);
322 }
323 }
324 } else if (auto o = QuantLib::ext::dynamic_pointer_cast<AverageONIndexedCoupon>(cf)) {
325
326 if (o->fixingDates().empty())
327 continue;
328
329 if (o->fixingDates().front() >= exerciseDate) {
330 o->setPricer(QuantLib::ext::make_shared<QuantExt::AverageONIndexedCouponPricer>());
331 effectiveLeg.push_back(o);
333 } else {
334
335 Real accrualToRatePeriodRatio = 1.0;
336 if (o->rateComputationStartDate() != Null<Date>() && o->rateComputationEndDate() != Null<Date>()) {
337 accrualToRatePeriodRatio =
338 o->dayCounter().yearFraction(o->accrualStartDate(), o->accrualEndDate()) /
339 o->dayCounter().yearFraction(o->rateComputationStartDate(), o->rateComputationEndDate());
340 }
341 Date firstFixingDate = o->fixingDates().front();
342 if (firstFixingDate < today) {
343 Date firstValueDate =
valueDate(firstFixingDate, o);
344 Date lastFixingDateBeforeToday =
345 *std::next(std::lower_bound(o->fixingDates().begin(), o->fixingDates().end(), today), -1);
346 Date lastValueDateBeforeToday =
valueDate(lastFixingDateBeforeToday, o);
347 if (lastValueDateBeforeToday > firstValueDate) {
348 auto tmp = QuantLib::ext::make_shared<AverageONIndexedCoupon>(
349 o->date(), o->nominal() * accrualToRatePeriodRatio, firstValueDate,
350 lastValueDateBeforeToday, o->overnightIndex(), o->gearing(), o->spread(), o->rateCutoff(),
351 o->dayCounter(), 0 * Days, o->fixingDays());
352 tmp->setPricer(QuantLib::ext::make_shared<QuantExt::AverageONIndexedCouponPricer>());
353 additionalDeterministicNpv +=
discountCurve_->discount(tmp->date()) * tmp->amount();
354 }
355 }
356 if (o->fixingDates().back() >= today) {
357 Date firstFixingDateGeqToday =
358 *std::lower_bound(o->fixingDates().begin(), o->fixingDates().end(), today);
359 Date firstValueDateGeqToday =
valueDate(firstFixingDateGeqToday, o);
360 Date lastValueDate =
valueDate(o->fixingDates().back(), o);
361 Date startDate = o->index()->fixingCalendar().adjust(exerciseDate);
362 Date endDate = std::max(lastValueDate, startDate + 1);
363 Real factor = o->dayCounter().yearFraction(firstValueDateGeqToday, lastValueDate) /
364 o->dayCounter().yearFraction(startDate, endDate);
365 auto tmp = QuantLib::ext::make_shared<AverageONIndexedCoupon>(
366 o->date(), o->nominal() * accrualToRatePeriodRatio * factor, startDate, endDate,
367 o->overnightIndex(), o->gearing(), o->spread(), o->rateCutoff(), o->dayCounter(), 0 * Days,
368 o->fixingDays());
369 tmp->setPricer(QuantLib::ext::make_shared<AverageONIndexedCouponPricer>());
370 effectiveLeg.push_back(tmp);
372 }
373 }
374 } else if (QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(cf) != nullptr ||
375 QuantLib::ext::dynamic_pointer_cast<SimpleCashFlow>(cf) != nullptr) {
376 effectiveLeg.push_back(cf);
378 } else {
379 QL_FAIL("internal error: coupon type in modelLinkedUnderlying_ not supported in representativeSwaption()");
380 }
381 }
382
383 if (effectiveLeg.empty())
384 return QuantLib::ext::shared_ptr<Swaption>();
385
386
387 exerciseDate =
swapIndexBase_->fixingCalendar().adjust(exerciseDate);
388
389
391
392
393 model_->parametrization()->shift() = -
model_->parametrization()->H(t_ex);
394
395
396 Real nominalSum = 0.0, nominalSumAbs = 0.0, strikeGuess = 0.0;
397 Size nCpns = 0;
400 strikeGuess += f->rate() * std::abs(f->nominal());
402 nominalSumAbs += std::abs(f->nominal());
403 nCpns++;
404 }
405 }
406 Real nominalGuess = nominalSum / static_cast<Real>(nCpns);
408 strikeGuess = 0.01;
409 else
410 strikeGuess /= nominalSumAbs;
411
412
413 Real maturityGuess = ActualActual(ActualActual::ISDA).yearFraction(exerciseDate, CashFlows::maturityDate(
modelLinkedUnderlying_));
414
415
416 struct Matcher : public CostFunction {
417 QuantLib::ext::shared_ptr<VanillaSwap> underlyingSwap(const QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase,
418 const Period& maturity) const {
419
420 return MakeVanillaSwap(maturity, swapIndexBase->iborIndex(), 0.0)
421 .withEffectiveDate(swapIndexBase->valueDate(exerciseDate))
422 .withFixedLegCalendar(swapIndexBase->fixingCalendar())
423 .withFixedLegDayCount(swapIndexBase->dayCounter())
424 .withFixedLegTenor(swapIndexBase->fixedLegTenor())
425 .withFixedLegConvention(swapIndexBase->fixedLegConvention())
426 .withFixedLegTerminationDateConvention(swapIndexBase->fixedLegConvention())
427 .receiveFixed(true)
428 .withNominal(1.0);
429 }
430 void setState(const Real state) const {
431 for (auto const& c : modelCurves)
432 c->state(state);
433 }
434 std::tuple<Size, Real> periodFromTime(Real t) const {
435 t *= 12.0;
436 Size months = static_cast<Size>(std::floor(t));
437 Real alpha = t - static_cast<Real>(months);
438 return std::make_tuple(months, alpha);
439 }
440 Array values(const Array& x) const override {
441 Real maturityTime = std::min(x[2] * x[2], maxMaturityTime);
442
443
444 Size months;
445 Real alpha;
446 std::tie(months, alpha) = periodFromTime(maturityTime);
447 RawResult rawResult;
448
449 auto res = cachedRawResults.find(months);
450 if (res != cachedRawResults.end()) {
451 rawResult = res->second;
452 } else {
453 Period lowerMaturity = months * Months;
454 Period upperMaturity = lowerMaturity + 1 * Months;
455
456 QuantLib::ext::shared_ptr<VanillaSwap> underlyingLower, underlyingUpper;
457 if (lowerMaturity > 0 * Months)
458 underlyingLower = underlyingSwap(modelSwapIndexBase, lowerMaturity);
459 underlyingUpper = underlyingSwap(modelSwapIndexBase, upperMaturity);
460 if (underlyingLower)
461 underlyingLower->setPricingEngine(engine);
462 underlyingUpper->setPricingEngine(engine);
463 setState(0.0);
464 rawResult.npv0_l = underlyingLower ? underlyingLower->NPV() : 0.0;
465 rawResult.bps0_l = underlyingLower ? underlyingLower->fixedLegBPS() * 1.0E4 : 0.0;
466 rawResult.npv0_u = underlyingUpper->NPV();
467 rawResult.bps0_u = underlyingUpper->fixedLegBPS() * 1.0E4;
468 setState(h);
469 rawResult.npvu_l = underlyingLower ? underlyingLower->NPV() : 0.0;
470 rawResult.bpsu_l = underlyingLower ? underlyingLower->fixedLegBPS() * 1.0E4 : 0.0;
471 rawResult.npvu_u = underlyingUpper->NPV();
472 rawResult.bpsu_u = underlyingUpper->fixedLegBPS() * 1.0E4;
473 setState(-h);
474 rawResult.npvd_l = underlyingLower ? underlyingLower->NPV() : 0.0;
475 rawResult.bpsd_l = underlyingLower ? underlyingLower->fixedLegBPS() * 1.0E4 : 0.0;
476 rawResult.npvd_u = underlyingUpper->NPV();
477 rawResult.bpsd_u = underlyingUpper->fixedLegBPS() * 1.0E4;
478 cachedRawResults[months] = rawResult;
479 }
480
481 Real v0_l = x[0] * (rawResult.npv0_l + rawResult.bps0_l * x[1]);
482 Real v0_u = x[0] * (rawResult.npv0_u + rawResult.bps0_u * x[1]);
483 Real vu_l = x[0] * (rawResult.npvu_l + rawResult.bpsu_l * x[1]);
484 Real vu_u = x[0] * (rawResult.npvu_u + rawResult.bpsu_u * x[1]);
485 Real vd_l = x[0] * (rawResult.npvd_l + rawResult.bpsd_l * x[1]);
486 Real vd_u = x[0] * (rawResult.npvd_u + rawResult.bpsd_u * x[1]);
487
488 Real v0 = v0_l * (1.0 - alpha) + v0_u * alpha;
489 Real vu = vu_l * (1.0 - alpha) + vu_u * alpha;
490 Real vd = vd_l * (1.0 - alpha) + vd_u * alpha;
491
492 Real delta = (vu - vd) / (2.0 * h);
493 Real gamma = (vu + vd) / (h * h);
494
495 Array target{(v0 - npv_target) / delta_target, (delta - delta_target) / delta_target,
496 (gamma - gamma_target) / gamma_target};
497 return target;
498 }
499
500 Real h;
501 Date exerciseDate;
502 Real maxMaturityTime;
503 QuantLib::ext::shared_ptr<SwapIndex> modelSwapIndexBase;
504 QuantLib::ext::shared_ptr<PricingEngine> engine;
505 std::set<QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected>> modelCurves;
506 Real npv_target, delta_target, gamma_target;
507
508 struct RawResult {
509 double npv0_l, npvu_l, npvd_l, bps0_l, bpsu_l, bpsd_l;
510 double npv0_u, npvu_u, npvd_u, bps0_u, bpsu_u, bpsd_u;
511 };
512 mutable std::map<Size, RawResult> cachedRawResults;
513 };
514
515
516 Matcher matcher;
517 matcher.h = h;
518
519 matcher.maxMaturityTime =
discountCurve_->dayCounter().yearFraction(exerciseDate, Date::maxDate() - 365);
520 matcher.exerciseDate = exerciseDate;
522 matcher.engine = QuantLib::ext::make_shared<DiscountingSwapEngine>(Handle<YieldTermStructure>(
modelDiscountCurve_),
false,
523 exerciseDate, exerciseDate);
525 matcher.modelCurves.insert(c.second);
529
530
531 for (auto const& c : matcher.modelCurves)
532 c->referenceDate(exerciseDate);
533
534
535 Leg rec, pay;
536 for (Size c = 0; c < effectiveLeg.size(); ++c) {
537 if (effectiveIsPayer[c])
538 pay.push_back(effectiveLeg[c]);
539 else
540 rec.push_back(effectiveLeg[c]);
541 }
542 Swap exotic(pay, rec);
543 exotic.setPricingEngine(matcher.engine);
544 Real v0 = exotic.NPV();
545 matcher.setState(h);
546 Real vu = exotic.NPV();
547 matcher.setState(-h);
548 Real vd = exotic.NPV();
549 matcher.npv_target = v0 + additionalDeterministicNpv;
550 matcher.delta_target = (vu - vd) / (2.0 * h);
551 matcher.gamma_target = (vu + vd) / (h * h);
552
553
554 NoConstraint constraint;
555 Array guess{nominalGuess, strikeGuess, std::sqrt(maturityGuess)};
556 Problem problem(matcher, constraint, guess);
557 LevenbergMarquardt opt;
558 EndCriteria ec(1000, 20, 1E-8, 1E-8, 1E-8);
559 opt.minimize(problem, ec);
560
561
562 Array x = problem.currentValue();
563 Real nominal = x[0];
564 Real strike = x[1];
565 Size months;
566 Real alpha;
567 std::tie(months, alpha) = matcher.periodFromTime(std::min(x[2] * x[2], matcher.maxMaturityTime));
568 Size nMonths = std::max<Size>(1, (months + static_cast<Size>(std::round(alpha))));
569 Period maturity = nMonths * Months;
570
571 nominal *= x[2] * x[2] * 12.0 / static_cast<Real>(nMonths);
572 QuantLib::ext::shared_ptr<VanillaSwap> underlying =
580 .receiveFixed(nominal > 0.0)
581 .withNominal(std::abs(nominal));
582 underlying->setPricingEngine(QuantLib::ext::make_shared<DiscountingSwapEngine>(
discountCurve_));
583 return QuantLib::ext::make_shared<Swaption>(underlying, QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate));
584}
QuantLib::Date valueDate(const QuantLib::Date &fixingDate, const QuantLib::ext::shared_ptr< QuantLib::FloatingRateCoupon > &cpn) const
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)