Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
optionletstripperwithatm.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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/*! \file qle/termstructures/optionletstripperwithatm.hpp
20 \brief Optionlet stripper that amends existing stripped optionlets to incorporate ATM cap floor volatilities
21 \ingroup termstructures
22*/
23
24#ifndef quantext_optionletstripper_with_atm_hpp
25#define quantext_optionletstripper_with_atm_hpp
26
33
34#include <ql/instruments/capfloor.hpp>
35#include <ql/instruments/makecapfloor.hpp>
36#include <ql/math/solvers1d/brent.hpp>
37#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
38#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
39#include <ql/quotes/simplequote.hpp>
40#include <ql/cashflows/cashflows.hpp>
41#include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp>
42
43namespace QuantExt {
44
45/*! Optionlet stripper that amends existing stripped optionlets to incorporate ATM cap floor
46 term volatilities
47 \ingroup termstructures
48*/
49template <class TimeInterpolator, class SmileInterpolator>
51
52public:
53 //! Constructor
54 OptionletStripperWithAtm(const QuantLib::ext::shared_ptr<QuantExt::OptionletStripper>& osBase,
55 const QuantLib::Handle<CapFloorTermVolCurve>& atmCurve,
56 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount =
57 QuantLib::Handle<QuantLib::YieldTermStructure>(),
58 const QuantLib::VolatilityType atmVolatilityType = QuantLib::ShiftedLognormal,
59 QuantLib::Real atmDisplacement = 0.0, QuantLib::Size maxEvaluations = 10000,
60 QuantLib::Real accuracy = 1.0e-12, const TimeInterpolator& ti = TimeInterpolator(),
61 const SmileInterpolator& si = SmileInterpolator());
62
63 //! \name Inspectors
64 std::vector<QuantLib::Rate> atmStrikes() const;
65 std::vector<QuantLib::Real> atmPrices() const;
66 std::vector<QuantLib::Volatility> volSpreads() const;
67 //@}
68
69 //! \name LazyObject interface
70 //@{
71 void performCalculations() const override;
72 //@}
73
74private:
75 //! Return the implied optionlet spreads to retrieve the ATM cap floor term volatilities
76 std::vector<QuantLib::Volatility> volSpreads(const QuantLib::Handle<QuantLib::YieldTermStructure>& discount,
77 const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs,
78 const bool isOis) const;
79
80 //! Class that is used to imply the spreads at each tenor such that the ATM cap floor volatilities are retrieved
82 public:
83 ObjectiveFunction(const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs,
84 const QuantLib::ext::shared_ptr<QuantLib::CapFloor>& cap, QuantLib::Real targetValue,
85 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount);
86
87 Real operator()(QuantLib::Volatility volSpread) const;
88
89 private:
90 QuantLib::ext::shared_ptr<QuantLib::SimpleQuote> spreadQuote_;
91 QuantLib::ext::shared_ptr<QuantLib::CapFloor> cap_;
92 QuantLib::Real targetValue_;
93 QuantLib::Handle<QuantLib::YieldTermStructure> discount_;
94 };
95
97 public:
98 ObjectiveFunctionOIS(const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs, const Leg& cap,
99 QuantLib::Real targetValue,
100 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount);
101
102 Real operator()(QuantLib::Volatility volSpread) const;
103
104 private:
105 QuantLib::ext::shared_ptr<QuantLib::SimpleQuote> spreadQuote_;
106 Leg cap_;
107 QuantLib::Real targetValue_;
108 QuantLib::Handle<QuantLib::YieldTermStructure> discount_;
109 };
110
111 //! Base optionlet stripper
112 QuantLib::ext::shared_ptr<QuantExt::OptionletStripper> osBase_;
113
114 //! ATM volatility curve
115 QuantLib::Handle<CapFloorTermVolCurve> atmCurve_;
116
117 //! ATM volatility type
118 QuantLib::VolatilityType atmVolatilityType_;
119
120 //! ATM volatility shift if the ATM volatility type is ShiftedLognormal
121 QuantLib::Real atmDisplacement_;
122
123 //! Maximum number of evaluations when searching for spread to match ATM volatilities
124 QuantLib::Size maxEvaluations_;
125
126 //! Required accuracy when searching for spread to match ATM volatilities
127 QuantLib::Real accuracy_;
128
129 //! The interpolation object in the time direction
130 TimeInterpolator ti_;
131
132 //! The interpolation object in the strike direction
133 SmileInterpolator si_;
134
135 //! The day counter for atmCurve_ and osBase_
136 DayCounter dayCounter_;
137
138 //! The number of ATM instruments in the ATM curve, atmCurve_
140
141 mutable std::vector<QuantLib::Rate> atmStrikes_;
142 mutable std::vector<QuantLib::Real> atmPrices_;
143 mutable std::vector<QuantLib::Volatility> volSpreads_;
144 mutable std::vector<QuantLib::ext::shared_ptr<QuantLib::CapFloor> > caps_;
145 mutable std::vector<QuantLib::Leg> capsOIS_;
146};
147
148template <class TimeInterpolator, class SmileInterpolator>
150 const QuantLib::ext::shared_ptr<QuantExt::OptionletStripper>& osBase,
151 const QuantLib::Handle<CapFloorTermVolCurve>& atmCurve,
152 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount, const QuantLib::VolatilityType atmVolatilityType,
153 QuantLib::Real atmDisplacement, QuantLib::Size maxEvaluations, QuantLib::Real accuracy, const TimeInterpolator& ti,
154 const SmileInterpolator& si)
155 : QuantExt::OptionletStripper(osBase->termVolSurface(), osBase->index(), discount, osBase->volatilityType(),
156 osBase->displacement(), osBase->rateComputationPeriod()),
157 osBase_(osBase), atmCurve_(atmCurve), atmVolatilityType_(atmVolatilityType), atmDisplacement_(atmDisplacement),
158 maxEvaluations_(maxEvaluations), accuracy_(accuracy), dayCounter_(osBase_->termVolSurface()->dayCounter()),
159 nAtmExpiries_(atmCurve_->optionTenors().size()), atmStrikes_(nAtmExpiries_), atmPrices_(nAtmExpiries_),
160 volSpreads_(nAtmExpiries_), caps_(nAtmExpiries_), capsOIS_(nAtmExpiries_) {
161
162 registerWith(osBase_);
163 registerWith(atmCurve_);
164
165 QL_REQUIRE(dayCounter_ == atmCurve_->dayCounter(),
166 "The ATM curve day counter should equal that of the underlying base optionlet stripper");
167}
168
169template <class TimeInterpolator, class SmileInterpolator>
171 calculate();
172 return atmStrikes_;
173}
174
175template <class TimeInterpolator, class SmileInterpolator>
177 calculate();
178 return atmPrices_;
179}
180
181template <class TimeInterpolator, class SmileInterpolator>
182inline std::vector<QuantLib::Volatility>
184 calculate();
185 return volSpreads_;
186}
187
188template <class TimeInterpolator, class SmileInterpolator>
190
191 // Some localised typedefs and using declarations to make the code more readable
192 using QuantLib::BachelierCapFloorEngine;
193 using QuantLib::BlackCapFloorEngine;
194 using QuantLib::Handle;
195 using QuantLib::MakeCapFloor;
196 using QuantLib::OptionletVolatilityStructure;
197 using QuantLib::Period;
198 using QuantLib::Size;
199 using QuantLib::Time;
200 using QuantLib::Volatility;
201 using QuantLib::YieldTermStructure;
202 using std::vector;
203
204 bool isOis = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(index_) != nullptr;
205
206 // Possible update based on underlying optionlet stripper data
207 optionletDates_ = osBase_->optionletFixingDates();
208 optionletPaymentDates_ = osBase_->optionletPaymentDates();
209 optionletAccrualPeriods_ = osBase_->optionletAccrualPeriods();
210 optionletTimes_ = osBase_->optionletFixingTimes();
211 atmOptionletRate_ = osBase_->atmOptionletRates();
212 for (Size i = 0; i < optionletTimes_.size(); ++i) {
213 optionletStrikes_[i] = osBase_->optionletStrikes(i);
214 optionletVolatilities_[i] = osBase_->optionletVolatilities(i);
215 }
216
217 // ATM curve tenors
218 const vector<Period>& atmTenors = atmCurve_->optionTenors();
219 vector<Time> atmTimes(atmTenors.size());
220 for (Size i = 0; i < atmTenors.size(); ++i) {
221 Date d = atmCurve_->optionDateFromTenor(atmTenors[i]);
222 atmTimes[i] = atmCurve_->timeFromReference(d);
223 }
224
225 // discount curve
226 const Handle<YieldTermStructure>& discountCurve = discount_.empty() ? index_->forwardingTermStructure() : discount_;
227
228 // Populate ATM strikes and prices
229 for (Size j = 0; j < nAtmExpiries_; ++j) {
230
231 if (isOis) {
232
233 Volatility atmVol = atmCurve_->volatility(atmTimes[j], 0.01);
234 auto pricer = QuantLib::ext::make_shared<BlackOvernightIndexedCouponPricer>(
235 Handle<OptionletVolatilityStructure>(QuantLib::ext::make_shared<ConstantOptionletVolatility>(
236 0, NullCalendar(), Unadjusted, atmVol, Actual365Fixed(), volatilityType(), false)));
237 capsOIS_[j] =
238 MakeOISCapFloor(CapFloor::Cap, atmTenors[j], QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(index_),
239 osBase_->rateComputationPeriod(), Null<Real>(), discountCurve)
240 .withCouponPricer(pricer);
241 QL_REQUIRE(!capsOIS_[j].empty(),
242 "OptionletStripperWithAtm: internal error: empty cap for expiry " << atmTenors[j]);
243 atmStrikes_[j] = getOisCapFloorStrikes(capsOIS_[j]).front().first;
244 atmPrices_[j] = QuantLib::CashFlows::npv(capsOIS_[j], **discountCurve, false);
245
246 } else {
247
248 // Create a cap for each pillar point on ATM curve and attach relevant pricing engine
249 Volatility atmVol = atmCurve_->volatility(atmTimes[j], 0.01);
250 QuantLib::ext::shared_ptr<PricingEngine> engine;
251 if (atmVolatilityType_ == ShiftedLognormal) {
252 engine = QuantLib::ext::make_shared<BlackCapFloorEngine>(discountCurve, atmVol, dayCounter_, atmDisplacement_);
253 } else if (atmVolatilityType_ == Normal) {
254 engine = QuantLib::ext::make_shared<BachelierCapFloorEngine>(discountCurve, atmVol, dayCounter_);
255 } else {
256 QL_FAIL("Unknown volatility type: " << atmVolatilityType_);
257 }
258
259 // Using Null<Rate>() as strike => strike will be set to ATM rate. However, to calculate ATM rate, QL
260 // requires a BlackCapFloorEngine to be set (not a BachelierCapFloorEngine)! So, need a temp
261 // BlackCapFloorEngine with a dummy vol to calculate ATM rate. Needs to be fixed in QL.
262 QuantLib::ext::shared_ptr<PricingEngine> tempEngine = QuantLib::ext::make_shared<BlackCapFloorEngine>(discountCurve, 0.01);
263 caps_[j] =
264 MakeCapFloor(CapFloor::Cap, atmTenors[j], index_, Null<Rate>(), 0 * Days).withPricingEngine(tempEngine);
265
266 // Now set correct engine and get the ATM rate and the price
267 caps_[j]->setPricingEngine(engine);
268 atmStrikes_[j] = caps_[j]->atmRate(**discountCurve);
269 atmPrices_[j] = caps_[j]->NPV();
270 }
271 }
272
273 // Create an optionlet volatility structure from the underlying stripped optionlet surface
274 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovs =
275 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<TimeInterpolator, SmileInterpolator> >(
276 atmCurve_->referenceDate(), osBase_);
277 ovs->enableExtrapolation();
278
279 // Imply the volatility spreads that match the ATM prices
280 volSpreads_ = volSpreads(discountCurve, Handle<OptionletVolatilityStructure>(ovs), isOis);
281
282 // This stripped optionlet surface is the underlying stripped optionlet surface modified by the implied spreads
283 for (Size j = 0; j < nAtmExpiries_; ++j) {
284 for (Size i = 0; i < optionletTimes_.size(); ++i) {
285 if (i < (isOis ? capsOIS_[j].size() : caps_[j]->floatingLeg().size())) {
286
287 Volatility unadjustedVol = ovs->volatility(optionletTimes_[i], atmStrikes_[j]);
288 Volatility adjustedVol = unadjustedVol + volSpreads_[j];
289
290 // insert ATM strike and adjusted volatility
291 vector<Rate>::const_iterator previous =
292 lower_bound(optionletStrikes_[i].begin(), optionletStrikes_[i].end(), atmStrikes_[j]);
293 Size insertIndex = previous - optionletStrikes_[i].begin();
294 optionletStrikes_[i].insert(optionletStrikes_[i].begin() + insertIndex, atmStrikes_[j]);
295 optionletVolatilities_[i].insert(optionletVolatilities_[i].begin() + insertIndex, adjustedVol);
296 }
297 }
298 }
299}
300
301template <class TimeInterpolator, class SmileInterpolator>
303 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount,
304 const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs, const bool isOis) const {
305
306 // Some localised typedefs and using declarations to make the code more readable
307 using QuantLib::Size;
308 using QuantLib::Volatility;
309 using std::vector;
310
311 QuantLib::Brent solver;
312 vector<Volatility> result(nAtmExpiries_);
313 Volatility guess = 0.0001;
314 Volatility minSpread = -0.1;
315 Volatility maxSpread = 0.1;
316 solver.setMaxEvaluations(maxEvaluations_);
317
318 for (Size j = 0; j < nAtmExpiries_; ++j) {
319 if (isOis) {
320 ObjectiveFunctionOIS f(ovs, capsOIS_[j], atmPrices_[j], discount);
321 result[j] = solver.solve(f, accuracy_, guess, minSpread, maxSpread);
322 } else {
323 ObjectiveFunction f(ovs, caps_[j], atmPrices_[j], discount);
324 result[j] = solver.solve(f, accuracy_, guess, minSpread, maxSpread);
325 }
326 }
327
328 return result;
329}
330
331template <class TimeInterpolator, class SmileInterpolator>
333 const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs,
334 const QuantLib::ext::shared_ptr<QuantLib::CapFloor>& cap, QuantLib::Real targetValue,
335 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount)
336 : cap_(cap), targetValue_(targetValue), discount_(discount) {
337
338 // Some localised typedefs and using declarations to make the code more readable
339 using QuantLib::Handle;
340 using QuantLib::OptionletVolatilityStructure;
341 using QuantLib::Quote;
342 using QuantLib::SimpleQuote;
343
344 // set an implausible value, so that calculation is forced at first operator()(Volatility x) call
345 spreadQuote_ = QuantLib::ext::make_shared<SimpleQuote>(-1.0);
346
347 // Spreaded optionlet volatility structure that will be used to price the ATM cap
348 Handle<OptionletVolatilityStructure> spreadedOvs = Handle<OptionletVolatilityStructure>(
349 QuantLib::ext::make_shared<QuantExt::SpreadedOptionletVolatility>(ovs, Handle<Quote>(spreadQuote_)));
350
351 // Attach the relevant engine to the cap with the spreaded optionlet volatility structure
352 if (ovs->volatilityType() == ShiftedLognormal) {
353 cap_->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(discount_, spreadedOvs, ovs->displacement()));
354 } else if (ovs->volatilityType() == Normal) {
355 cap_->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount_, spreadedOvs));
356 } else {
357 QL_FAIL("Unknown volatility type: " << ovs->volatilityType());
358 }
359}
360
361template <class TimeInterpolator, class SmileInterpolator>
363 const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& ovs, const Leg& cap, QuantLib::Real targetValue,
364 const QuantLib::Handle<QuantLib::YieldTermStructure>& discount)
365 : cap_(cap), targetValue_(targetValue), discount_(discount) {
366
367 // Some localised typedefs and using declarations to make the code more readable
368 using QuantLib::Handle;
369 using QuantLib::OptionletVolatilityStructure;
370 using QuantLib::Quote;
371 using QuantLib::SimpleQuote;
372
373 // set an implausible value, so that calculation is forced at first operator()(Volatility x) call
374 spreadQuote_ = QuantLib::ext::make_shared<SimpleQuote>(-1.0);
375
376 // Spreaded optionlet volatility structure that will be used to price the ATM cap
377 Handle<OptionletVolatilityStructure> spreadedOvs = Handle<OptionletVolatilityStructure>(
378 QuantLib::ext::make_shared<QuantExt::SpreadedOptionletVolatility>(ovs, Handle<Quote>(spreadQuote_)));
379
380 auto pricer = QuantLib::ext::make_shared<BlackOvernightIndexedCouponPricer>(spreadedOvs, false);
381 for (auto& c : cap_) {
382 if (auto f = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(c))
383 f->setPricer(pricer);
384 }
385}
386
387template <class TimeInterpolator, class SmileInterpolator>
389 QuantLib::Volatility volSpread) const {
390 if (volSpread != spreadQuote_->value()) {
391 spreadQuote_->setValue(volSpread);
392 }
393 return cap_->NPV() - targetValue_;
394}
395
396template <class TimeInterpolator, class SmileInterpolator>
398 QuantLib::Volatility volSpread) const {
399 if (volSpread != spreadQuote_->value()) {
400 spreadQuote_->setValue(volSpread);
401 }
402 return QuantLib::CashFlows::npv(cap_, **discount_, false) - targetValue_;
403}
404
405} // namespace QuantExt
406
407#endif
black coupon pricer for capped / floored ON indexed coupons
Cap floor at-the-money term volatility curve.
Real targetValue_
Definition: cdsoption.cpp:79
MakeOISCapFloor & withCouponPricer(const ext::shared_ptr< CappedFlooredOvernightIndexedCouponPricer > &pricer)
Handle< YieldTermStructure > discount_
Class that is used to imply the spreads at each tenor such that the ATM cap floor volatilities are re...
ObjectiveFunction(const QuantLib::Handle< QuantLib::OptionletVolatilityStructure > &ovs, const QuantLib::ext::shared_ptr< QuantLib::CapFloor > &cap, QuantLib::Real targetValue, const QuantLib::Handle< QuantLib::YieldTermStructure > &discount)
Real operator()(QuantLib::Volatility volSpread) const
QuantLib::Handle< QuantLib::YieldTermStructure > discount_
QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > spreadQuote_
QuantLib::ext::shared_ptr< QuantLib::CapFloor > cap_
ObjectiveFunctionOIS(const QuantLib::Handle< QuantLib::OptionletVolatilityStructure > &ovs, const Leg &cap, QuantLib::Real targetValue, const QuantLib::Handle< QuantLib::YieldTermStructure > &discount)
QuantLib::Handle< QuantLib::YieldTermStructure > discount_
QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > spreadQuote_
QuantLib::VolatilityType atmVolatilityType_
ATM volatility type.
OptionletStripperWithAtm(const QuantLib::ext::shared_ptr< QuantExt::OptionletStripper > &osBase, const QuantLib::Handle< CapFloorTermVolCurve > &atmCurve, const QuantLib::Handle< QuantLib::YieldTermStructure > &discount=QuantLib::Handle< QuantLib::YieldTermStructure >(), const QuantLib::VolatilityType atmVolatilityType=QuantLib::ShiftedLognormal, QuantLib::Real atmDisplacement=0.0, QuantLib::Size maxEvaluations=10000, QuantLib::Real accuracy=1.0e-12, const TimeInterpolator &ti=TimeInterpolator(), const SmileInterpolator &si=SmileInterpolator())
Constructor.
TimeInterpolator ti_
The interpolation object in the time direction.
std::vector< QuantLib::Rate > atmStrikes_
std::vector< QuantLib::Volatility > volSpreads() const
std::vector< QuantLib::ext::shared_ptr< QuantLib::CapFloor > > caps_
DayCounter dayCounter_
The day counter for atmCurve_ and osBase_.
Size nAtmExpiries_
The number of ATM instruments in the ATM curve, atmCurve_.
SmileInterpolator si_
The interpolation object in the strike direction.
std::vector< QuantLib::Volatility > volSpreads_
QuantLib::Real atmDisplacement_
ATM volatility shift if the ATM volatility type is ShiftedLognormal.
QuantLib::Real accuracy_
Required accuracy when searching for spread to match ATM volatilities.
QuantLib::Handle< CapFloorTermVolCurve > atmCurve_
ATM volatility curve.
QuantLib::Size maxEvaluations_
Maximum number of evaluations when searching for spread to match ATM volatilities.
QuantLib::ext::shared_ptr< QuantExt::OptionletStripper > osBase_
Base optionlet stripper.
std::vector< QuantLib::Real > atmPrices_
std::vector< QuantLib::Real > atmPrices() const
std::vector< QuantLib::Rate > atmStrikes() const
helper class to instantiate standard market OIS cap / floors
std::vector< std::pair< Real, Real > > getOisCapFloorStrikes(const Leg &oisCapFloor)
get the (cap, floor) - strikes from an OIS cf
optionlet (caplet/floorlet) volatility stripper
Adds floor to QuantLib::SpreadedOptionletVolatility.
Convert a StrippedOptionletBase in to an OptionletVolatilityStructure.