Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
lognormalcmsspreadpricer.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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
19/*
20 Copyright (C) 2014, 2015, 2018 Peter Caspers
21
22 This file is part of QuantLib, a free-software/open-source library
23 for financial quantitative analysts and developers - http://quantlib.org/
24
25 QuantLib is free software: you can redistribute it and/or modify it
26 under the terms of the QuantLib license. You should have received a
27 copy of the license along with this program; if not, please email
28 <quantlib-dev@lists.sf.net>. The license is also available online at
29 <http://quantlib.org/license.shtml>.
30
31
32 This program is distributed in the hope that it will be useful, but
33 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
34 or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
35*/
36
38
39#include <ql/experimental/coupons/cmsspreadcoupon.hpp>
40#include <ql/math/integrals/kronrodintegral.hpp>
41#include <ql/pricingengines/blackformula.hpp>
42#include <ql/termstructures/volatility/swaption/swaptionvolcube.hpp>
43
44#include <boost/make_shared.hpp>
45
46using std::sqrt;
47
48using namespace QuantLib;
49
50namespace QuantExt {
51
52class LognormalCmsSpreadPricer::integrand_f {
53 const LognormalCmsSpreadPricer* pricer;
54
55public:
56 explicit integrand_f(const LognormalCmsSpreadPricer* pricer) : pricer(pricer) {}
57 Real operator()(Real x) const { return pricer->integrand(x); }
58};
59
60LognormalCmsSpreadPricer::LognormalCmsSpreadPricer(const QuantLib::ext::shared_ptr<CmsCouponPricer> cmsPricer,
61 const Handle<QuantExt::CorrelationTermStructure>& correlation,
62 const Handle<YieldTermStructure>& couponDiscountCurve,
63 const Size integrationPoints,
64 const boost::optional<VolatilityType> volatilityType,
65 const Real shift1, const Real shift2)
66 : CmsSpreadCouponPricer2(correlation), cmsPricer_(cmsPricer), couponDiscountCurve_(couponDiscountCurve) {
67
68 if (!couponDiscountCurve_.empty())
69 registerWith(couponDiscountCurve_);
70 registerWith(cmsPricer_);
71
72 QL_REQUIRE(integrationPoints >= 4, "at least 4 integration points should be used (" << integrationPoints << ")");
73 integrator_ = QuantLib::ext::make_shared<GaussHermiteIntegration>(integrationPoints);
74
75 cnd_ = QuantLib::ext::make_shared<CumulativeNormalDistribution>(0.0, 1.0);
76
77 if (volatilityType == boost::none) {
78 QL_REQUIRE(shift1 == Null<Real>() && shift2 == Null<Real>(),
79 "if volatility type is inherited, no shifts should be "
80 "specified");
82 volType_ = cmsPricer->swaptionVolatility()->volatilityType();
83 } else {
84 shift1_ = shift1 == Null<Real>() ? 0.0 : shift1;
85 shift2_ = shift2 == Null<Real>() ? 0.0 : shift2;
87 volType_ = *volatilityType;
88 }
89}
90
91Real LognormalCmsSpreadPricer::integrand(const Real x) const {
92
93 // this is Brigo, 13.16.2 with x = v / sqrt(2)
94
95 Real v = M_SQRT2 * x;
96 Real h = k_ - b_ * s2_ * std::exp((m2_ - 0.5 * v2_ * v2_) * fixingTime_ + v2_ * std::sqrt(fixingTime_) * v);
97 Real phi1, phi2;
98 phi1 = (*cnd_)(phi_ *
99 (std::log(a_ * s1_ / h) + (m1_ + (0.5 - rho() * rho()) * v1_ * v1_) * fixingTime_ +
100 rho() * v1_ * std::sqrt(fixingTime_) * v) /
101 (v1_ * std::sqrt(fixingTime_ * (1.0 - rho() * rho()))));
102 phi2 = (*cnd_)(
103 phi_ *
104 (std::log(a_ * s1_ / h) + (m1_ - 0.5 * v1_ * v1_) * fixingTime_ + rho() * v1_ * std::sqrt(fixingTime_) * v) /
105 (v1_ * std::sqrt(fixingTime_ * (1.0 - rho() * rho()))));
106 Real f = a_ * phi_ * s1_ *
107 std::exp(m1_ * fixingTime_ - 0.5 * rho() * rho() * v1_ * v1_ * fixingTime_ +
108 rho() * v1_ * std::sqrt(fixingTime_) * v) *
109 phi1 -
110 phi_ * h * phi2;
111 return std::exp(-x * x) * f;
112}
113
115
116 // this is http://ssrn.com/abstract=2686998, 3.20 with x = s / sqrt(2)
117
118 Real s = M_SQRT2 * x;
119
120 Real beta = phi_ * (gearing1_ * adjustedRate1_ + gearing2_ * adjustedRate2_ - k_ +
121 std::sqrt(fixingTime_) * (rho() * gearing1_ * vol1_ + gearing2_ * vol2_) * s);
122 Real f = close_enough(alpha_, 0.0)
123 ? std::max(beta, 0.0)
124 : psi_ * alpha_ / (M_SQRTPI * M_SQRT2) * std::exp(-beta * beta / (2.0 * alpha_ * alpha_)) +
125 beta * (1.0 - (*cnd_)(-psi_ * beta / alpha_));
126 return std::exp(-x * x) * f;
127}
128
130
131 coupon_ = dynamic_cast<const CmsSpreadCoupon*>(&coupon);
132 QL_REQUIRE(coupon_, "CMS spread coupon needed");
133 index_ = coupon_->swapSpreadIndex();
134 gearing_ = coupon_->gearing();
135 spread_ = coupon_->spread();
136
137 fixingDate_ = coupon_->fixingDate();
138 paymentDate_ = coupon_->date();
139
140 // if no coupon discount curve is given just use the discounting curve
141 // from the _first_ swap index.
142 // for rate calculation this curve cancels out in the computation, so
143 // e.g. the discounting
144 // swap engine will produce correct results, even if the
145 // couponDiscountCurve is not set here.
146 // only the price member function in this class will be dependent on the
147 // coupon discount curve.
148
149 today_ = QuantLib::Settings::instance().evaluationDate();
150
151 if (couponDiscountCurve_.empty())
152 couponDiscountCurve_ = index_->swapIndex1()->exogenousDiscount()
153 ? index_->swapIndex1()->discountingTermStructure()
154 : index_->swapIndex1()->forwardingTermStructure();
155
156 discount_ =
157 paymentDate_ > couponDiscountCurve_->referenceDate() ? couponDiscountCurve_->discount(paymentDate_) : 1.0;
158
159 spreadLegValue_ = spread_ * coupon_->accrualPeriod() * discount_;
160
161 gearing1_ = index_->gearing1();
162 gearing2_ = index_->gearing2();
163
164 QL_REQUIRE(gearing1_ > 0.0 && gearing2_ < 0.0, "gearing1 (" << gearing1_ << ") should be positive while gearing2 ("
165 << gearing2_ << ") should be negative");
166
167 c1_ = QuantLib::ext::shared_ptr<CmsCoupon>(
168 new CmsCoupon(coupon_->date(), coupon_->nominal(), coupon_->accrualStartDate(), coupon_->accrualEndDate(),
169 coupon_->fixingDays(), index_->swapIndex1(), 1.0, 0.0, coupon_->referencePeriodStart(),
170 coupon_->referencePeriodEnd(), coupon_->dayCounter(), coupon_->isInArrears()));
171
172 c2_ = QuantLib::ext::shared_ptr<CmsCoupon>(
173 new CmsCoupon(coupon_->date(), coupon_->nominal(), coupon_->accrualStartDate(), coupon_->accrualEndDate(),
174 coupon_->fixingDays(), index_->swapIndex2(), 1.0, 0.0, coupon_->referencePeriodStart(),
175 coupon_->referencePeriodEnd(), coupon_->dayCounter(), coupon_->isInArrears()));
176
177 c1_->setPricer(cmsPricer_);
178 c2_->setPricer(cmsPricer_);
179
180 if (fixingDate_ > today_) {
181
182 fixingTime_ = cmsPricer_->swaptionVolatility()->timeFromReference(fixingDate_);
183
184 swapRate1_ = c1_->indexFixing();
185 swapRate2_ = c2_->indexFixing();
186
187 adjustedRate1_ = c1_->adjustedFixing();
188 adjustedRate2_ = c2_->adjustedFixing();
189
190 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> swvol = *cmsPricer_->swaptionVolatility();
191 QuantLib::ext::shared_ptr<SwaptionVolatilityCube> swcub = QuantLib::ext::dynamic_pointer_cast<SwaptionVolatilityCube>(swvol);
192
193 if (inheritedVolatilityType_ && volType_ == ShiftedLognormal) {
194 shift1_ = swvol->shift(fixingDate_, index_->swapIndex1()->tenor());
195 shift2_ = swvol->shift(fixingDate_, index_->swapIndex2()->tenor());
196 }
197
198 if (swcub == NULL) {
199 // not a cube, just an atm surface given, so we can
200 // not easily convert volatilities and just forbid it
201 QL_REQUIRE(inheritedVolatilityType_, "if only an atm surface is given, the volatility "
202 "type must be inherited");
203 vol1_ = swvol->volatility(fixingDate_, index_->swapIndex1()->tenor(), swapRate1_);
204 vol2_ = swvol->volatility(fixingDate_, index_->swapIndex2()->tenor(), swapRate2_);
205 } else {
206 vol1_ = swcub->smileSection(fixingDate_, index_->swapIndex1()->tenor())
207 ->volatility(swapRate1_, volType_, shift1_);
208 vol2_ = swcub->smileSection(fixingDate_, index_->swapIndex2()->tenor())
209 ->volatility(swapRate2_, volType_, shift2_);
210 }
211
212 if (volType_ == ShiftedLognormal) {
213 mu1_ = 1.0 / fixingTime_ * std::log((adjustedRate1_ + shift1_) / (swapRate1_ + shift1_));
214 mu2_ = 1.0 / fixingTime_ * std::log((adjustedRate2_ + shift2_) / (swapRate2_ + shift2_));
215 }
216 // for the normal volatility case we do not need the drifts
217 // but rather use adjusted rates directly in the integrand
218
219 } else {
220 // fixing is in the past or today
221 adjustedRate1_ = c1_->indexFixing();
222 adjustedRate2_ = c2_->indexFixing();
223 }
224}
225
226Real LognormalCmsSpreadPricer::optionletPrice(Option::Type optionType, Real strike) const {
227 // this method is only called for future fixings
228 optionType_ = optionType;
229 phi_ = optionType == Option::Call ? 1.0 : -1.0;
230 Real res = 0.0;
231 if (volType_ == ShiftedLognormal) {
232 // (shifted) lognormal volatility
233 if (strike >= 0.0) {
234 a_ = gearing1_;
235 b_ = gearing2_;
238 m1_ = mu1_;
239 m2_ = mu2_;
240 v1_ = vol1_;
241 v2_ = vol2_;
242 k_ = strike + gearing1_ * shift1_ + gearing2_ * shift2_;
243 } else {
244 a_ = -gearing2_;
245 b_ = -gearing1_;
248 m1_ = mu2_;
249 m2_ = mu1_;
250 v1_ = vol2_;
251 v2_ = vol1_;
252 k_ = -strike - gearing1_ * shift1_ - gearing2_ * shift2_;
253 res += phi_ * (gearing1_ * adjustedRate1_ + gearing2_ * adjustedRate2_ - strike);
254 }
255 res += 1.0 / M_SQRTPI * (*integrator_)(integrand_f(this));
256 } else {
257 // normal volatility
259 Real stddev =
261 2.0 * gearing1_ * gearing2_ * rho() * vol1_ * vol2_));
262 res = bachelierBlackFormula(optionType_, strike, forward, stddev, 1.0);
263 }
264 return res * discount_ * coupon_->accrualPeriod();
265}
266
267Rate LognormalCmsSpreadPricer::swapletRate() const { return swapletPrice() / (coupon_->accrualPeriod() * discount_); }
268
269Real LognormalCmsSpreadPricer::capletPrice(Rate effectiveCap) const {
270 // caplet is equivalent to call option on fixing
271 if (fixingDate_ <= today_) {
272 // the fixing is determined
273 const Rate Rs = std::max(coupon_->index()->fixing(fixingDate_) - effectiveCap, 0.);
274 Rate price = gearing_ * Rs * coupon_->accrualPeriod() * discount_;
275 return price;
276 } else {
277 Real capletPrice = optionletPrice(Option::Call, effectiveCap);
278 return gearing_ * capletPrice;
279 }
280}
281
282Rate LognormalCmsSpreadPricer::capletRate(Rate effectiveCap) const {
283 return capletPrice(effectiveCap) / (coupon_->accrualPeriod() * discount_);
284}
285
286Real LognormalCmsSpreadPricer::floorletPrice(Rate effectiveFloor) const {
287 // floorlet is equivalent to put option on fixing
288 if (fixingDate_ <= today_) {
289 // the fixing is determined
290 const Rate Rs = std::max(effectiveFloor - coupon_->index()->fixing(fixingDate_), 0.);
291 Rate price = gearing_ * Rs * coupon_->accrualPeriod() * discount_;
292 return price;
293 } else {
294 Real floorletPrice = optionletPrice(Option::Put, effectiveFloor);
295 return gearing_ * floorletPrice;
296 }
297}
298
299Rate LognormalCmsSpreadPricer::floorletRate(Rate effectiveFloor) const {
300 return floorletPrice(effectiveFloor) / (coupon_->accrualPeriod() * discount_);
301}
302
304 return gearing_ * coupon_->accrualPeriod() * discount_ * (gearing1_ * adjustedRate1_ + gearing2_ * adjustedRate2_) +
306}
307} // namespace QuantExt
base pricer for vanilla CMS spread coupons with a correlation surface
virtual Real capletPrice(Rate effectiveCap) const override
virtual Rate floorletRate(Rate effectiveFloor) const override
QuantLib::ext::shared_ptr< CmsCoupon > c1_
void initialize(const FloatingRateCoupon &coupon) override
QuantLib::ext::shared_ptr< GaussianQuadrature > integrator_
QuantLib::ext::shared_ptr< CmsCoupon > c2_
QuantLib::ext::shared_ptr< CmsCouponPricer > cmsPricer_
QuantLib::ext::shared_ptr< SwapSpreadIndex > index_
QuantLib::ext::shared_ptr< CumulativeNormalDistribution > cnd_
Handle< YieldTermStructure > couponDiscountCurve_
virtual Rate swapletRate() const override
LognormalCmsSpreadPricer(const QuantLib::ext::shared_ptr< CmsCouponPricer > cmsPricer, const Handle< QuantExt::CorrelationTermStructure > &correlation, const Handle< YieldTermStructure > &couponDiscountCurve=Handle< YieldTermStructure >(), const Size IntegrationPoints=16, const boost::optional< VolatilityType > volatilityType=boost::none, const Real shift1=Null< Real >(), const Real shift2=Null< Real >())
virtual Real floorletPrice(Rate effectiveFloor) const override
virtual Real swapletPrice() const override
Real optionletPrice(Option::Type optionType, Real strike) const
virtual Rate capletRate(Rate effectiveCap) const override
cms spread coupon pricer as in Brigo, Mercurio, 13.6.2, with extensions for shifted lognormal and nor...
Filter close_enough(const RandomVariable &x, const RandomVariable &y)