Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Public Types | Public Member Functions | Private Member Functions | Private Attributes | List of all members
RepresentativeSwaptionMatcher Class Reference

#include <qle/models/representativeswaption.hpp>

+ Collaboration diagram for RepresentativeSwaptionMatcher:

Public Types

enum class  InclusionCriterion { AccrualStartGeqExercise , PayDateGtExercise }
 

Public Member Functions

 RepresentativeSwaptionMatcher (const std::vector< Leg > &underlying, const std::vector< bool > &isPayer, const QuantLib::ext::shared_ptr< SwapIndex > &standardSwapIndexBase, const bool useUnderlyingIborIndex, const Handle< YieldTermStructure > &discountCurve, const Real reversion, const Real volatility=0.0050, const Real flatRate=Null< Real >())
 
QuantLib::ext::shared_ptr< Swaption > representativeSwaption (Date exerciseDate, const InclusionCriterion criterion=InclusionCriterion::AccrualStartGeqExercise)
 

Private Member Functions

QuantLib::Date valueDate (const QuantLib::Date &fixingDate, const QuantLib::ext::shared_ptr< QuantLib::FloatingRateCoupon > &cpn) const
 

Private Attributes

const std::vector< Leg > underlying_
 
const std::vector< boolisPayer_
 
const QuantLib::ext::shared_ptr< SwapIndex > swapIndexBase_
 
const bool useUnderlyingIborIndex_
 
const Handle< YieldTermStructure > discountCurve_
 
const Real reversion_
 
const Real volatility_
 
const Real flatRate_
 
QuantLib::ext::shared_ptr< LGMmodel_
 
Leg modelLinkedUnderlying_
 
std::vector< boolmodelLinkedUnderlyingIsPayer_
 
std::map< std::string, QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrected > > modelForwardCurves_
 
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrectedmodelDiscountCurve_
 
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrectedmodelSwapIndexForwardCurve_
 
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrectedmodelSwapIndexDiscountCurve_
 
QuantLib::ext::shared_ptr< SwapIndex > swapIndexBaseFinal_
 
QuantLib::ext::shared_ptr< SwapIndex > modelSwapIndexBase_
 

Detailed Description

Given an exotic underlying find a standard swap matching the underlying using the representative swaption method in a LGM model.

The swaption that is returned does not have a pricing engine attached, the underlying swap has a discounting swap engine attached (using the given discount curve) though and the ibor index of the underlying swap is using the forwarding curve from the given swap index.

The LGM model uses to find the representative swaption

For the methodology, see Andersen, Piterbarg, Interest Rate Modelling, ch. 19.4.

The underlying may only contain simple cahsflows, fixed coupons and standard ibor coupons (i.e. without cap/floor or in arrears fixings).

Definition at line 54 of file representativeswaption.hpp.

Member Enumeration Documentation

◆ InclusionCriterion

enum class InclusionCriterion
strong

Constructor & Destructor Documentation

◆ RepresentativeSwaptionMatcher()

RepresentativeSwaptionMatcher ( const std::vector< Leg > &  underlying,
const std::vector< bool > &  isPayer,
const QuantLib::ext::shared_ptr< SwapIndex > &  standardSwapIndexBase,
const bool  useUnderlyingIborIndex,
const Handle< YieldTermStructure > &  discountCurve,
const Real  reversion,
const Real  volatility = 0.0050,
const Real  flatRate = Null<Real>() 
)

Definition at line 41 of file representativeswaption.cpp.

