Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
cpipricevolatilitysurface.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2022 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 strippedcpivolatilitystructure.hpp
20 \brief zero inflation volatility structure implied from a cpi cap/floor price surface
21 */
22
23#pragma once
24
25#include <iostream>
26#include <ql/math/solvers1d/brent.hpp>
33
34namespace QuantExt {
35
37 static constexpr QuantLib::Real upperVolBound = 1.0;
38 static constexpr QuantLib::Real lowerVolBound = 0.000001;
39 static constexpr QuantLib::Real solverTolerance = 1.0e-12;
40};
41
42//! Stripped zero inflation volatility structure
43/*!
44 The surface provides implied CPI Black volatilities for the union of strikes that occur in the underlying
45 cap and floor price surface.
46
47 The type argument determines which kind of price quotes are used with priority when there is an overlap,
48 i.e. strikes for which we have both cap and floor quotes:
49 If type is Cap: Use cap quotes where available, floor quotes otherwise
50 If type is Floor: Use floor quotes where available, cap quotes otherwise
51 If type is CapFloor: In case of overlap, use floor quotes up to the ATM strike, cap quotes for strikes beyond
52 ATM
53 */
54
55template <class InterpolatorStrike, class InterpolatorTime>
56class CPIPriceVolatilitySurface : public QuantExt::CPIVolatilitySurface, public QuantLib::LazyObject {
57public:
59 PriceQuotePreference type, const QuantLib::Period& observationLag,
60 const QuantLib::Calendar& cal, // calendar in index may not be useful
61 const QuantLib::BusinessDayConvention& bdc, const QuantLib::DayCounter& dc,
62 const QuantLib::ext::shared_ptr<QuantLib::ZeroInflationIndex> index,
63 QuantLib::Handle<QuantLib::YieldTermStructure> yts,
64 const std::vector<QuantLib::Rate>& cStrikes, const std::vector<QuantLib::Rate>& fStrikes,
65 const std::vector<QuantLib::Period>& cfMaturities, const QuantLib::Matrix& cPrice,
66 const QuantLib::Matrix& fPrice, const QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine>& engine,
67 const bool quotedInstrumentsAreInterpolated = false,
68 const QuantLib::Date& capFloorStartDate = QuantLib::Date(),
69 bool ignoreMissingPrices = false, // if true, it allows prices to be Null and work as long there is one
70 bool lowerStrikeConstExtrap = true, bool upperStrikeConstExtrap = true,
71 const QuantLib::VolatilityType& volType = QuantLib::ShiftedLognormal, const double displacement = 0.0,
72 const QuantLib::Real& upperVolBound = CPIPriceVolatilitySurfaceDefaultValues::upperVolBound,
73 const QuantLib::Real& lowerVolBound = CPIPriceVolatilitySurfaceDefaultValues::lowerVolBound,
74 const QuantLib::Real& solverTolerance = CPIPriceVolatilitySurfaceDefaultValues::solverTolerance);
75
76
77 QL_DEPRECATED CPIPriceVolatilitySurface(
79 const QuantLib::Period& observationLag,
80 const QuantLib::Calendar& cal, // calendar in index may not be useful
81 const QuantLib::BusinessDayConvention& bdc,
82 const QuantLib::DayCounter& dc,
83 const QuantLib::ext::shared_ptr<QuantLib::ZeroInflationIndex> index,
84 QuantLib::Handle<QuantLib::YieldTermStructure> yts,
85 const std::vector<QuantLib::Rate>& cStrikes, const std::vector<QuantLib::Rate>& fStrikes,
86 const std::vector<QuantLib::Period>& cfMaturities, const QuantLib::Matrix& cPrice,
87 const QuantLib::Matrix& fPrice,
88 const QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine>& engine,
89 const QuantLib::Date& capFloorStartDate = QuantLib::Date(),
90 bool ignoreMissingPrices = false, // if true, it allows prices to be Null and work as long there is one
91 bool lowerStrikeConstExtrap = true, bool upperStrikeConstExtrap = true,
92 const QuantLib::VolatilityType& volType = QuantLib::ShiftedLognormal, const double displacement = 0.0,
93 const QuantLib::Real& upperVolBound = CPIPriceVolatilitySurfaceDefaultValues::upperVolBound,
94 const QuantLib::Real& lowerVolBound = CPIPriceVolatilitySurfaceDefaultValues::lowerVolBound,
95 const QuantLib::Real& solverTolerance = CPIPriceVolatilitySurfaceDefaultValues::solverTolerance);
96
97 //! \name LazyObject interface
98 //@{
99 void performCalculations() const override;
100 //@}
101
102 void update() override {
103 CPIVolatilitySurface::update();
104 QuantLib::LazyObject::update();
105 }
106
107 //! \name Limits
108 //@{
109 //! the minimum strike for which the term structure can return vols
110 QuantLib::Real minStrike() const override;
111 //! the maximum strike for which the term structure can return vols
112 QuantLib::Real maxStrike() const override;
113 //! maximum date for which the term structure can return vols
114 QuantLib::Date maxDate() const override;
115 //@}
116
117 //! \name Inspectors
118 //@{
119 //! Returns the strikes
120 const std::vector<QuantLib::Real>& strikes() { return strikes_; }
121
122 //! Returns the tenors
123 const std::vector<QuantLib::Period>& maturities() { return expiries_; }
124
125 const QuantLib::Matrix& volData() const {
126 calculate();
127 return volData_;
128 }
129
130 const std::vector<std::vector<bool>>& missingValues() const {
131 calculate();
132 return missingPrices_;
133 }
134
135 const std::vector<std::vector<bool>>& pricesFailedToConvert() const {
136 calculate();
137 return failedPrices_;
138 }
139
140 //@}
141
142protected:
143 //! CPI fixing on the baseDate of the surface
144 double baseCPI() const;
145
146 double atmGrowth(QuantLib::Period& tenor) const;
147
148 double atmGrowth(const QuantLib::Date& date) const;
149
150 QuantLib::Real atmStrike(const QuantLib::Date& maturity,
151 const QuantLib::Period& obsLag = QuantLib::Period(-1, QuantLib::Days)) const override;
152
153private:
154 virtual void validateInputParameters() const;
155
156 // unique union of capStrikes and floorStrikes
157 virtual void initializeStrikes() const;
158
159 //! Computes a cap price from a floor price using the put-call parity and vice-versa
160 double priceFromPutCallParity(double price, bool isCapPrice, double atm, double strikeGrowth, double df) const;
161
162 //! Returns floor price for strike level (average annual inflation) and maturity index
163 double floorPrice(double strike, size_t tenorIdx, double atm, double strikeGrowth, double df) const;
164
165 //! Returns cap price for strike level (average annual inflation) and maturity index
166 double capPrice(double strike, size_t tenorIdx, double atm, double strikeGrowth, double df) const;
167
168 QuantLib::Volatility volatilityImpl(QuantLib::Time length, QuantLib::Rate strike) const override;
169
170 // prefer cap or floor quote
171 bool chooseFloor(QuantLib::Real strike, QuantLib::Real atmRate) const;
172
173 // imply the black / bachelier vol from the cap/floor price using brent solver
174 double implyVol(double strike, const QuantLib::Date& maturity, double price, bool isFloor) const;
175
177 QuantLib::ext::shared_ptr<QuantLib::ZeroInflationIndex> index_;
178 QuantLib::Handle<QuantLib::YieldTermStructure> yts_;
179 std::vector<double> capStrikes_;
180 std::vector<double> floorStrikes_;
181
182 QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine> engine_;
186 QuantLib::Real upperVolBound_;
187 QuantLib::Real lowerVolBound_;
188 QuantLib::Real solverTolerance_;
189
190 std::vector<QuantLib::Period> expiries_;
191 mutable std::vector<QuantLib::Rate> strikes_;
192
193 QuantLib::Matrix capPrices_;
194 QuantLib::Matrix floorPrices_;
195 mutable std::vector<QuantLib::Date> fixingDates_;
196 mutable QuantLib::Matrix volData_;
197 mutable std::vector<std::vector<bool>> missingPrices_;
198 mutable std::vector<std::vector<bool>> failedPrices_;
199
200 mutable QuantLib::ext::shared_ptr<QuantExt::OptionInterpolator2d<InterpolatorStrike, InterpolatorTime>> volSurface_;
201};
202
203
204
205template <class InterpolatorStrike, class InterpolatorTime>
207 PriceQuotePreference type, const QuantLib::Period& observationLag,
208 const QuantLib::Calendar& cal, // calendar in index may not be useful
209 const QuantLib::BusinessDayConvention& bdc, const QuantLib::DayCounter& dc,
210 const QuantLib::ext::shared_ptr<QuantLib::ZeroInflationIndex> index, QuantLib::Handle<QuantLib::YieldTermStructure> yts,
211 const std::vector<QuantLib::Rate>& cStrikes, const std::vector<QuantLib::Rate>& fStrikes,
212 const std::vector<QuantLib::Period>& cfMaturities, const QuantLib::Matrix& cPrice, const QuantLib::Matrix& fPrice,
213 const QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine>& engine, const bool quotedInstrumentsAreInterpolated,
214 const QuantLib::Date& capFloorStartDate,
215 bool ignoreMissingPrices, // if true, it allows prices to be Null and work as long there is one
216 bool lowerStrikeConstExtrap, bool upperStrikeConstExtrap, const QuantLib::VolatilityType& volType,
217 const double displacement, const QuantLib::Real& upperVolBound, const QuantLib::Real& lowerVolBound,
218 const QuantLib::Real& solverTolerance)
219 : QuantExt::CPIVolatilitySurface(0, cal, bdc, dc, observationLag, index->frequency(), quotedInstrumentsAreInterpolated,
220 capFloorStartDate, volType, displacement),
221 preference_(type), index_(index), yts_(yts), capStrikes_(cStrikes), floorStrikes_(fStrikes), engine_(engine),
222 ignoreMissingPrices_(ignoreMissingPrices), lowerStrikeConstExtrap_(lowerStrikeConstExtrap),
223 upperStrikeConstExtrap_(upperStrikeConstExtrap), upperVolBound_(upperVolBound), lowerVolBound_(lowerVolBound),
224 solverTolerance_(solverTolerance), expiries_(cfMaturities), capPrices_(cPrice), floorPrices_(fPrice) {
227 QL_REQUIRE(!yts.empty(), "DiscountCurve not provided");
228 QL_REQUIRE(engine, "PricingEngine not provided");
229 QL_REQUIRE(index, "PricingEngine not provided");
230 registerWith(index_);
231 registerWith(yts_);
232}
233
234QL_DEPRECATED_DISABLE_WARNING
235template <class InterpolatorStrike, class InterpolatorTime>
238 const QuantLib::Period& observationLag,
239 const QuantLib::Calendar& cal, // calendar in index may not be useful
240 const QuantLib::BusinessDayConvention& bdc,
241 const QuantLib::DayCounter& dc,
242 const QuantLib::ext::shared_ptr<QuantLib::ZeroInflationIndex> index,
243 QuantLib::Handle<QuantLib::YieldTermStructure> yts,
244 const std::vector<QuantLib::Rate>& cStrikes, const std::vector<QuantLib::Rate>& fStrikes,
245 const std::vector<QuantLib::Period>& cfMaturities, const QuantLib::Matrix& cPrice, const QuantLib::Matrix& fPrice,
246 const QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine>& engine,
247 const QuantLib::Date& capFloorStartDate,
248 bool ignoreMissingPrices, // if true, it allows prices to be Null and work as long there is one
249 bool lowerStrikeConstExtrap, bool upperStrikeConstExtrap,
250 const QuantLib::VolatilityType& volType, const double displacement, const QuantLib::Real& upperVolBound,
251 const QuantLib::Real& lowerVolBound, const QuantLib::Real& solverTolerance)
252 : QuantExt::CPIVolatilitySurface(0, cal, bdc, dc, observationLag, index->frequency(), index->interpolated(),
253 capFloorStartDate, volType, displacement),
254 preference_(type), index_(index), yts_(yts), capStrikes_(cStrikes), floorStrikes_(fStrikes), engine_(engine),
255 ignoreMissingPrices_(ignoreMissingPrices), lowerStrikeConstExtrap_(lowerStrikeConstExtrap),
256 upperStrikeConstExtrap_(upperStrikeConstExtrap), upperVolBound_(upperVolBound), lowerVolBound_(lowerVolBound),
257 solverTolerance_(solverTolerance), expiries_(cfMaturities), capPrices_(cPrice), floorPrices_(fPrice) {
260 QL_REQUIRE(!yts.empty(), "DiscountCurve not provided");
261 QL_REQUIRE(engine, "PricingEngine not provided");
262 QL_REQUIRE(index, "PricingEngine not provided");
263 registerWith(index_);
264 registerWith(yts_);
265}
266QL_DEPRECATED_ENABLE_WARNING
267
268template <class InterpolatorStrike, class InterpolatorTime>
270 volData_ = QuantLib::Matrix(strikes_.size(), expiries_.size(), QuantLib::Null<QuantLib::Real>());
271 missingPrices_ = std::vector<std::vector<bool>>(strikes_.size(), std::vector<bool>(expiries_.size(), false));
272 failedPrices_ = std::vector<std::vector<bool>>(strikes_.size(), std::vector<bool>(expiries_.size(), false));
273
274
275 std::vector<QuantLib::Date> dates;
276 std::vector<double> strikes;
277 std::vector<double> vols;
278
279 for (QuantLib::Size tenorIdx = 0; tenorIdx < expiries_.size(); tenorIdx++) {
280 QuantLib::Date maturityDate = optionDateFromTenor(expiries_[tenorIdx]);
281 QuantLib::Date fixingDate =
282 ZeroInflation::fixingDate(maturityDate, observationLag(), frequency(), indexIsInterpolated());
283 double atm = atmGrowth(maturityDate);
284 double df = yts_->discount(fixingDate);
285 double ttm =
286 QuantLib::inflationYearFraction(frequency(), indexIsInterpolated(), dayCounter(), baseDate(), fixingDate);
287 fixingDates_.push_back(fixingDate);
288 double atmAvgRate = std::pow(atm, 1.0 / ttm) - 1.0;
289 for (QuantLib::Size strikeIdx = 0; strikeIdx < strikes_.size(); strikeIdx++) {
290 double strike = strikes_[strikeIdx];
291 double strikeGrowth = std::pow(1.0 + strike, ttm);
292 bool useFloor = chooseFloor(strike, atmAvgRate);
293 double vol = QuantLib::Null<QuantLib::Real>();
294 QuantLib::Real priceToMatch = useFloor ? floorPrice(strike, tenorIdx, atm, strikeGrowth, df)
295 : capPrice(strike, tenorIdx, atm, strikeGrowth, df);
296 if (priceToMatch != QuantLib::Null<QuantLib::Real>()) {
297 try {
298 vol = implyVol(strike, maturityDate, priceToMatch, useFloor);
299 } catch (const std::exception&) {
300 // implied failed, we try to interpolate the failed values
301 vol = QuantLib::Null<QuantLib::Real>();
302 failedPrices_[strikeIdx][tenorIdx] = true;
303 }
304 } else {
305 missingPrices_[strikeIdx][tenorIdx] = true;
306 QL_REQUIRE(ignoreMissingPrices_, "Missing price for cpi capfloor vol for tenor "
307 << expiries_[tenorIdx] << " and strike " << strike);
308 }
309 if (vol != QuantLib::Null<QuantLib::Real>()) {
310 dates.push_back(fixingDate);
311 strikes.push_back(strike);
312 vols.push_back(vol);
313 }
314 }
315 }
316
317 volSurface_ = QuantLib::ext::make_shared<QuantExt::OptionInterpolator2d<InterpolatorStrike, InterpolatorTime>>(
318 referenceDate(), dayCounter(), dates, strikes, vols, lowerStrikeConstExtrap_, upperStrikeConstExtrap_,
319 InterpolatorStrike(), InterpolatorTime(), baseDate());
320
321 for (QuantLib::Size strikeIdx = 0; strikeIdx < strikes_.size(); strikeIdx++) {
322 for (QuantLib::Size tenorIdx = 0; tenorIdx < expiries_.size(); tenorIdx++) {
323 volData_[strikeIdx][tenorIdx] = volSurface_->getValue(fixingDates_[tenorIdx], strikes_[strikeIdx]);
324 }
325 }
326}
327
328template <class InterpolatorStrike, class InterpolatorTime>
330 QuantLib::Real atmRate) const {
331 if (floorStrikes_.empty()) {
332 return false;
333 }
334
335 if (capStrikes_.empty()) {
336 return true;
337 }
338
339 if (preference_ == Floor) {
340 if (strike <= floorStrikes_.back())
341 return true;
342 else
343 return false;
344 }
345
346 if (preference_ == Cap) {
347 if (strike < capStrikes_.front())
348 return true;
349 else
350 return false;
351 }
352
353 // else: Use floors where we have floor quotes only, caps where we have cap quotes only,
354 // and decide based on ATM where we have both cap and floor quotes
355
356 // 1) strike < maxFloorStrike < minCapStrike: Floor!
357 if (strike <= floorStrikes_.back() && strike < capStrikes_.front())
358 return true;
359 // 2) strike > maxFloorStrike and strike >= minCapStrike: Cap!
360 else if (strike > floorStrikes_.back() && strike >= capStrikes_.front())
361 return false;
362 // 3) Overlap, maxFloorStrike > minCapStrike and strike in between: Depends on atmRate which surface we pick
363 else if (strike <= floorStrikes_.back() && strike >= capStrikes_.front()) {
364 // we have overlapping strikes, decide depending on atm level
365 if (strike < atmRate)
366 return true;
367 else
368 return false;
369 // 4) Gap, maxFloorStrike < minCapStrike and strike in the gap: Depends on atmRate which surface we
370 // extrapolate
371 } else if (strike > floorStrikes_.back() && strike < capStrikes_.front()) {
372 // there is a gap between floor end and caps begin, decide again depending on strike level
373 if (strike < atmRate)
374 return true;
375 else
376 return false;
377 } else {
378 QL_FAIL("case not covered in StrippedCPIVolatilitySurface: strike="
379 << strike << " maxFloorStrike=" << floorStrikes_.back() << " minCapStrike=" << capStrikes_.front()
380 << " atm=" << atmRate);
381 return false;
382 }
383}
384
385template <class InterpolatorStrike, class InterpolatorTime>
387 return strikes_.front() - QL_EPSILON;
388}
389
390template <class InterpolatorStrike, class InterpolatorTime>
392 return strikes_.back() + QL_EPSILON;
393}
394
395template <class InterpolatorStrike, class InterpolatorTime>
397 return optionDateFromTenor(expiries_.back());
398}
399
400template <class InterpolatorStrike, class InterpolatorTime>
401QuantLib::Real
403 QuantLib::Rate strike) const {
404 calculate();
405 return volSurface_->getValue(length, strike);
406}
407
408template <class InterpolatorStrike, class InterpolatorTime>
410 return ZeroInflation::cpiFixing(index_, capFloorStartDate(), observationLag(), indexIsInterpolated());
411}
412
413template <class InterpolatorStrike, class InterpolatorTime>
415 return atmGrowth(optionDateFromTenor(tenor));
416}
417
418template <class InterpolatorStrike, class InterpolatorTime>
420 return ZeroInflation::cpiFixing(index_, date, observationLag(), indexIsInterpolated()) / baseCPI();
421}
422
423template <class InterpolatorStrike, class InterpolatorTime>
425 QL_REQUIRE(!expiries_.empty(), "Need at least one tenor");
426 QL_REQUIRE(!floorStrikes_.empty() || !capStrikes_.empty(), "cap and floor strikes can not be both empty");
427 QL_REQUIRE(capPrices_.rows() == capStrikes_.size() && capPrices_.columns() == (capStrikes_.empty() ? 0 : expiries_.size()),
428 "mismatch between cap price matrix dimension and number of strikes and tenors");
429 QL_REQUIRE(floorPrices_.rows() == floorStrikes_.size() &&
430 floorPrices_.columns() == (floorStrikes_.empty() ? 0 : expiries_.size()),
431 "mismatch between cap price matrix dimension and number of strikes and tenors");
432
433 // Some basic arbitrage checks
434 for (size_t tenorIdx = 0; tenorIdx < capPrices_.columns(); ++tenorIdx) {
435 double prevPrice = std::numeric_limits<double>::max();
436 for (size_t strikeIdx = 0; strikeIdx < capPrices_.rows(); ++strikeIdx) {
437 double currentPrice = capPrices_[strikeIdx][tenorIdx];
438 QL_REQUIRE(ignoreMissingPrices_ || currentPrice != QuantLib::Null<QuantLib::Real>(),
439 "Input prices can not be null");
440 QL_REQUIRE(!QuantLib::close_enough(0.0, currentPrice) && currentPrice > 0.0, "No zero cap prices allowd");
441 if (currentPrice != QuantLib::Null<Real>()) {
442 QL_REQUIRE(currentPrice <= prevPrice, "Non decreasing cap prices");
443 prevPrice = currentPrice;
444 }
445 }
446 }
447
448 for (size_t tenorIdx = 0; tenorIdx < floorPrices_.columns(); ++tenorIdx) {
449 double prevPrice = QL_EPSILON;
450 for (size_t strikeIdx = 0; strikeIdx < floorPrices_.rows(); ++strikeIdx) {
451 double currentPrice = floorPrices_[strikeIdx][tenorIdx];
452 QL_REQUIRE(ignoreMissingPrices_ || currentPrice != QuantLib::Null<QuantLib::Real>(),
453 "Input prices can not be null");
454 QL_REQUIRE(!QuantLib::close_enough(0.0, currentPrice) && currentPrice > 0.0, "No zero cap prices allowd");
455 if (currentPrice != QuantLib::Null<Real>()) {
456 QL_REQUIRE(currentPrice >= prevPrice, "Non increasing floor prices");
457 prevPrice = currentPrice;
458 }
459 }
460 }
461}
462
463template <class InterpolatorStrike, class InterpolatorTime>
465 strikes_.clear();
466 std::set<double> uniqueStrikes(floorStrikes_.begin(), floorStrikes_.end());
467 uniqueStrikes.insert(capStrikes_.begin(), capStrikes_.end());
468 std::unique_copy(uniqueStrikes.begin(), uniqueStrikes.end(), std::back_inserter(strikes_),
469 [](double a, double b) { return QuantLib::close_enough(a, b); });
470}
471
472//! Computes a cap price from a floor price using the put-call parity and vice-versa
473template <class InterpolatorStrike, class InterpolatorTime>
475 double price, bool isCapPrice, double atm, double strikeGrowth, double df) const {
476 if (isCapPrice) {
477 return price + atm - strikeGrowth * df;
478 } else {
479 return price + strikeGrowth * df - atm;
480 }
481}
482
483//! Returns floor price for strike level (average annual inflation) and maturity index
484template <class InterpolatorStrike, class InterpolatorTime>
486 double atm, double strikeGrowth,
487 double df) const {
488 auto close_enough = [&strike](const double& x) { return QuantLib::close_enough(strike, x); };
489 auto itCap = std::find_if(capStrikes_.begin(), capStrikes_.end(), close_enough);
490 auto itFloor = std::find_if(floorStrikes_.begin(), floorStrikes_.end(), close_enough);
491 if (itFloor != floorStrikes_.end()) {
492 size_t floorPriceIdx = std::distance(floorStrikes_.begin(), itFloor);
493 return floorPrices_[floorPriceIdx][tenorIdx];
494 } else if (itCap != capStrikes_.end()) {
495 size_t capPriceIdx = std::distance(capStrikes_.begin(), itCap);
496 double capPrice = capPrices_[capPriceIdx][tenorIdx];
497 return priceFromPutCallParity(capPrice, true, atm, strikeGrowth, df);
498 } else {
499 return QuantLib::Null<double>();
500 }
501}
502
503//! Returns cap price for strike level (average annual inflation) and maturity index
504template <class InterpolatorStrike, class InterpolatorTime>
506 double atm, double strikeGrowth,
507 double df) const {
508 auto close_enough = [&strike](const double& x) { return QuantLib::close_enough(strike, x); };
509 auto itCap = std::find_if(capStrikes_.begin(), capStrikes_.end(), close_enough);
510 auto itFloor = std::find_if(floorStrikes_.begin(), floorStrikes_.end(), close_enough);
511 if (itCap != capStrikes_.end()) {
512 size_t capPriceIdx = std::distance(capStrikes_.begin(), itCap);
513 return capPrices_[capPriceIdx][tenorIdx];
514 } else if (itFloor != floorStrikes_.end()) {
515 size_t floorPriceIdx = std::distance(floorStrikes_.begin(), itFloor);
516 double floorPrice = floorPrices_[floorPriceIdx][tenorIdx];
517 return priceFromPutCallParity(floorPrice, false, atm, strikeGrowth, df);
518 } else {
519 return QuantLib::Null<double>();
520 }
521}
522
523template <class InterpolatorStrike, class InterpolatorTime>
525 const QuantLib::Date& maturity,
526 double price, bool isFloor) const {
527 QuantLib::Date startDate = capFloorStartDate();
528 QuantLib::Calendar cal = calendar();
529 auto bdc = businessDayConvention();
530 auto dc = dayCounter();
531 auto index = index_;
532 auto freq = frequency();
533 auto obsLag = observationLag();
534
535 QuantLib::CPICapFloor capFloor(isFloor ? QuantLib::Option::Put : QuantLib::Option::Call,
536 1.0, // unit nominal, because the price surface returns unit nominal prices
537 capFloorStartDate(), baseCPI(), maturity, calendar(), businessDayConvention(),
538 calendar(), businessDayConvention(), strike,
539 index_, observationLag(),
540 indexIsInterpolated() ? QuantLib::CPI::Linear : QuantLib::CPI::Flat);
541
542 QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine> engine = engine_;
543
544 bool interpolated = indexIsInterpolated();
545 capFloor.setPricingEngine(engine);
546
547 auto targetFunction = [&engine, &cal, &dc, &bdc, &startDate, &obsLag, &freq, &price,
548 &capFloor, &interpolated](const double& guess) {
549 QuantLib::ext::shared_ptr<QuantExt::ConstantCPIVolatility> vol = QuantLib::ext::make_shared<QuantExt::ConstantCPIVolatility>(
550 guess, 0, cal, bdc, dc, obsLag, freq, interpolated, startDate);
551
552 engine->setVolatility(QuantLib::Handle<QuantLib::CPIVolatilitySurface>(vol));
553
554 QuantLib::Real npv = capFloor.NPV();
555 return price - npv;
556 };
557 QuantLib::Brent solver;
558 QuantLib::Real guess = (upperVolBound_ + lowerVolBound_) / 2.0;
559 return solver.solve(targetFunction, solverTolerance_, guess, lowerVolBound_, upperVolBound_);
560}
561
562template <class InterpolatorStrike, class InterpolatorTime>
563QuantLib::Real
565 const QuantLib::Period& obsLag) const {
566 QuantLib::Period lag = obsLag == -1 * QuantLib::Days ? observationLag() : obsLag;
567 QuantLib::Date fixingDate = ZeroInflation::fixingDate(maturity, lag, frequency(), indexIsInterpolated());
568 double forwardCPI = ZeroInflation::cpiFixing(index_, maturity, lag, indexIsInterpolated());
569 double atm = forwardCPI / baseCPI();
570 double ttm =
571 QuantLib::inflationYearFraction(frequency(), indexIsInterpolated(), dayCounter(), baseDate(), fixingDate);
572 return std::pow(atm, 1.0 / ttm) - 1.0;
573}
574} // namespace QuantExt
QuantLib::ext::shared_ptr< PricingEngine > engine_
Definition: cdsoption.cpp:78
Stripped zero inflation volatility structure.
double priceFromPutCallParity(double price, bool isCapPrice, double atm, double strikeGrowth, double df) const
Computes a cap price from a floor price using the put-call parity and vice-versa.
const std::vector< QuantLib::Period > & maturities()
Returns the tenors.
const std::vector< QuantLib::Real > & strikes()
Returns the strikes.
const std::vector< std::vector< bool > > & pricesFailedToConvert() const
std::vector< std::vector< bool > > failedPrices_
QuantLib::Real minStrike() const override
the minimum strike for which the term structure can return vols
QuantLib::ext::shared_ptr< QuantExt::OptionInterpolator2d< InterpolatorStrike, InterpolatorTime > > volSurface_
const QuantLib::Matrix & volData() const
bool chooseFloor(QuantLib::Real strike, QuantLib::Real atmRate) const
double atmGrowth(QuantLib::Period &tenor) const
QuantLib::Real maxStrike() const override
the maximum strike for which the term structure can return vols
const std::vector< std::vector< bool > > & missingValues() const
QuantLib::Date maxDate() const override
maximum date for which the term structure can return vols
QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > index_
double capPrice(double strike, size_t tenorIdx, double atm, double strikeGrowth, double df) const
Returns cap price for strike level (average annual inflation) and maturity index.
double baseCPI() const
CPI fixing on the baseDate of the surface.
CPIPriceVolatilitySurface(PriceQuotePreference type, const QuantLib::Period &observationLag, const QuantLib::Calendar &cal, const QuantLib::BusinessDayConvention &bdc, const QuantLib::DayCounter &dc, const QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > index, QuantLib::Handle< QuantLib::YieldTermStructure > yts, const std::vector< QuantLib::Rate > &cStrikes, const std::vector< QuantLib::Rate > &fStrikes, const std::vector< QuantLib::Period > &cfMaturities, const QuantLib::Matrix &cPrice, const QuantLib::Matrix &fPrice, const QuantLib::ext::shared_ptr< QuantExt::CPICapFloorEngine > &engine, const bool quotedInstrumentsAreInterpolated=false, const QuantLib::Date &capFloorStartDate=QuantLib::Date(), bool ignoreMissingPrices=false, bool lowerStrikeConstExtrap=true, bool upperStrikeConstExtrap=true, const QuantLib::VolatilityType &volType=QuantLib::ShiftedLognormal, const double displacement=0.0, const QuantLib::Real &upperVolBound=CPIPriceVolatilitySurfaceDefaultValues::upperVolBound, const QuantLib::Real &lowerVolBound=CPIPriceVolatilitySurfaceDefaultValues::lowerVolBound, const QuantLib::Real &solverTolerance=CPIPriceVolatilitySurfaceDefaultValues::solverTolerance)
QuantLib::ext::shared_ptr< QuantExt::CPICapFloorEngine > engine_
QuantLib::Handle< QuantLib::YieldTermStructure > yts_
double floorPrice(double strike, size_t tenorIdx, double atm, double strikeGrowth, double df) const
Returns floor price for strike level (average annual inflation) and maturity index.
std::vector< std::vector< bool > > missingPrices_
std::vector< QuantLib::Period > expiries_
QuantLib::Volatility volatilityImpl(QuantLib::Time length, QuantLib::Rate strike) const override
double implyVol(double strike, const QuantLib::Date &maturity, double price, bool isFloor) const
QuantLib::Real atmStrike(const QuantLib::Date &maturity, const QuantLib::Period &obsLag=QuantLib::Period(-1, QuantLib::Days)) const override
double displacement() const
Returns the displacement for lognormal volatilities.
QuantLib::Date capFloorStartDate() const
Constant CPI Volatility Surface.
CPI cap/floor engine using the Black pricing formula and interpreting the volatility data as lognorma...
interpolated correlation term structure
some inflation related utilities.
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Definition: inflation.cpp:183
QuantLib::Rate cpiFixing(const QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > &index, const QuantLib::Date &maturity, const QuantLib::Period &obsLag, bool interpolated)
Computes a CPI fixing giving an zeroIndex, with interpolation if needed.
Definition: inflation.cpp:166
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
zero inflation volatility structure implied from a cpi cap/floor price surface
vector< Real > strikes