Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
swaptionvolatilityconverter.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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
22
23#include <ql/exercise.hpp>
24#include <ql/instruments/makevanillaswap.hpp>
25#include <ql/instruments/swaption.hpp>
26#include <ql/pricingengines/swap/discountingswapengine.hpp>
27#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
28#include <ql/quotes/simplequote.hpp>
29
30#include <boost/make_shared.hpp>
31
32namespace QuantExt {
33
34const Volatility SwaptionVolatilityConverter::minVol_ = 1.0e-7;
35const Volatility SwaptionVolatilityConverter::maxVol_ = 10.0;
37
39 const Date& asof, const QuantLib::ext::shared_ptr<SwaptionVolatilityStructure>& svsIn,
40 const Handle<YieldTermStructure>& discount, const Handle<YieldTermStructure>& shortDiscount,
41 const QuantLib::ext::shared_ptr<SwapConventions>& conventions, const QuantLib::ext::shared_ptr<SwapConventions>& shortConventions,
42 const Period& conventionsTenor, const Period& shortConventionsTenor, const VolatilityType targetType,
43 const Matrix& targetShifts)
44 : asof_(asof), svsIn_(svsIn), discount_(discount), shortDiscount_(shortDiscount), conventions_(conventions),
45 shortConventions_(shortConventions), conventionsTenor_(conventionsTenor),
46 shortConventionsTenor_(shortConventionsTenor), targetType_(targetType), targetShifts_(targetShifts),
47 accuracy_(1.0e-5), maxEvaluations_(100) {
48
49 // Some checks
51}
52
54 const QuantLib::ext::shared_ptr<SwaptionVolatilityStructure>& svsIn,
55 const QuantLib::ext::shared_ptr<SwapIndex>& swapIndex,
56 const QuantLib::ext::shared_ptr<SwapIndex>& shortSwapIndex,
57 const VolatilityType targetType, const Matrix& targetShifts)
58 : asof_(asof), svsIn_(svsIn), discount_(swapIndex->discountingTermStructure()),
59 shortDiscount_(shortSwapIndex->discountingTermStructure()),
60 conventions_(QuantLib::ext::make_shared<SwapConventions>(swapIndex->fixingDays(), swapIndex->fixedLegTenor(),
61 swapIndex->fixingCalendar(), swapIndex->fixedLegConvention(),
62 swapIndex->dayCounter(), swapIndex->iborIndex())),
63 shortConventions_(QuantLib::ext::make_shared<SwapConventions>(
64 shortSwapIndex->fixingDays(), shortSwapIndex->fixedLegTenor(), shortSwapIndex->fixingCalendar(),
65 shortSwapIndex->fixedLegConvention(), shortSwapIndex->dayCounter(), shortSwapIndex->iborIndex())),
66 conventionsTenor_(swapIndex->tenor()), shortConventionsTenor_(shortSwapIndex->tenor()), targetType_(targetType),
67 targetShifts_(targetShifts), accuracy_(1.0e-5), maxEvaluations_(100) {
68
69 // Some checks
70 if (discount_.empty())
71 discount_ = swapIndex->iborIndex()->forwardingTermStructure();
72 if (shortDiscount_.empty())
73 shortDiscount_ = shortSwapIndex->iborIndex()->forwardingTermStructure();
75}
76
78 QL_REQUIRE(svsIn_->referenceDate() == asof_,
79 "SwaptionVolatilityConverter requires the asof date and reference date to align");
80 QL_REQUIRE(!discount_.empty() && discount_->referenceDate() == asof_,
81 "SwaptionVolatilityConverter requires a valid discount curve with reference date equal to asof date");
82 Handle<YieldTermStructure> forwardCurve = conventions_->floatIndex()->forwardingTermStructure();
83 QL_REQUIRE(!forwardCurve.empty() && forwardCurve->referenceDate() == asof_,
84 "SwaptionVolatilityConverter requires a valid forward curve with reference date equal to asof date");
85}
86
87QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> SwaptionVolatilityConverter::convert() const {
88
89 QuantLib::ext::shared_ptr<SwaptionVolatilityDiscrete> svDisc;
90
91 // We expect either the wrapper adding ATM to a cube or a swaption vol discrete
92 // instance (like a matrix, a regular cube).
93
94 vector<Real> strikeSpreads(1, 0.0);
95 QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase, shortSwapIndexBase;
96 QuantLib::ext::shared_ptr<SwaptionVolatilityCube> cube;
97 if (QuantLib::ext::dynamic_pointer_cast<SwaptionVolCubeWithATM>(svsIn_)) {
98 cube = QuantLib::ext::static_pointer_cast<SwaptionVolCubeWithATM>(svsIn_)->cube();
99 }
100 if (QuantLib::ext::dynamic_pointer_cast<SwaptionVolatilityCube>(svsIn_)) {
101 cube = QuantLib::ext::static_pointer_cast<SwaptionVolatilityCube>(svsIn_);
102 }
103 if (cube) {
104 svDisc = cube;
105 strikeSpreads = cube->strikeSpreads();
106 swapIndexBase = cube->swapIndexBase();
107 shortSwapIndexBase = cube->shortSwapIndexBase();
108 } else if (QuantLib::ext::dynamic_pointer_cast<SwaptionVolatilityDiscrete>(svsIn_)) {
109 svDisc = QuantLib::ext::static_pointer_cast<SwaptionVolatilityDiscrete>(svsIn_);
110 } else {
111 QL_FAIL("SwaptionVolatilityConverter: unknown input volatility structure");
112 }
113
114 // Some aspects of original volatility structure that we will need
115 DayCounter dayCounter = svDisc->dayCounter();
116 bool extrapolation = svDisc->allowsExtrapolation();
117 Calendar calendar = svDisc->calendar();
118 BusinessDayConvention bdc = svDisc->businessDayConvention();
119
120 const vector<Date>& optionDates = svDisc->optionDates();
121 const vector<Period>& optionTenors = svDisc->optionTenors();
122 const vector<Period>& swapTenors = svDisc->swapTenors();
123 const vector<Time>& optionTimes = svDisc->optionTimes();
124 const vector<Time>& swapLengths = svDisc->swapLengths();
125 Size nOptionTimes = optionTimes.size();
126 Size nSwapLengths = swapLengths.size();
127 Size nStrikeSpreads = strikeSpreads.size();
128
129 Real targetShift = 0.0;
130
131 // If target type is ShiftedLognormal and shifts are provided, check size
132 if (targetType_ == ShiftedLognormal && !targetShifts_.empty()) {
133 QL_REQUIRE(targetShifts_.rows() == nOptionTimes,
134 "SwaptionVolatilityConverter: number of shift rows does not equal the number of option tenors");
135 QL_REQUIRE(targetShifts_.columns() == nSwapLengths,
136 "SwaptionVolatilityConverter: number of shift columns does not equal the number of swap tenors");
137 }
138
139 // Calculate the converted ATM volatilities
140 Matrix volatilities(nOptionTimes, nSwapLengths);
141 for (Size i = 0; i < nOptionTimes; ++i) {
142 for (Size j = 0; j < nSwapLengths; ++j) {
143 if (!targetShifts_.empty())
144 targetShift = targetShifts_[i][j];
145 volatilities[i][j] = convert(optionDates[i], swapTenors[j], 0.0, dayCounter, targetType_, targetShift);
146 }
147 }
148
149 // Build ATM matrix
150 Handle<SwaptionVolatilityStructure> atmStructure;
151 if (calendar.empty() || optionTenors.empty()) {
152 // Original matrix was created with fixed option dates
153 atmStructure = Handle<SwaptionVolatilityStructure>(
154 QuantLib::ext::make_shared<SwaptionVolatilityMatrix>(asof_, calendar, bdc, optionDates, swapTenors, volatilities,
155 Actual365Fixed(), extrapolation, targetType_, targetShifts_));
156 } else {
157 atmStructure = Handle<SwaptionVolatilityStructure>(QuantLib::ext::shared_ptr<SwaptionVolatilityMatrix>(
158 new SwaptionVolatilityMatrix(asof_, calendar, bdc, optionTenors, swapTenors, volatilities, Actual365Fixed(),
159 extrapolation, targetType_, targetShifts_)));
160 }
161
162 // no cube input => we are done
163 if (!cube)
164 return *atmStructure;
165
166 // convert non-ATM volatilities, note that we use the ATM option tenors and swap tenors here!
167
168 std::vector<std::vector<Handle<Quote> > > volSpreads(nOptionTimes * nSwapLengths,
169 std::vector<Handle<Quote> >(nStrikeSpreads));
170 for (Size k = 0; k < nStrikeSpreads; ++k) {
171 for (Size i = 0; i < nOptionTimes; ++i) {
172 for (Size j = 0; j < nSwapLengths; ++j) {
173 if (!targetShifts_.empty())
174 targetShift = targetShifts_[i][j];
175 Real outVol =
176 convert(optionDates[i], swapTenors[j], strikeSpreads[k], dayCounter, targetType_, targetShift);
177 volSpreads[i * nSwapLengths + j][k] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(outVol));
178 }
179 }
180 }
181
182 // Build and return cube, note that we hardcode flat extrapolation
183 QuantLib::ext::shared_ptr<SwaptionVolatilityCube> cubeOut = QuantLib::ext::shared_ptr<QuantExt::SwaptionVolCube2>(
184 new QuantExt::SwaptionVolCube2(atmStructure, optionTenors, swapTenors, strikeSpreads, volSpreads, swapIndexBase,
185 shortSwapIndexBase, false, true, false));
186 cubeOut->enableExtrapolation(cube->allowsExtrapolation());
187 return QuantLib::ext::make_shared<SwaptionVolCubeWithATM>(cubeOut);
188
189} // namespace QuantExt
190
191Real SwaptionVolatilityConverter::convert(const Date& expiry, const Period& swapTenor, Real strikeSpread,
192 const DayCounter& volDayCounter, VolatilityType outType,
193 Real outShift) const {
194
195 const QuantLib::ext::shared_ptr<SwapConventions> tmpConv =
197 Handle<YieldTermStructure> tmpDiscount = swapTenor <= shortConventionsTenor_ ? shortDiscount_ : discount_;
198
199 // Create the underlying swap with fixed rate = fair rate
200 // We rely on the fact that MakeVanillaSwap sets the fixed rate to the fair rate if it is left null in the ctor
201 Date effectiveDate = tmpConv->fixedCalendar().advance(expiry, tmpConv->settlementDays(), Days);
202 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<DiscountingSwapEngine>(tmpDiscount);
203 QuantLib::ext::shared_ptr<VanillaSwap> swap = MakeVanillaSwap(swapTenor, tmpConv->floatIndex())
204 .withType(strikeSpread < 0.0 ? VanillaSwap::Receiver : VanillaSwap::Payer)
205 .withEffectiveDate(effectiveDate)
206 .withFixedLegCalendar(tmpConv->fixedCalendar())
207 .withFixedLegDayCount(tmpConv->fixedDayCounter())
208 .withFixedLegTenor(tmpConv->fixedTenor())
209 .withFixedLegConvention(tmpConv->fixedConvention())
210 .withFixedLegTerminationDateConvention(tmpConv->fixedConvention())
211 .withFloatingLegSpread(0.0)
212 .withPricingEngine(engine);
213 // we need this also for non-atm swaps
214 Rate atmRate = swap->fairRate(), strike;
215 if (!close_enough(strikeSpread, 0.0)) {
216 strike = atmRate + strikeSpread;
217 swap = MakeVanillaSwap(swapTenor, tmpConv->floatIndex(), strike)
218 .withEffectiveDate(effectiveDate)
219 .withFixedLegTenor(tmpConv->fixedTenor())
220 .withFixedLegDayCount(tmpConv->fixedDayCounter())
221 .withFloatingLegSpread(0.0)
222 .withPricingEngine(engine);
223 } else {
224 strike = atmRate;
225 }
226
227 Real inShift = svsIn_->shift(expiry, swapTenor);
228 VolatilityType inType = svsIn_->volatilityType();
229
230 // if strike is invalid w.r.t. the given input or output vol types, return zero vol
231 // (for a lognormal cube e.g. it is common that some effective strikes are negative)
232 Real inMinStrike = inType == ShiftedLognormal ? -inShift : -QL_MAX_REAL;
233 Real outMinStrike = outType == ShiftedLognormal ? -outShift : -QL_MAX_REAL;
234 if (strike < inMinStrike || strike < outMinStrike)
235 return 0.0;
236
237 Real inVol = svsIn_->volatility(expiry, swapTenor, strike);
238
239 // Create the swaption
240 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiry);
241 QuantLib::ext::shared_ptr<Swaption> swaption = QuantLib::ext::make_shared<Swaption>(swap, exercise, Settlement::Physical);
242
243 // Price the swaption with the input volatility
244 QuantLib::ext::shared_ptr<PricingEngine> swaptionEngine;
245 if (inType == ShiftedLognormal) {
246 swaptionEngine = QuantLib::ext::make_shared<BlackSwaptionEngine>(discount_, inVol, volDayCounter, inShift);
247 } else {
248 swaptionEngine = QuantLib::ext::make_shared<BachelierSwaptionEngine>(discount_, inVol, volDayCounter);
249 }
250 swaption->setPricingEngine(swaptionEngine);
251
252 // zero might be the actual implied vol (if we e.g. convert from LN to N with a strike near zero),
253 // but the Swaption::impliedVolatility function will not find this, so we check for this case
254 // explicitly here
255 Real vega = swaption->result<Real>("vega");
256 if (vega < minVega_)
257 return 0.0;
258
259 Volatility impliedVol = 0.0;
260 try {
261 Real npv = swaption->NPV();
262
263 // Calculate guess for implied volatility solver
264 Real guess = 0.0;
265 if (outType == ShiftedLognormal) {
266 QL_REQUIRE(atmRate + outShift > 0.0, "SwaptionVolatilityConverter: ATM rate + shift must be > 0.0");
267 if (inType == Normal)
268 guess = inVol / (atmRate + outShift);
269 else
270 guess = inVol * (atmRate + inShift) / (atmRate + outShift);
271 } else {
272 if (inType == Normal)
273 guess = inVol;
274 else
275 guess = inVol * (atmRate + inShift);
276 }
277
278 // Note: In implying the volatility the volatility day counter is hardcoded to Actual365Fixed
279 impliedVol = swaption->impliedVolatility(npv, discount_, guess, accuracy_, maxEvaluations_, minVol_, maxVol_,
280 outType, outShift);
281
282 } catch (std::exception& e) {
283 // couldn't find implied volatility
284 QL_FAIL("SwaptionVolatilityConverter: volatility conversion failed while trying to convert volatility"
285 " for expiry "
286 << expiry << " and swap tenor " << swapTenor << ". Error: " << e.what());
287 }
288
289 return impliedVol;
290}
291} // namespace QuantExt
SwaptionVolatilityConverter(const Date &asof, const QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > &svsIn, const Handle< YieldTermStructure > &discount, const Handle< YieldTermStructure > &shortDiscount, const QuantLib::ext::shared_ptr< SwapConventions > &conventions, const QuantLib::ext::shared_ptr< SwapConventions > &shortConventions, const Period &conventionsTenor, const Period &shortConventionsTenor, const VolatilityType targetType, const Matrix &targetShifts=Matrix())
Construct from SwapConventions.
const QuantLib::ext::shared_ptr< SwapConventions > conventions_
QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > convert() const
Method that returns the converted SwaptionVolatilityStructure
const QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > svsIn_
const QuantLib::ext::shared_ptr< SwapConventions > shortConventions_
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Convert swaption volatilities from one type to another.
Swaption volatility cube, fit-later-interpolate-early approach.
Wrapper class for a SwaptionVolatilityCube that easily and efficiently exposes ATM vols.