45 : underlying_(underlying), isPayer_(isPayer), swapIndexBase_(swapIndexBase),
46 useUnderlyingIborIndex_(useUnderlyingIborIndex), discountCurve_(discountCurve), reversion_(reversion),
47 volatility_(volatility), flatRate_(flatRate) {
48
49 // set up flat curve, if we want that
50 Handle<YieldTermStructure> flatCurve;
51 if (flatRate != Null<Real>()) {
52 flatCurve =
53 Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), flatRate_, ActualActual(ActualActual::ISDA)));
54 }
55
56 // determine last cashflow date of underlying
57 Date maturityDate = Date::minDate();
58 for (auto const& l : underlying)
59 for (auto const& c : l)
60 maturityDate = std::max(c->date(), maturityDate);
61 QL_REQUIRE(maturityDate > discountCurve->referenceDate(), "underlying maturity ("
62 << maturityDate << ") must be gt reference date ("
63 << discountCurve_->referenceDate() << ")");
64
65 // set up model
66 model_ = QuantLib::ext::make_shared<LGM>(QuantLib::ext::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
67 swapIndexBase_->currency(), flatCurve.empty() ? discountCurve_ : flatCurve, Array(), Array(1, volatility_),
68 Array(), Array(1, reversion_)));
69
70 // build underlying leg with its ibor / ois coupons linked to model forward curves
71 QuantLib::ext::shared_ptr<IborIndex> modelIborIndexToUse, iborIndexToUse;
72 for (Size l = 0; l < underlying_.size(); ++l) {
73 for (auto const& c : underlying_[l]) {
74 if (auto i = QuantLib::ext::dynamic_pointer_cast<IborCoupon>(c)) {
75 // standard ibor coupon
76 QL_REQUIRE(!i->isInArrears(), "RepresentativeSwaptionMatcher: can not handle in arrears fixing");
77 QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> y;
78 auto fc = modelForwardCurves_.find(i->iborIndex()->name());
79 if (fc != modelForwardCurves_.end())
80 y = fc->second;
81 else {
82 y = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(
83 model_, flatCurve.empty() ? i->iborIndex()->forwardingTermStructure() : flatCurve);
84 modelForwardCurves_[i->iborIndex()->name()] = y;
85 }
86 auto iborIndexLinkedToModelCurve = i->iborIndex()->clone(Handle<YieldTermStructure>(y));
87 auto tmp = QuantLib::ext::make_shared<IborCoupon>(
88 i->date(), i->nominal(), i->accrualStartDate(), i->accrualEndDate(), i->fixingDays(),
89 iborIndexLinkedToModelCurve, i->gearing(), i->spread(), i->referencePeriodStart(),
90 i->referencePeriodEnd(), i->dayCounter(), false);
91 tmp->setPricer(QuantLib::ext::make_shared<BlackIborCouponPricer>());
92 modelLinkedUnderlying_.push_back(tmp);
93 if (modelIborIndexToUse == nullptr) {
94 modelIborIndexToUse = iborIndexLinkedToModelCurve;
95 iborIndexToUse = i->iborIndex();
96 }
97 } else if (auto o = QuantLib::ext::dynamic_pointer_cast<QuantExt::OvernightIndexedCoupon>(c)) {
98 auto onIndex = o->overnightIndex();
99 QL_REQUIRE(onIndex, "internal error: could not cast o->index() to overnightIndex");
100 QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> y;
101 auto fc = modelForwardCurves_.find(onIndex->name());
102 if (fc != modelForwardCurves_.end())
103 y = fc->second;
104 else {
105 y = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(
106 model_, flatCurve.empty() ? onIndex->forwardingTermStructure() : flatCurve);
107 modelForwardCurves_[onIndex->name()] = y;
108 }
109 auto onIndexLinkedToModelCurve =
110 QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(onIndex->clone(Handle<YieldTermStructure>(y)));
111 QL_REQUIRE(onIndexLinkedToModelCurve,
112 "internal error: could not cast onIndex->clone() to OvernightIndex");
113 auto tmp = QuantLib::ext::make_shared<QuantExt::OvernightIndexedCoupon>(
114 o->date(), o->nominal(), o->accrualStartDate(), o->accrualEndDate(), onIndexLinkedToModelCurve,
115 o->gearing(), o->spread(), o->referencePeriodStart(), o->referencePeriodEnd(), o->dayCounter(),
116 false, o->includeSpread(), o->lookback(), o->rateCutoff(), o->fixingDays(),
117 o->rateComputationStartDate(), o->rateComputationEndDate());
118 tmp->setPricer(QuantLib::ext::make_shared<OvernightIndexedCouponPricer>());
119 modelLinkedUnderlying_.push_back(tmp);
120 if (modelIborIndexToUse == nullptr) {
121 modelIborIndexToUse = onIndexLinkedToModelCurve;
122 iborIndexToUse = o->overnightIndex();
123 }
124 } else if (auto o = QuantLib::ext::dynamic_pointer_cast<QuantExt::AverageONIndexedCoupon>(c)) {
125 auto onIndex = o->overnightIndex();
126 QL_REQUIRE(onIndex, "internal error: could not cast o->index() to overnightIndex");
127 QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> y;
128 auto fc = modelForwardCurves_.find(onIndex->name());
129 if (fc != modelForwardCurves_.end())
130 y = fc->second;
131 else {
132 y = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(
133 model_, flatCurve.empty() ? onIndex->forwardingTermStructure() : flatCurve);
134 modelForwardCurves_[onIndex->name()] = y;
135 }
136 auto onIndexLinkedToModelCurve =
137 QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(onIndex->clone(Handle<YieldTermStructure>(y)));
138 QL_REQUIRE(onIndexLinkedToModelCurve,
139 "internal error: could not cast onIndex->clone() to OvernightIndex");
140 auto tmp = QuantLib::ext::make_shared<QuantExt::AverageONIndexedCoupon>(
141 o->date(), o->nominal(), o->accrualStartDate(), o->accrualEndDate(), onIndexLinkedToModelCurve,
142 o->gearing(), o->spread(), o->rateCutoff(), o->dayCounter(), o->lookback(), o->fixingDays(),
143 o->rateComputationStartDate(), o->rateComputationEndDate());
144 tmp->setPricer(QuantLib::ext::make_shared<AverageONIndexedCouponPricer>());
145 modelLinkedUnderlying_.push_back(tmp);
146 if (modelIborIndexToUse == nullptr) {
147 modelIborIndexToUse = onIndexLinkedToModelCurve;
148 iborIndexToUse = o->overnightIndex();
149 }
150 } else if (QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(c) ||
151 QuantLib::ext::dynamic_pointer_cast<SimpleCashFlow>(c)) {
152 // fixed coupon or simple cashflow
153 modelLinkedUnderlying_.push_back(c);
154 } else {
155 QL_FAIL("RepresentativeSwaptionMatcher: unsupported coupon type");
156 }
157 modelLinkedUnderlyingIsPayer_.push_back(isPayer[l]);
158 }
159 }
160
161 // build model linked discounting curve
163 QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(model_, flatCurve.empty() ? discountCurve_ : flatCurve);
164
165 // identify the ibor index to use for the matching
166 if (modelIborIndexToUse == nullptr || !useUnderlyingIborIndex_) {
167 auto fc = modelForwardCurves_.find(swapIndexBase_->iborIndex()->name());
168 if (fc != modelForwardCurves_.end())
169 modelIborIndexToUse = swapIndexBase_->iborIndex()->clone(Handle<YieldTermStructure>(fc->second));
170 else
171 modelIborIndexToUse = swapIndexBase_->iborIndex()->clone(
172 Handle<YieldTermStructure>(QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(
173 model_, flatCurve.empty() ? swapIndexBase_->iborIndex()->forwardingTermStructure() : flatCurve)));
174 iborIndexToUse = swapIndexBase_->iborIndex();
175 }
176
177 // build model linked swap index base
179 QuantLib::ext::dynamic_pointer_cast<LgmImpliedYtsFwdFwdCorrected>(*modelIborIndexToUse->forwardingTermStructure());
181 "internal error: could not cast modelIborIndexToUse->forwardingTermStructure() to "
182 "LgmImpliedYtsFwdFwdCorrected");
183 modelSwapIndexDiscountCurve_ = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(
184 model_, flatCurve.empty() ? (swapIndexBase_->discountingTermStructure().empty()
185 ? modelIborIndexToUse->forwardingTermStructure()
186 : swapIndexBase_->discountingTermStructure())
187 : flatCurve);
188
189 // create the final swap index base to use, i.e. the one with replaced ibor index, if desired
190 swapIndexBaseFinal_ = QuantLib::ext::make_shared<SwapIndex>(
191 swapIndexBase_->familyName(), swapIndexBase_->tenor(), swapIndexBase_->fixingDays(), swapIndexBase_->currency(),
192 swapIndexBase_->fixingCalendar(), swapIndexBase_->fixedLegTenor(), swapIndexBase_->fixedLegConvention(),
193 swapIndexBase_->dayCounter(), iborIndexToUse, Handle<YieldTermStructure>(modelSwapIndexDiscountCurve_));
194
195 // clone the swap index base using the model fwd and dsc curves and replacing the ibor tenor, if that applies
196 modelSwapIndexBase_ = QuantLib::ext::make_shared<SwapIndex>(
197 swapIndexBase_->familyName(), swapIndexBase_->tenor(), swapIndexBase_->fixingDays(), swapIndexBase_->currency(),
198 swapIndexBase_->fixingCalendar(), swapIndexBase_->fixedLegTenor(), swapIndexBase_->fixedLegConvention(),
199 swapIndexBase_->dayCounter(), modelIborIndexToUse, Handle<YieldTermStructure>(modelSwapIndexDiscountCurve_));
200}
QuantLib::ext::shared_ptr< SwapIndex > modelSwapIndexBase_
const Handle< YieldTermStructure > discountCurve_
QuantLib::ext::shared_ptr< SwapIndex > swapIndexBaseFinal_
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrected > modelSwapIndexDiscountCurve_
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrected > modelDiscountCurve_
QuantLib::ext::shared_ptr< LGM > model_
std::map< std::string, QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrected > > modelForwardCurves_
const QuantLib::ext::shared_ptr< SwapIndex > swapIndexBase_
QuantLib::ext::shared_ptr< LgmImpliedYtsFwdFwdCorrected > modelSwapIndexForwardCurve_

