1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | |
3 | /* |
4 | Copyright (C) 2007 Ferdinando Ametrano |
5 | Copyright (C) 2001, 2002, 2003 Sadruddin Rejeb |
6 | Copyright (C) 2006 StatPro Italia srl |
7 | Copyright (C) 2015, 2016, 2017 Peter Caspers |
8 | Copyright (C) 2017 Paul Giltinan |
9 | Copyright (C) 2017 Werner Kuerzinger |
10 | Copyright (C) 2020 Marcin Rybacki |
11 | |
12 | This file is part of QuantLib, a free-software/open-source library |
13 | for financial quantitative analysts and developers - http://quantlib.org/ |
14 | |
15 | QuantLib is free software: you can redistribute it and/or modify it |
16 | under the terms of the QuantLib license. You should have received a |
17 | copy of the license along with this program; if not, please email |
18 | <quantlib-dev@lists.sf.net>. The license is also available online at |
19 | <http://quantlib.org/license.shtml>. |
20 | |
21 | This program is distributed in the hope that it will be useful, but WITHOUT |
22 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
23 | FOR A PARTICULAR PURPOSE. See the license for more details. |
24 | */ |
25 | |
26 | /*! \file blackswaptionengine.hpp |
27 | \brief Black-formula swaption engine |
28 | */ |
29 | |
30 | #ifndef quantlib_pricers_black_swaption_hpp |
31 | #define quantlib_pricers_black_swaption_hpp |
32 | |
33 | #include <ql/cashflows/cashflows.hpp> |
34 | #include <ql/cashflows/fixedratecoupon.hpp> |
35 | #include <ql/exercise.hpp> |
36 | #include <ql/indexes/iborindex.hpp> |
37 | #include <ql/instruments/swaption.hpp> |
38 | #include <ql/pricingengines/blackformula.hpp> |
39 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
40 | #include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp> |
41 | #include <ql/termstructures/volatility/swaption/swaptionvolstructure.hpp> |
42 | #include <ql/time/calendars/nullcalendar.hpp> |
43 | #include <utility> |
44 | |
45 | namespace QuantLib { |
46 | |
47 | class Quote; |
48 | |
49 | namespace detail { |
50 | |
51 | /*! Generic Black-style-formula swaption engine |
52 | This is the base class for the Black and Bachelier swaption engines */ |
53 | template<class Spec> |
54 | class BlackStyleSwaptionEngine : public Swaption::engine { |
55 | public: |
56 | enum CashAnnuityModel { SwapRate, DiscountCurve }; |
57 | BlackStyleSwaptionEngine(Handle<YieldTermStructure> discountCurve, |
58 | Volatility vol, |
59 | const DayCounter& dc = Actual365Fixed(), |
60 | Real displacement = 0.0, |
61 | CashAnnuityModel model = DiscountCurve); |
62 | BlackStyleSwaptionEngine(Handle<YieldTermStructure> discountCurve, |
63 | const Handle<Quote>& vol, |
64 | const DayCounter& dc = Actual365Fixed(), |
65 | Real displacement = 0.0, |
66 | CashAnnuityModel model = DiscountCurve); |
67 | BlackStyleSwaptionEngine(Handle<YieldTermStructure> discountCurve, |
68 | Handle<SwaptionVolatilityStructure> vol, |
69 | CashAnnuityModel model = DiscountCurve); |
70 | void calculate() const override; |
71 | Handle<YieldTermStructure> termStructure() { return discountCurve_; } |
72 | Handle<SwaptionVolatilityStructure> volatility() { return vol_; } |
73 | |
74 | private: |
75 | Handle<YieldTermStructure> discountCurve_; |
76 | Handle<SwaptionVolatilityStructure> vol_; |
77 | CashAnnuityModel model_; |
78 | }; |
79 | |
80 | // shifted lognormal type engine |
81 | struct Black76Spec { |
82 | static const VolatilityType type = ShiftedLognormal; |
83 | Real value(const Option::Type type, const Real strike, |
84 | const Real atmForward, const Real stdDev, const Real annuity, |
85 | const Real displacement) { |
86 | return blackFormula(optionType: type, strike, forward: atmForward, stdDev, discount: annuity, |
87 | displacement); |
88 | } |
89 | Real vega(const Real strike, const Real atmForward, const Real stdDev, |
90 | const Real exerciseTime, const Real annuity, |
91 | const Real displacement) { |
92 | return std::sqrt(x: exerciseTime) * |
93 | blackFormulaStdDevDerivative(strike, forward: atmForward, stdDev, |
94 | discount: annuity, displacement); |
95 | } |
96 | Real delta(const Option::Type type, const Real strike, |
97 | const Real atmForward, const Real stdDev, const Real annuity, |
98 | const Real displacement) { |
99 | return blackFormulaForwardDerivative(optionType: type, strike, forward: atmForward, stdDev, |
100 | discount: annuity, displacement); |
101 | } |
102 | }; |
103 | |
104 | // normal type engine |
105 | struct BachelierSpec { |
106 | static const VolatilityType type = Normal; |
107 | Real value(const Option::Type type, const Real strike, |
108 | const Real atmForward, const Real stdDev, const Real annuity, |
109 | const Real) { |
110 | return bachelierBlackFormula(optionType: type, strike, forward: atmForward, stdDev, |
111 | discount: annuity); |
112 | } |
113 | Real vega(const Real strike, const Real atmForward, const Real stdDev, |
114 | const Real exerciseTime, const Real annuity, const Real) { |
115 | return std::sqrt(x: exerciseTime) * |
116 | bachelierBlackFormulaStdDevDerivative( |
117 | strike, forward: atmForward, stdDev, discount: annuity); |
118 | } |
119 | Real delta(const Option::Type type, const Real strike, |
120 | const Real atmForward, const Real stdDev, const Real annuity, |
121 | const Real) { |
122 | return bachelierBlackFormulaForwardDerivative( |
123 | optionType: type, strike, forward: atmForward, stdDev, discount: annuity); |
124 | } |
125 | }; |
126 | |
127 | } // namespace detail |
128 | |
129 | //! Shifted Lognormal Black-formula swaption engine |
130 | /*! \ingroup swaptionengines |
131 | |
132 | \warning The engine assumes that the exercise date lies before the |
133 | start date of the passed swap. |
134 | */ |
135 | |
136 | class BlackSwaptionEngine |
137 | : public detail::BlackStyleSwaptionEngine<detail::Black76Spec> { |
138 | public: |
139 | BlackSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
140 | Volatility vol, |
141 | const DayCounter& dc = Actual365Fixed(), |
142 | Real displacement = 0.0, |
143 | CashAnnuityModel model = DiscountCurve); |
144 | BlackSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
145 | const Handle<Quote>& vol, |
146 | const DayCounter& dc = Actual365Fixed(), |
147 | Real displacement = 0.0, |
148 | CashAnnuityModel model = DiscountCurve); |
149 | BlackSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
150 | const Handle<SwaptionVolatilityStructure>& vol, |
151 | CashAnnuityModel model = DiscountCurve); |
152 | }; |
153 | |
154 | //! Normal Bachelier-formula swaption engine |
155 | /*! \ingroup swaptionengines |
156 | |
157 | \warning The engine assumes that the exercise date lies before the |
158 | start date of the passed swap. |
159 | */ |
160 | |
161 | class BachelierSwaptionEngine |
162 | : public detail::BlackStyleSwaptionEngine<detail::BachelierSpec> { |
163 | public: |
164 | BachelierSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
165 | Volatility vol, |
166 | const DayCounter& dc = Actual365Fixed(), |
167 | CashAnnuityModel model = DiscountCurve); |
168 | BachelierSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
169 | const Handle<Quote>& vol, |
170 | const DayCounter& dc = Actual365Fixed(), |
171 | CashAnnuityModel model = DiscountCurve); |
172 | BachelierSwaptionEngine(const Handle<YieldTermStructure>& discountCurve, |
173 | const Handle<SwaptionVolatilityStructure>& vol, |
174 | CashAnnuityModel model = DiscountCurve); |
175 | }; |
176 | |
177 | // implementation |
178 | |
179 | namespace detail { |
180 | |
181 | template <class Spec> |
182 | BlackStyleSwaptionEngine<Spec>::BlackStyleSwaptionEngine( |
183 | Handle<YieldTermStructure> discountCurve, |
184 | Volatility vol, |
185 | const DayCounter& dc, |
186 | Real displacement, |
187 | CashAnnuityModel model) |
188 | : discountCurve_(std::move(discountCurve)), |
189 | vol_(ext::shared_ptr<SwaptionVolatilityStructure>(new ConstantSwaptionVolatility( |
190 | 0, NullCalendar(), Following, vol, dc, Spec().type, displacement))), |
191 | model_(model) { |
192 | registerWith(h: discountCurve_); |
193 | } |
194 | |
195 | template <class Spec> |
196 | BlackStyleSwaptionEngine<Spec>::BlackStyleSwaptionEngine( |
197 | Handle<YieldTermStructure> discountCurve, |
198 | const Handle<Quote>& vol, |
199 | const DayCounter& dc, |
200 | Real displacement, |
201 | CashAnnuityModel model) |
202 | : discountCurve_(std::move(discountCurve)), |
203 | vol_(ext::shared_ptr<SwaptionVolatilityStructure>(new ConstantSwaptionVolatility( |
204 | 0, NullCalendar(), Following, vol, dc, Spec().type, displacement))), |
205 | model_(model) { |
206 | registerWith(h: discountCurve_); |
207 | registerWith(h: vol_); |
208 | } |
209 | |
210 | template <class Spec> |
211 | BlackStyleSwaptionEngine<Spec>::BlackStyleSwaptionEngine( |
212 | Handle<YieldTermStructure> discountCurve, |
213 | Handle<SwaptionVolatilityStructure> volatility, |
214 | CashAnnuityModel model) |
215 | : discountCurve_(std::move(discountCurve)), vol_(std::move(volatility)), model_(model) { |
216 | registerWith(h: discountCurve_); |
217 | registerWith(h: vol_); |
218 | } |
219 | |
220 | template<class Spec> |
221 | void BlackStyleSwaptionEngine<Spec>::calculate() const { |
222 | static const Spread basisPoint = 1.0e-4; |
223 | |
224 | Date exerciseDate = arguments_.exercise->date(index: 0); |
225 | |
226 | // the part of the swap preceding exerciseDate should be truncated |
227 | // to avoid taking into account unwanted cashflows |
228 | // for the moment we add a check avoiding this situation |
229 | VanillaSwap swap = *arguments_.swap; |
230 | const Leg& fixedLeg = swap.fixedLeg(); |
231 | ext::shared_ptr<FixedRateCoupon> firstCoupon = |
232 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: fixedLeg[0]); |
233 | QL_REQUIRE(firstCoupon->accrualStartDate() >= exerciseDate, |
234 | "swap start (" << firstCoupon->accrualStartDate() << ") before exercise date (" |
235 | << exerciseDate << ") not supported in Black swaption engine" ); |
236 | |
237 | Rate strike = swap.fixedRate(); |
238 | |
239 | // using the discounting curve |
240 | // swap.iborIndex() might be using a different forwarding curve |
241 | swap.setPricingEngine(ext::shared_ptr<PricingEngine>(new |
242 | DiscountingSwapEngine(discountCurve_, false))); |
243 | Rate atmForward = swap.fairRate(); |
244 | |
245 | // Volatilities are quoted for zero-spreaded swaps. |
246 | // Therefore, any spread on the floating leg must be removed |
247 | // with a corresponding correction on the fixed leg. |
248 | if (swap.spread()!=0.0) { |
249 | Spread correction = swap.spread() * |
250 | std::fabs(x: swap.floatingLegBPS()/swap.fixedLegBPS()); |
251 | strike -= correction; |
252 | atmForward -= correction; |
253 | results_.additionalResults["spreadCorrection" ] = correction; |
254 | } else { |
255 | results_.additionalResults["spreadCorrection" ] = Real(0.0); |
256 | } |
257 | results_.additionalResults["strike" ] = strike; |
258 | results_.additionalResults["atmForward" ] = atmForward; |
259 | |
260 | // using the discounting curve |
261 | swap.setPricingEngine(ext::shared_ptr<PricingEngine>( |
262 | new DiscountingSwapEngine(discountCurve_, false))); |
263 | Real annuity; |
264 | if (arguments_.settlementType == Settlement::Physical || |
265 | (arguments_.settlementType == Settlement::Cash && |
266 | arguments_.settlementMethod == |
267 | Settlement::CollateralizedCashPrice)) { |
268 | annuity = std::fabs(x: swap.fixedLegBPS()) / basisPoint; |
269 | } else if (arguments_.settlementType == Settlement::Cash && |
270 | arguments_.settlementMethod == Settlement::ParYieldCurve) { |
271 | DayCounter dayCount = firstCoupon->dayCounter(); |
272 | // we assume that the cash settlement date is equal |
273 | // to the swap start date |
274 | Date discountDate = model_ == DiscountCurve |
275 | ? firstCoupon->accrualStartDate() |
276 | : discountCurve_->referenceDate(); |
277 | Real fixedLegCashBPS = CashFlows::bps( |
278 | leg: fixedLeg, |
279 | yield: InterestRate(atmForward, dayCount, Compounded, Annual), includeSettlementDateFlows: false, |
280 | settlementDate: discountDate); |
281 | annuity = std::fabs(x: fixedLegCashBPS / basisPoint) * |
282 | discountCurve_->discount(d: discountDate); |
283 | } else { |
284 | QL_FAIL("invalid (settlementType, settlementMethod) pair" ); |
285 | } |
286 | results_.additionalResults["annuity" ] = annuity; |
287 | |
288 | Time swapLength = vol_->swapLength(start: swap.floatingSchedule().dates().front(), |
289 | end: swap.floatingSchedule().dates().back()); |
290 | |
291 | // swapLength is rounded to whole months. To ensure we can read a variance |
292 | // and a shift from vol_ we floor swapLength at 1/12 here therefore. |
293 | swapLength = std::max(a: swapLength, b: 1.0 / 12.0); |
294 | results_.additionalResults["swapLength" ] = swapLength; |
295 | |
296 | Real variance = vol_->blackVariance(optionDate: exerciseDate, swapLength, strike); |
297 | |
298 | Real displacement = |
299 | vol_->volatilityType() == ShiftedLognormal ? |
300 | vol_->shift(optionDate: exerciseDate, swapLength) : 0.0; |
301 | |
302 | Real stdDev = std::sqrt(x: variance); |
303 | results_.additionalResults["stdDev" ] = stdDev; |
304 | Option::Type w = (arguments_.type==Swap::Payer) ? Option::Call : Option::Put; |
305 | results_.value = Spec().value(w, strike, atmForward, stdDev, annuity, displacement); |
306 | |
307 | Time exerciseTime = vol_->timeFromReference(d: exerciseDate); |
308 | results_.additionalResults["vega" ] = Spec().vega( |
309 | strike, atmForward, stdDev, exerciseTime, annuity, displacement); |
310 | results_.additionalResults["delta" ] = Spec().delta( |
311 | w, strike, atmForward, stdDev, annuity, displacement); |
312 | results_.additionalResults["timeToExpiry" ] = exerciseTime; |
313 | results_.additionalResults["impliedVolatility" ] = Real(stdDev / std::sqrt(x: exerciseTime)); |
314 | } |
315 | |
316 | } // namespace detail |
317 | |
318 | } |
319 | |
320 | #endif |
321 | |