1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2007 Giorgio Facchinetti
5 Copyright (C) 2007 Cristina Duminuco
6 Copyright (C) 2011 Ferdinando Ametrano
7 Copyright (C) 2015 Peter Caspers
8
9 This file is part of QuantLib, a free-software/open-source library
10 for financial quantitative analysts and developers - http://quantlib.org/
11
12 QuantLib is free software: you can redistribute it and/or modify it
13 under the terms of the QuantLib license. You should have received a
14 copy of the license along with this program; if not, please email
15 <quantlib-dev@lists.sf.net>. The license is also available online at
16 <http://quantlib.org/license.shtml>.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the license for more details.
21*/
22
23#include <ql/cashflows/capflooredcoupon.hpp>
24#include <ql/cashflows/couponpricer.hpp>
25#include <ql/cashflows/digitalcmscoupon.hpp>
26#include <ql/cashflows/digitalcoupon.hpp>
27#include <ql/cashflows/digitaliborcoupon.hpp>
28#include <ql/cashflows/rangeaccrual.hpp>
29#include <ql/cashflows/subperiodcoupon.hpp>
30#include <ql/experimental/coupons/cmsspreadcoupon.hpp> /* internal */
31#include <ql/experimental/coupons/digitalcmsspreadcoupon.hpp> /* internal */
32#include <ql/pricingengines/blackformula.hpp>
33#include <ql/termstructures/yieldtermstructure.hpp>
34#include <ql/optional.hpp>
35#include <utility>
36
37namespace QuantLib {
38
39//===========================================================================//
40// IborCouponPricer //
41//===========================================================================//
42
43 IborCouponPricer::IborCouponPricer(
44 Handle<OptionletVolatilityStructure> v,
45 ext::optional<bool> useIndexedCoupon)
46 : capletVol_(std::move(v)),
47 useIndexedCoupon_(useIndexedCoupon ?
48 *useIndexedCoupon :
49 !IborCoupon::Settings::instance().usingAtParCoupons()) {
50 registerWith(h: capletVol_);
51 }
52
53 void IborCouponPricer::initializeCachedData(const IborCoupon& coupon) const {
54
55 if(coupon.cachedDataIsInitialized_)
56 return;
57
58 coupon.fixingValueDate_ = coupon.iborIndex()->fixingCalendar().advance(
59 coupon.fixingDate_, n: coupon.iborIndex()->fixingDays(), unit: Days);
60 coupon.fixingMaturityDate_ = coupon.iborIndex()->maturityDate(valueDate: coupon.fixingValueDate_);
61
62 if (useIndexedCoupon_) {
63 coupon.fixingEndDate_ = coupon.fixingMaturityDate_;
64 } else {
65 if (coupon.isInArrears_)
66 coupon.fixingEndDate_ = coupon.fixingMaturityDate_;
67 else { // par coupon approximation
68 Date nextFixingDate = coupon.iborIndex()->fixingCalendar().advance(
69 coupon.accrualEndDate(), n: -static_cast<Integer>(coupon.fixingDays_), unit: Days);
70 coupon.fixingEndDate_ = coupon.iborIndex()->fixingCalendar().advance(
71 nextFixingDate, n: coupon.iborIndex()->fixingDays(), unit: Days);
72 // make sure the estimation period contains at least one day
73 coupon.fixingEndDate_ =
74 std::max(a: coupon.fixingEndDate_, b: coupon.fixingValueDate_ + 1);
75 }
76 }
77
78 coupon.spanningTime_ = coupon.iborIndex()->dayCounter().yearFraction(
79 d1: coupon.fixingValueDate_, d2: coupon.fixingEndDate_);
80
81 QL_REQUIRE(coupon.spanningTime_ > 0.0,
82 "\n cannot calculate forward rate between "
83 << coupon.fixingValueDate_ << " and " << coupon.fixingEndDate_
84 << ":\n non positive time (" << coupon.spanningTime_ << ") using "
85 << coupon.iborIndex()->dayCounter().name() << " daycounter");
86
87 coupon.spanningTimeIndexMaturity_ = coupon.iborIndex()->dayCounter().yearFraction(
88 d1: coupon.fixingValueDate_, d2: coupon.fixingMaturityDate_);
89
90 coupon.cachedDataIsInitialized_ = true;
91 }
92
93 void IborCouponPricer::initialize(const FloatingRateCoupon& coupon) {
94 coupon_ = dynamic_cast<const IborCoupon *>(&coupon);
95 QL_REQUIRE(coupon_, "IborCouponPricer: expected IborCoupon");
96
97 initializeCachedData(coupon: *coupon_);
98
99 index_ = coupon_->iborIndex();
100 gearing_ = coupon_->gearing();
101 spread_ = coupon_->spread();
102 accrualPeriod_ = coupon_->accrualPeriod();
103 QL_REQUIRE(accrualPeriod_ != 0.0, "null accrual period");
104
105 fixingDate_ = coupon_->fixingDate_;
106 fixingValueDate_ = coupon_->fixingValueDate_;
107 fixingMaturityDate_ = coupon_->fixingMaturityDate_;
108 spanningTime_ = coupon_->spanningTime_;
109 spanningTimeIndexMaturity_ = coupon_->spanningTimeIndexMaturity_;
110 }
111
112
113//===========================================================================//
114// BlackIborCouponPricer //
115//===========================================================================//
116
117 void BlackIborCouponPricer::initialize(const FloatingRateCoupon& coupon) {
118
119 IborCouponPricer::initialize(coupon);
120
121 const Handle<YieldTermStructure>& rateCurve = index_->forwardingTermStructure();
122
123 if (rateCurve.empty()) {
124 discount_ = Null<Real>(); // might not be needed, will be checked later
125 } else {
126 Date paymentDate = coupon_->date();
127 if (paymentDate > rateCurve->referenceDate())
128 discount_ = rateCurve->discount(d: paymentDate);
129 else
130 discount_ = 1.0;
131 }
132
133 }
134
135 Real BlackIborCouponPricer::optionletRate(Option::Type optionType, Real effStrike) const {
136 if (fixingDate_ <= Settings::instance().evaluationDate()) {
137 // the amount is determined
138 Real a, b;
139 if (optionType==Option::Call) {
140 a = coupon_->indexFixing();
141 b = effStrike;
142 } else {
143 a = effStrike;
144 b = coupon_->indexFixing();
145 }
146 return std::max(a: a - b, b: 0.0);
147 } else {
148 // not yet determined, use Black model
149 QL_REQUIRE(!capletVolatility().empty(),
150 "missing optionlet volatility");
151 Real stdDev =
152 std::sqrt(x: capletVolatility()->blackVariance(optionDate: fixingDate_,
153 strike: effStrike));
154 Real shift = capletVolatility()->displacement();
155 bool shiftedLn =
156 capletVolatility()->volatilityType() == ShiftedLognormal;
157 Rate fixing =
158 shiftedLn
159 ? blackFormula(optionType, strike: effStrike, forward: adjustedFixing(),
160 stdDev, discount: 1.0, displacement: shift)
161 : bachelierBlackFormula(optionType, strike: effStrike,
162 forward: adjustedFixing(), stdDev, discount: 1.0);
163 return fixing;
164 }
165 }
166
167 Real BlackIborCouponPricer::optionletPrice(Option::Type optionType,
168 Real effStrike) const {
169 QL_REQUIRE(discount_ != Null<Rate>(), "no forecast curve provided");
170 return optionletRate(optionType, effStrike) * accrualPeriod_ * discount_;
171 }
172
173 Rate BlackIborCouponPricer::adjustedFixing(Rate fixing) const {
174
175 if (fixing == Null<Rate>())
176 fixing = coupon_->indexFixing();
177
178 // if the pay date is equal to the index estimation end date
179 // there is no convexity; in all other cases in principle an
180 // adjustment has to be applied, but the Black76 method only
181 // applies the standard in arrears adjustment; the bivariate
182 // lognormal method is more accurate in this regard.
183 if ((!coupon_->isInArrears() && timingAdjustment_ == Black76))
184 return fixing;
185 const Date& d1 = fixingDate_;
186 const Date& d2 = fixingValueDate_;
187 const Date& d3 = fixingMaturityDate_;
188 if (coupon_->date() == d3)
189 return fixing;
190
191 QL_REQUIRE(!capletVolatility().empty(),
192 "missing optionlet volatility");
193 Date referenceDate = capletVolatility()->referenceDate();
194 // no variance has accumulated, so the convexity is zero
195 if (d1 <= referenceDate)
196 return fixing;
197 const Time& tau = spanningTimeIndexMaturity_;
198 Real variance = capletVolatility()->blackVariance(optionDate: d1, strike: fixing);
199
200 Real shift = capletVolatility()->displacement();
201 bool shiftedLn =
202 capletVolatility()->volatilityType() == ShiftedLognormal;
203
204 Spread adjustment = shiftedLn
205 ? Real((fixing + shift) * (fixing + shift) *
206 variance * tau / (1.0 + fixing * tau))
207 : Real(variance * tau / (1.0 + fixing * tau));
208
209 if (timingAdjustment_ == BivariateLognormal) {
210 QL_REQUIRE(!correlation_.empty(), "no correlation given");
211 const Date& d4 = coupon_->date();
212 const Date& d5 = d4 >= d3 ? d3 : d2;
213 Time tau2 = index_->dayCounter().yearFraction(d1: d5, d2: d4);
214 if (d4 >= d3)
215 adjustment = 0.0;
216 // if d4 < d2 (payment before index start) we just apply the
217 // Black76 in arrears adjustment
218 if (tau2 > 0.0) {
219 Real fixing2 =
220 (index_->forwardingTermStructure()->discount(d: d5) /
221 index_->forwardingTermStructure()->discount(d: d4) -
222 1.0) /
223 tau2;
224 adjustment -= shiftedLn
225 ? Real(correlation_->value() * tau2 * variance *
226 (fixing + shift) * (fixing2 + shift) /
227 (1.0 + fixing2 * tau2))
228 : Real(correlation_->value() * tau2 * variance /
229 (1.0 + fixing2 * tau2));
230 }
231 }
232 return fixing + adjustment;
233 }
234
235//===========================================================================//
236// CouponSelectorToSetPricer //
237//===========================================================================//
238
239 namespace {
240
241 class PricerSetter : public AcyclicVisitor,
242 public Visitor<CashFlow>,
243 public Visitor<Coupon>,
244 public Visitor<FloatingRateCoupon>,
245 public Visitor<CappedFlooredCoupon>,
246 public Visitor<IborCoupon>,
247 public Visitor<CmsCoupon>,
248 public Visitor<CmsSpreadCoupon>,
249 public Visitor<CappedFlooredIborCoupon>,
250 public Visitor<CappedFlooredCmsCoupon>,
251 public Visitor<CappedFlooredCmsSpreadCoupon>,
252 public Visitor<DigitalIborCoupon>,
253 public Visitor<DigitalCmsCoupon>,
254 public Visitor<DigitalCmsSpreadCoupon>,
255 public Visitor<RangeAccrualFloatersCoupon>,
256 public Visitor<SubPeriodsCoupon> {
257 private:
258 ext::shared_ptr<FloatingRateCouponPricer> pricer_;
259 public:
260 explicit PricerSetter(ext::shared_ptr<FloatingRateCouponPricer> pricer)
261 : pricer_(std::move(pricer)) {}
262
263 void visit(CashFlow& c) override;
264 void visit(Coupon& c) override;
265 void visit(FloatingRateCoupon& c) override;
266 void visit(CappedFlooredCoupon& c) override;
267 void visit(IborCoupon& c) override;
268 void visit(CappedFlooredIborCoupon& c) override;
269 void visit(DigitalIborCoupon& c) override;
270 void visit(CmsCoupon& c) override;
271 void visit(CmsSpreadCoupon& c) override;
272 void visit(CappedFlooredCmsCoupon& c) override;
273 void visit(CappedFlooredCmsSpreadCoupon& c) override;
274 void visit(DigitalCmsCoupon& c) override;
275 void visit(DigitalCmsSpreadCoupon& c) override;
276 void visit(RangeAccrualFloatersCoupon& c) override;
277 void visit(SubPeriodsCoupon& c) override;
278 };
279
280 void PricerSetter::visit(CashFlow&) {
281 // nothing to do
282 }
283
284 void PricerSetter::visit(Coupon&) {
285 // nothing to do
286 }
287
288 void PricerSetter::visit(FloatingRateCoupon& c) {
289 c.setPricer(pricer_);
290 }
291
292 void PricerSetter::visit(CappedFlooredCoupon& c) {
293 // we might end up here because a CappedFlooredCoupon
294 // was directly constructed; we should then check
295 // the underlying for consistency with the pricer
296 if (ext::dynamic_pointer_cast<IborCoupon>(r: c.underlying()) != nullptr) {
297 QL_REQUIRE(ext::dynamic_pointer_cast<IborCouponPricer>(pricer_),
298 "pricer not compatible with Ibor Coupon");
299 } else if (ext::dynamic_pointer_cast<CmsCoupon>(r: c.underlying()) != nullptr) {
300 QL_REQUIRE(ext::dynamic_pointer_cast<CmsCouponPricer>(pricer_),
301 "pricer not compatible with CMS Coupon");
302 } else if (ext::dynamic_pointer_cast<CmsSpreadCoupon>(r: c.underlying()) != nullptr) {
303 QL_REQUIRE(ext::dynamic_pointer_cast<CmsSpreadCouponPricer>(pricer_),
304 "pricer not compatible with CMS spread Coupon");
305 }
306 c.setPricer(pricer_);
307 }
308
309 void PricerSetter::visit(IborCoupon& c) {
310 const ext::shared_ptr<IborCouponPricer> iborCouponPricer =
311 ext::dynamic_pointer_cast<IborCouponPricer>(r: pricer_);
312 QL_REQUIRE(iborCouponPricer,
313 "pricer not compatible with Ibor coupon");
314 c.setPricer(iborCouponPricer);
315 }
316
317 void PricerSetter::visit(DigitalIborCoupon& c) {
318 const ext::shared_ptr<IborCouponPricer> iborCouponPricer =
319 ext::dynamic_pointer_cast<IborCouponPricer>(r: pricer_);
320 QL_REQUIRE(iborCouponPricer,
321 "pricer not compatible with Ibor coupon");
322 c.setPricer(iborCouponPricer);
323 }
324
325 void PricerSetter::visit(CappedFlooredIborCoupon& c) {
326 const ext::shared_ptr<IborCouponPricer> iborCouponPricer =
327 ext::dynamic_pointer_cast<IborCouponPricer>(r: pricer_);
328 QL_REQUIRE(iborCouponPricer,
329 "pricer not compatible with Ibor coupon");
330 c.setPricer(iborCouponPricer);
331 }
332
333 void PricerSetter::visit(CmsCoupon& c) {
334 const ext::shared_ptr<CmsCouponPricer> cmsCouponPricer =
335 ext::dynamic_pointer_cast<CmsCouponPricer>(r: pricer_);
336 QL_REQUIRE(cmsCouponPricer,
337 "pricer not compatible with CMS coupon");
338 c.setPricer(cmsCouponPricer);
339 }
340
341 void PricerSetter::visit(CmsSpreadCoupon& c) {
342 const ext::shared_ptr<CmsSpreadCouponPricer> cmsSpreadCouponPricer =
343 ext::dynamic_pointer_cast<CmsSpreadCouponPricer>(r: pricer_);
344 QL_REQUIRE(cmsSpreadCouponPricer,
345 "pricer not compatible with CMS spread coupon");
346 c.setPricer(cmsSpreadCouponPricer);
347 }
348
349 void PricerSetter::visit(CappedFlooredCmsCoupon& c) {
350 const ext::shared_ptr<CmsCouponPricer> cmsCouponPricer =
351 ext::dynamic_pointer_cast<CmsCouponPricer>(r: pricer_);
352 QL_REQUIRE(cmsCouponPricer,
353 "pricer not compatible with CMS coupon");
354 c.setPricer(cmsCouponPricer);
355 }
356
357 void PricerSetter::visit(CappedFlooredCmsSpreadCoupon& c) {
358 const ext::shared_ptr<CmsSpreadCouponPricer> cmsSpreadCouponPricer =
359 ext::dynamic_pointer_cast<CmsSpreadCouponPricer>(r: pricer_);
360 QL_REQUIRE(cmsSpreadCouponPricer,
361 "pricer not compatible with CMS spread coupon");
362 c.setPricer(cmsSpreadCouponPricer);
363 }
364
365 void PricerSetter::visit(DigitalCmsCoupon& c) {
366 const ext::shared_ptr<CmsCouponPricer> cmsCouponPricer =
367 ext::dynamic_pointer_cast<CmsCouponPricer>(r: pricer_);
368 QL_REQUIRE(cmsCouponPricer,
369 "pricer not compatible with CMS coupon");
370 c.setPricer(cmsCouponPricer);
371 }
372
373 void PricerSetter::visit(DigitalCmsSpreadCoupon& c) {
374 const ext::shared_ptr<CmsSpreadCouponPricer> cmsSpreadCouponPricer =
375 ext::dynamic_pointer_cast<CmsSpreadCouponPricer>(r: pricer_);
376 QL_REQUIRE(cmsSpreadCouponPricer,
377 "pricer not compatible with CMS spread coupon");
378 c.setPricer(cmsSpreadCouponPricer);
379 }
380
381 void PricerSetter::visit(RangeAccrualFloatersCoupon& c) {
382 const ext::shared_ptr<RangeAccrualPricer> rangeAccrualPricer =
383 ext::dynamic_pointer_cast<RangeAccrualPricer>(r: pricer_);
384 QL_REQUIRE(rangeAccrualPricer,
385 "pricer not compatible with range-accrual coupon");
386 c.setPricer(rangeAccrualPricer);
387 }
388
389 void PricerSetter::visit(SubPeriodsCoupon& c) {
390 const ext::shared_ptr<SubPeriodsPricer> subPeriodsPricer =
391 ext::dynamic_pointer_cast<SubPeriodsPricer>(r: pricer_);
392 QL_REQUIRE(subPeriodsPricer,
393 "pricer not compatible with sub-period coupon");
394 c.setPricer(subPeriodsPricer);
395 }
396
397 void setCouponPricersFirstMatching(const Leg& leg,
398 const std::vector<ext::shared_ptr<FloatingRateCouponPricer> >& p) {
399 std::vector<PricerSetter> setter;
400 setter.reserve(n: p.size());
401 for (const auto& i : p) {
402 setter.emplace_back(args: i);
403 }
404 for (const auto& i : leg) {
405 Size j = 0;
406 do {
407 try {
408 i->accept(setter[j]);
409 j = p.size();
410 } catch (...) {
411 ++j;
412 }
413 } while (j < p.size());
414 }
415 }
416
417 } // anonymous namespace
418
419 void setCouponPricer(const Leg& leg, const ext::shared_ptr<FloatingRateCouponPricer>& pricer) {
420 PricerSetter setter(pricer);
421 for (const auto& i : leg) {
422 i->accept(setter);
423 }
424 }
425
426 void setCouponPricers(
427 const Leg& leg,
428 const std::vector<ext::shared_ptr<FloatingRateCouponPricer> >&
429 pricers) {
430 Size nCashFlows = leg.size();
431 QL_REQUIRE(nCashFlows>0, "no cashflows");
432
433 Size nPricers = pricers.size();
434 QL_REQUIRE(nCashFlows >= nPricers,
435 "mismatch between leg size (" << nCashFlows <<
436 ") and number of pricers (" << nPricers << ")");
437
438 for (Size i=0; i<nCashFlows; ++i) {
439 PricerSetter setter(i<nPricers ? pricers[i] : pricers[nPricers-1]);
440 leg[i]->accept(setter);
441 }
442 }
443
444 void setCouponPricers(
445 const Leg& leg,
446 const ext::shared_ptr<FloatingRateCouponPricer>& p1,
447 const ext::shared_ptr<FloatingRateCouponPricer>& p2) {
448 std::vector<ext::shared_ptr<FloatingRateCouponPricer> > p;
449 p.push_back(x: p1);
450 p.push_back(x: p2);
451 setCouponPricersFirstMatching(leg, p);
452 }
453
454 void setCouponPricers(
455 const Leg& leg,
456 const ext::shared_ptr<FloatingRateCouponPricer>& p1,
457 const ext::shared_ptr<FloatingRateCouponPricer>& p2,
458 const ext::shared_ptr<FloatingRateCouponPricer>& p3) {
459 std::vector<ext::shared_ptr<FloatingRateCouponPricer> > p;
460 p.push_back(x: p1);
461 p.push_back(x: p2);
462 p.push_back(x: p3);
463 setCouponPricersFirstMatching(leg, p);
464 }
465
466 void setCouponPricers(
467 const Leg& leg,
468 const ext::shared_ptr<FloatingRateCouponPricer>& p1,
469 const ext::shared_ptr<FloatingRateCouponPricer>& p2,
470 const ext::shared_ptr<FloatingRateCouponPricer>& p3,
471 const ext::shared_ptr<FloatingRateCouponPricer>& p4) {
472 std::vector<ext::shared_ptr<FloatingRateCouponPricer> > p;
473 p.push_back(x: p1);
474 p.push_back(x: p2);
475 p.push_back(x: p3);
476 p.push_back(x: p4);
477 setCouponPricersFirstMatching(leg, p);
478 }
479
480
481}
482

source code of quantlib/ql/cashflows/couponpricer.cpp