Member Function Documentation

◆ representativeSwaption()

QuantLib::ext::shared_ptr< Swaption > representativeSwaption ( Date  exerciseDate,
const InclusionCriterion  criterion = InclusionCriterion::AccrualStartGeqExercise 
)

find representative swaption for all specified underlying cashflows a null pointer is returned if there are no live cashflows found

Definition at line 217 of file representativeswaption.cpp.

218 {
219
220 QL_REQUIRE(exerciseDate > discountCurve_->referenceDate(),
221 "exerciseDate (" << exerciseDate << ") must be greater than reference date ("
222 << discountCurve_->referenceDate() << ")");
223
224 // shift size for derivative computation
225
226 constexpr static Real h = 1.0E-4;
227
228 // build leg containing all coupons with pay date > exerciseDate
229
230 Date today = discountCurve_->referenceDate();
231 Leg effectiveLeg;
232 Real additionalDeterministicNpv = 0.0;
233 std::vector<bool> effectiveIsPayer;
234 for (Size c = 0; c < modelLinkedUnderlying_.size(); ++c) {
235 QuantLib::ext::shared_ptr<CashFlow> cf = modelLinkedUnderlying_[c];
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 // an ibor coupon with today <= fixing date < exerciseDate is modified such that the fixing date is on
242 // (or due to holiday adjustments shortly after) the exercise date; the nominal is adjusted such that
243 // the effective accrual time remains the same
244 // if on the other hand the fixing date is in the past, the historic fixing is required
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);
257 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
258 } else {
259 // leave the ibor coupon as is
260 effectiveLeg.push_back(cf);
261 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
262 }
263 } else if (auto o = QuantLib::ext::dynamic_pointer_cast<OvernightIndexedCoupon>(cf)) {
264
265 if (o->fixingDates().empty())
266 continue;
267
268 // For an OIS coupon, we keep the original coupon, if the first fixing date >= exercise date
269
270 if (o->fixingDates().front() >= exerciseDate) {
271 o->setPricer(QuantLib::ext::make_shared<QuantExt::OvernightIndexedCouponPricer>());
272 effectiveLeg.push_back(o);
273 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
274 } else {
275
276 // For an OIS coupon with first exercise date <= exercise date, represent
277 // a) fixing dates < today via a fixed cashflow
278 // b) fixing dates >= today via a float cashflow
279 // For b) keep fixing dates >= exerciseDate only, but at least one fixing date and scale the result
280 // to the full rate period associated to b). Furthermore, the accrual period will be the same as
281 // the rate computation (value dates) period
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);
321 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
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);
332 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
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);
371 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
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);
377 effectiveIsPayer.push_back(modelLinkedUnderlyingIsPayer_[c]);
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 // adjust exercise date to a valid fixing date, otherwise MakeVanillaSwap below may fail
387 exerciseDate = swapIndexBase_->fixingCalendar().adjust(exerciseDate);
388
389 // compute exercise time (the dc of the discount curve defines the date => time mapping by convention)
390 Real t_ex = discountCurve_->timeFromReference(exerciseDate);
391
392 // use T = t_ex - forward measure for all calculations instead of original LGM measure
393 model_->parametrization()->shift() = -model_->parametrization()->H(t_ex);
394
395 // initial guess for strike = nominal weighted fixed rate of coupons
396 Real nominalSum = 0.0, nominalSumAbs = 0.0, strikeGuess = 0.0;
397 Size nCpns = 0;
398 for (Size c = 0; c < modelLinkedUnderlying_.size(); ++c) {
399 if (auto f = QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(modelLinkedUnderlying_[c])) {
400 strikeGuess += f->rate() * std::abs(f->nominal());
401 nominalSum += f->nominal() * (modelLinkedUnderlyingIsPayer_[c] ? -1.0 : 1.0);
402 nominalSumAbs += std::abs(f->nominal());
403 nCpns++;
404 }
405 }
406 Real nominalGuess = nominalSum / static_cast<Real>(nCpns);
407 if (close_enough(nominalSumAbs, 0.0))
408 strikeGuess = 0.01; // default guess
409 else
410 strikeGuess /= nominalSumAbs;
411
412 // initial guess for maturity = maturity of last cashflow
413 Real maturityGuess = ActualActual(ActualActual::ISDA).yearFraction(exerciseDate, CashFlows::maturityDate(modelLinkedUnderlying_));
414
415 // target function, input components are nominal, strike, maturity, output rel. error in npv, delta, gamma
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 // same as in SwapIndex::underlyingSwap() to make sure we are consistent
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 // bracket continuous maturity between two discrete tenors rounded to whole months
443 // this is essential to provide a smooth target function
444 Size months;
445 Real alpha;
446 std::tie(months, alpha) = periodFromTime(maturityTime);
447 RawResult rawResult;
448 // do we have a cached result?
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 // generate candidate underlying and compute npv for states 0,+h,-h with chosen stepsize
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 // compute npv of lower and upper underlying
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 // compute interpolated npv
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 // compute delta and gamma w.r.t. model state
492 Real delta = (vu - vd) / (2.0 * h);
493 Real gamma = (vu + vd) / (h * h);
494 // return target function
495 Array target{(v0 - npv_target) / delta_target, (delta - delta_target) / delta_target,
496 (gamma - gamma_target) / gamma_target};
497 return target;
498 }
499 // to be provided
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 // internal
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 // init target function
516 Matcher matcher;
517 matcher.h = h;
518 // limit max maturity time such that we are safe anyway
519 matcher.maxMaturityTime = discountCurve_->dayCounter().yearFraction(exerciseDate, Date::maxDate() - 365);
520 matcher.exerciseDate = exerciseDate;
521 matcher.modelSwapIndexBase = modelSwapIndexBase_;
522 matcher.engine = QuantLib::ext::make_shared<DiscountingSwapEngine>(Handle<YieldTermStructure>(modelDiscountCurve_), false,
523 exerciseDate, exerciseDate);
524 for (auto const& c : modelForwardCurves_)
525 matcher.modelCurves.insert(c.second);
526 matcher.modelCurves.insert(modelDiscountCurve_);
527 matcher.modelCurves.insert(modelSwapIndexForwardCurve_);
528 matcher.modelCurves.insert(modelSwapIndexDiscountCurve_);
529
530 // set reference date in model curves
531 for (auto const& c : matcher.modelCurves)
532 c->referenceDate(exerciseDate);
533
534 // compute exotic underlying npv, delta, gamma and set as target
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 // set up optimizer and run it
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 // extract result and return it
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 // rescale notional to adjust for the difference between the calibrated maturity and the actual maturity we set
571 nominal *= x[2] * x[2] * 12.0 / static_cast<Real>(nMonths);
572 QuantLib::ext::shared_ptr<VanillaSwap> underlying =
573 MakeVanillaSwap(maturity, swapIndexBaseFinal_->iborIndex(), strike)
574 .withEffectiveDate(swapIndexBaseFinal_->valueDate(exerciseDate))
575 .withFixedLegCalendar(swapIndexBaseFinal_->fixingCalendar())
576 .withFixedLegDayCount(swapIndexBaseFinal_->dayCounter())
577 .withFixedLegTenor(swapIndexBaseFinal_->fixedLegTenor())
578 .withFixedLegConvention(swapIndexBaseFinal_->fixedLegConvention())
579 .withFixedLegTerminationDateConvention(swapIndexBaseFinal_->fixedLegConvention())
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)
Definition: inflation.cpp:183
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
+ Here is the call graph for this function:

◆ valueDate()

QuantLib::Date valueDate ( const QuantLib::Date &  fixingDate,
const QuantLib::ext::shared_ptr< QuantLib::FloatingRateCoupon > &  cpn 
) const
private

Definition at line 587 of file representativeswaption.cpp.

588 {
589 return cpn->index()->fixingCalendar().advance(fixingDate, cpn->fixingDays(), Days, Following);
590}
+ Here is the caller graph for this function:

Member Data Documentation

◆ underlying_

const std::vector<Leg> underlying_
private

Definition at line 72 of file representativeswaption.hpp.

◆ isPayer_

const std::vector<bool> isPayer_
private

Definition at line 73 of file representativeswaption.hpp.

◆ swapIndexBase_

const QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase_
private

Definition at line 74 of file representativeswaption.hpp.

◆ useUnderlyingIborIndex_

const bool useUnderlyingIborIndex_
private

Definition at line 75 of file representativeswaption.hpp.

◆ discountCurve_

const Handle<YieldTermStructure> discountCurve_
private

Definition at line 76 of file representativeswaption.hpp.

◆ reversion_

const Real reversion_
private

Definition at line 77 of file representativeswaption.hpp.

◆ volatility_

const Real volatility_
private

Definition at line 77 of file representativeswaption.hpp.

◆ flatRate_

const Real flatRate_
private

Definition at line 77 of file representativeswaption.hpp.

◆ model_

QuantLib::ext::shared_ptr<LGM> model_
private

Definition at line 79 of file representativeswaption.hpp.

◆ modelLinkedUnderlying_

Leg modelLinkedUnderlying_
private

Definition at line 80 of file representativeswaption.hpp.

◆ modelLinkedUnderlyingIsPayer_

std::vector<bool> modelLinkedUnderlyingIsPayer_
private

Definition at line 81 of file representativeswaption.hpp.

◆ modelForwardCurves_

std::map<std::string, QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> > modelForwardCurves_
private

Definition at line 83 of file representativeswaption.hpp.

◆ modelDiscountCurve_

QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> modelDiscountCurve_
private

Definition at line 84 of file representativeswaption.hpp.

◆ modelSwapIndexForwardCurve_

QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> modelSwapIndexForwardCurve_
private

Definition at line 84 of file representativeswaption.hpp.

◆ modelSwapIndexDiscountCurve_

QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected> modelSwapIndexDiscountCurve_
private

Definition at line 85 of file representativeswaption.hpp.

◆ swapIndexBaseFinal_

QuantLib::ext::shared_ptr<SwapIndex> swapIndexBaseFinal_
private

Definition at line 86 of file representativeswaption.hpp.

◆ modelSwapIndexBase_

QuantLib::ext::shared_ptr<SwapIndex> modelSwapIndexBase_
private

Definition at line 86 of file representativeswaption.hpp.