Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
capfloorvolcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ)
4 All rights reserved.
5
6 This file is part of ORE, a free-software/open-source library
7 for transparent pricing and risk analysis - http://opensourcerisk.org
8
9 ORE is free software: you can redistribute it and/or modify it
10 under the terms of the Modified BSD License. You should have received a
11 copy of the license along with this program.
12 The license is also available online at <http://opensourcerisk.org>
13
14 This program is distributed on the basis that it will form a useful
15 contribution to risk analytics and model standardisation, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
25
40#include <qle/termstructures/sabrstrippedoptionletadapter.hpp>
43
44#include <ql/math/comparison.hpp>
45#include <ql/math/matrix.hpp>
46#include <ql/termstructures/volatility/capfloor/capfloortermvolcurve.hpp>
47#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
48
49using namespace QuantLib;
50using namespace QuantExt;
51using namespace std;
52
56
57namespace ore {
58namespace data {
59
60// Currently, only two possibilities for variable InterpolateOn: TermVolatilities and OptionletVolatilities
61// Here, we convert the value to a bool for use in building the structures. May need to broaden if we add more.
62// If InputType is OptionletVolatilities, InterpolateOn node will be ignored.
64 QL_REQUIRE(config.interpolateOn() == "TermVolatilities" || config.interpolateOn() == "OptionletVolatilities",
65 "Expected InterpolateOn to be one of TermVolatilities or OptionletVolatilities");
66 return config.interpolateOn() == "OptionletVolatilities";
67}
68
70 const Date& asof, const CapFloorVolatilityCurveSpec& spec, const Loader& loader,
71 const CurveConfigurations& curveConfigs, QuantLib::ext::shared_ptr<IborIndex> iborIndex,
72 Handle<YieldTermStructure> discountCurve, const QuantLib::ext::shared_ptr<IborIndex> sourceIndex,
73 const QuantLib::ext::shared_ptr<IborIndex> targetIndex,
74 const std::map<std::string,
75 std::pair<QuantLib::ext::shared_ptr<ore::data::CapFloorVolCurve>, std::pair<std::string, QuantLib::Period>>>&
76 requiredCapFloorVolCurves,
77 const bool buildCalibrationInfo)
78 : spec_(spec) {
79
80 try {
81 // The configuration
82 const QuantLib::ext::shared_ptr<CapFloorVolatilityCurveConfig>& config =
83 curveConfigs.capFloorVolCurveConfig(spec_.curveConfigID());
84
85 if (!config->proxySourceCurveId().empty()) {
86 // handle proxy vol surfaces
87 buildProxyCurve(*config, sourceIndex, targetIndex, requiredCapFloorVolCurves);
88 } else {
89 QL_REQUIRE(QuantLib::ext::dynamic_pointer_cast<BMAIndexWrapper>(iborIndex) == nullptr,
90 "CapFloorVolCurve: BMA/SIFMA index in '"
91 << spec_.curveConfigID()
92 << " not allowed - vol surfaces for SIFMA can only be proxied from Ibor / OIS");
93
94 // Read the shift early if the configured volatility type is shifted lognormal
95 Real shift = 0.0;
96 if (config->volatilityType() == CfgVolType::ShiftedLognormal) {
97 shift = shiftQuote(asof, *config, loader);
98 }
99
100 // There are three possible cap floor configurations
101 if (config->type() == CfgType::TermAtm) {
102 termAtmOptCurve(asof, *config, loader, iborIndex, discountCurve, shift);
103 } else if (config->type() == CfgType::TermSurface || config->type() == CfgType::TermSurfaceWithAtm) {
104 termOptSurface(asof, *config, loader, iborIndex, discountCurve, shift);
105 } else if (config->type() == CfgType::OptionletSurface ||
106 config->type() == CfgType::OptionletSurfaceWithAtm) {
107 optOptSurface(asof, *config, loader, iborIndex, discountCurve, shift);
108 } else if (config->type() == CfgType::OptionletAtm) {
109 optAtmOptCurve(asof, *config, loader, iborIndex, discountCurve, shift);
110 } else {
111 QL_FAIL("Unexpected type (" << static_cast<int>(config->type()) << ") for cap floor config "
112 << config->curveID());
113 }
114 // Turn on or off extrapolation
115 capletVol_->enableExtrapolation(config->extrapolate());
116 }
117
118 // Build calibration info
119 if (buildCalibrationInfo) {
120 this->buildCalibrationInfo(asof, curveConfigs, config, iborIndex);
121 }
122
123 } catch (exception& e) {
124 QL_FAIL("cap/floor vol curve building failed :" << e.what());
125 } catch (...) {
126 QL_FAIL("cap/floor vol curve building failed: unknown error");
127 }
128
129 // force bootstrap so that errors are thrown during the build, not later
130 capletVol_->volatility(QL_EPSILON, capletVol_->minStrike());
131}
132
134 const CapFloorVolatilityCurveConfig& config, const QuantLib::ext::shared_ptr<IborIndex>& sourceIndex,
135 const QuantLib::ext::shared_ptr<IborIndex>& targetIndex,
136 const std::map<std::string,
137 std::pair<QuantLib::ext::shared_ptr<ore::data::CapFloorVolCurve>, std::pair<std::string, QuantLib::Period>>>&
138 requiredCapFloorVolCurves) {
139
140 auto sourceVol = requiredCapFloorVolCurves.find(config.proxySourceCurveId());
141 QL_REQUIRE(sourceVol != requiredCapFloorVolCurves.end(),
142 "CapFloorVolCurve::buildProxyCurve(): required source cap vol curve '" << config.proxySourceCurveId()
143 << "' not found.");
144
145 capletVol_ = QuantLib::ext::make_shared<ProxyOptionletVolatility>(
146 Handle<OptionletVolatilityStructure>(sourceVol->second.first->capletVolStructure()), sourceIndex, targetIndex,
148}
149
150void CapFloorVolCurve::termAtmOptCurve(const Date& asof, CapFloorVolatilityCurveConfig& config, const Loader& loader,
151 QuantLib::ext::shared_ptr<IborIndex> index, Handle<YieldTermStructure> discountCurve,
152 Real shift) {
153
154 // Get the ATM cap floor term vol curve
155 QuantLib::ext::shared_ptr<QuantExt::CapFloorTermVolCurve> cftvc = atmCurve(asof, config, loader);
156
157 // Hardcode some values. Can add them to the CapFloorVolatilityCurveConfig later if needed.
158 bool flatFirstPeriod = true;
159 VolatilityType optVolType = Normal;
160 Real optDisplacement = 0.0;
161
162 // Get configuration values for bootstrap
163 Real accuracy = config.bootstrapConfig().accuracy();
164 Real globalAccuracy = config.bootstrapConfig().globalAccuracy();
165 bool dontThrow = config.bootstrapConfig().dontThrow();
166 Size maxAttempts = config.bootstrapConfig().maxAttempts();
167 Real maxFactor = config.bootstrapConfig().maxFactor();
168 Real minFactor = config.bootstrapConfig().minFactor();
169 Size dontThrowSteps = config.bootstrapConfig().dontThrowSteps();
170
171 // On optionlets is the newly added interpolation approach whereas on term volatilities is legacy
172 bool onOpt = interpOnOpt(config);
173 if (onOpt) {
174 // This is not pretty but can't think of a better way (with template functions and or classes)
175 // Note: second template argument in StrippedOptionletAdapter doesn't matter so just use Linear here.
176 if (config.timeInterpolation() == "Linear") {
177 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<Linear>> tmp =
178 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Linear>>(
179 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
180 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, Linear(),
183 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
184 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(
185 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
186 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
187 tmp->volatilityType(), tmp->displacement()));
188 } else if (config.timeInterpolation() == "LinearFlat") {
189 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<LinearFlat>> tmp =
190 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat>>(
191 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
192 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, LinearFlat(),
195 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
196 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Linear>>(
197 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
198 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
199 tmp->volatilityType(), tmp->displacement()));
200 } else if (config.timeInterpolation() == "BackwardFlat") {
201 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<BackwardFlat>> tmp =
202 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<BackwardFlat>>(
203 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
204 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, BackwardFlat(),
207 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
208 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Linear>>(
209 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
210 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
211 tmp->volatilityType(), tmp->displacement()));
212 } else if (config.timeInterpolation() == "Cubic") {
213 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<Cubic>> tmp =
214 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Cubic>>(
215 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
216 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, Cubic(),
219 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
220 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Linear>>(
221 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
222 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
223 tmp->volatilityType(), tmp->displacement()));
224 } else if (config.timeInterpolation() == "CubicFlat") {
225 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<CubicFlat>> tmp =
226 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<CubicFlat>>(
227 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
228 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, CubicFlat(),
231 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
232 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Linear>>(
233 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
234 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
235 tmp->volatilityType(), tmp->displacement()));
236 } else {
237 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected time interpolation "
238 << config.timeInterpolation());
239 }
240 } else {
241 // Legacy method where we interpolate on the term volatilities.
242 // We don't need time interpolation in this instance - we just use the term volatility interpolation.
243 if (config.interpolationMethod() == CftvsInterp::BicubicSpline) {
244 if (config.flatExtrapolation()) {
245 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<CubicFlat>> tmp =
246 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<CubicFlat>>(
247 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
248 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, CubicFlat(),
251 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
252 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Linear>>(
253 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
254 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
255 tmp->volatilityType(), tmp->displacement()));
256 } else {
257 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<Cubic>> tmp =
258 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Cubic>>(
259 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
260 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, Cubic(),
263 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
264 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Linear>>(
265 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
266 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
267 tmp->volatilityType(), tmp->displacement()));
268 }
269 } else if (config.interpolationMethod() == CftvsInterp::Bilinear) {
270 if (config.flatExtrapolation()) {
271 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<LinearFlat>> tmp =
272 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat>>(
273 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
274 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt,
275 LinearFlat(),
278 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
279 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Linear>>(
280 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
281 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
282 tmp->volatilityType(), tmp->displacement()));
283 } else {
284 QuantLib::ext::shared_ptr<PiecewiseAtmOptionletCurve<Linear>> tmp =
285 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Linear>>(
286 config.settleDays(), cftvc, index, discountCurve, flatFirstPeriod,
287 volatilityType(config.volatilityType()), shift, optVolType, optDisplacement, onOpt, Linear(),
290 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps));
291 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(
292 asof, transform(asof, tmp->curve()->dates(), tmp->curve()->volatilities(), tmp->settlementDays(),
293 tmp->calendar(), tmp->businessDayConvention(), index, tmp->dayCounter(),
294 tmp->volatilityType(), tmp->displacement()));
295 }
296 } else {
297 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected interpolation method "
298 << static_cast<int>(config.interpolationMethod()));
299 }
300 }
301}
302
303void CapFloorVolCurve::termOptSurface(const Date& asof, CapFloorVolatilityCurveConfig& config, const Loader& loader,
304 QuantLib::ext::shared_ptr<IborIndex> index, Handle<YieldTermStructure> discountCurve,
305 Real shift) {
306
307 // Get the cap floor term vol surface
308 QuantLib::ext::shared_ptr<QuantExt::CapFloorTermVolSurface> cftvs = capSurface(asof, config, loader);
309
310 // Get the ATM cap floor term vol curve if we are including an ATM curve
311 bool includeAtm = config.includeAtm();
312 Handle<QuantExt::CapFloorTermVolCurve> cftvc;
313 if (includeAtm) {
314 cftvc = Handle<QuantExt::CapFloorTermVolCurve>(atmCurve(asof, config, loader));
315 }
316
317 // Hardcode some values. Can add them to the CapFloorVolatilityCurveConfig later if needed.
318 bool flatFirstPeriod = true;
319 VolatilityType optVolType = Normal;
320 Real optDisplacement = 0.0;
321
322 // Get configuration values for bootstrap
323 Real accuracy = config.bootstrapConfig().accuracy();
324 Real globalAccuracy = config.bootstrapConfig().globalAccuracy();
325 bool dontThrow = config.bootstrapConfig().dontThrow();
326 Size maxAttempts = config.bootstrapConfig().maxAttempts();
327 Real maxFactor = config.bootstrapConfig().maxFactor();
328 Real minFactor = config.bootstrapConfig().minFactor();
329 Size dontThrowSteps = config.bootstrapConfig().dontThrowSteps();
330
331 // Get configuration values for parametric smile
332 std::vector<std::vector<std::pair<Real, bool>>> initialModelParameters;
333 Size maxCalibrationAttempts = 10;
334 Real exitEarlyErrorThreshold = 0.005;
335 Real maxAcceptableError = 0.05;
336 if (config.parametricSmileConfiguration()) {
337 auto alpha = config.parametricSmileConfiguration()->parameter("alpha");
338 auto beta = config.parametricSmileConfiguration()->parameter("beta");
339 auto nu = config.parametricSmileConfiguration()->parameter("nu");
340 auto rho = config.parametricSmileConfiguration()->parameter("rho");
341 QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
342 alpha.initialValue.size() == nu.initialValue.size() &&
343 alpha.initialValue.size() == rho.initialValue.size(),
344 "CapFloorVolCurve: parametric smile config: alpha size ("
345 << alpha.initialValue.size() << ") beta size (" << beta.initialValue.size() << ") nu size ("
346 << nu.initialValue.size() << ") rho size (" << rho.initialValue.size() << ") must match");
347 for (Size i = 0; i < alpha.initialValue.size(); ++i) {
348 initialModelParameters.push_back(std::vector<std::pair<Real, bool>>());
349 initialModelParameters.back().push_back(std::make_pair(alpha.initialValue[i], alpha.isFixed));
350 initialModelParameters.back().push_back(std::make_pair(beta.initialValue[i], beta.isFixed));
351 initialModelParameters.back().push_back(std::make_pair(nu.initialValue[i], nu.isFixed));
352 initialModelParameters.back().push_back(std::make_pair(rho.initialValue[i], rho.isFixed));
353 }
354 maxCalibrationAttempts = config.parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
355 exitEarlyErrorThreshold = config.parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
356 maxAcceptableError = config.parametricSmileConfiguration()->calibration().maxAcceptableError;
357 }
358
359 // On optionlets is the newly added interpolation approach whereas on term volatilities is legacy
360 QuantLib::ext::shared_ptr<QuantExt::OptionletStripper> optionletStripper;
361 VolatilityType volType = volatilityType(config.volatilityType());
362 bool onOpt = interpOnOpt(config);
363 SabrParametricVolatility::ModelVariant sabrModelVariant;
364 if (onOpt) {
365 // This is not pretty but can't think of a better way (with template functions and or classes)
366 if (config.timeInterpolation() == "Linear") {
367 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<Linear>>(
368 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
369 Linear(),
372 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
373 config.rateComputationPeriod(), config.onCapSettlementDays());
374 if (config.strikeInterpolation() == "Linear") {
375 if (includeAtm) {
376 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, Linear>>(
377 optionletStripper, cftvc, discountCurve, volType, shift);
378 }
379 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(
380 asof, transform(*optionletStripper));
381 } else if (config.strikeInterpolation() == "LinearFlat") {
382 if (includeAtm) {
383 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, LinearFlat>>(
384 optionletStripper, cftvc, discountCurve, volType, shift);
385 }
386 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, LinearFlat>>(
387 asof, transform(*optionletStripper));
388 } else if (config.strikeInterpolation() == "Cubic") {
389 if (includeAtm) {
390 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, Cubic>>(
391 optionletStripper, cftvc, discountCurve, volType, shift);
392 }
393 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Cubic>>(
394 asof, transform(*optionletStripper));
395 } else if (config.strikeInterpolation() == "CubicFlat") {
396 if (includeAtm) {
397 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, CubicFlat>>(
398 optionletStripper, cftvc, discountCurve, volType, shift);
399 }
400 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, CubicFlat>>(
401 asof, transform(*optionletStripper));
402 } else if (tryParse(
403 config.strikeInterpolation(), sabrModelVariant,
404 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
405 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
406 if (includeAtm) {
407 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, Linear>>(
408 optionletStripper, cftvc, discountCurve, volType, shift);
409 }
410 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<Linear>>(
411 asof, transform(*optionletStripper), sabrModelVariant, Linear(), boost::none,
412 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
413 } else {
414 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected strike interpolation "
415 << config.strikeInterpolation());
416 }
417 } else if (config.timeInterpolation() == "LinearFlat") {
418 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<LinearFlat>>(
419 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
420 LinearFlat(),
423 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
424 config.rateComputationPeriod(), config.onCapSettlementDays());
425 if (config.strikeInterpolation() == "Linear") {
426 if (includeAtm) {
427 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, Linear>>(
428 optionletStripper, cftvc, discountCurve, volType, shift);
429 }
430 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Linear>>(
431 asof, transform(*optionletStripper));
432 } else if (config.strikeInterpolation() == "LinearFlat") {
433 if (includeAtm) {
434 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, LinearFlat>>(
435 optionletStripper, cftvc, discountCurve, volType, shift);
436 }
437 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, LinearFlat>>(
438 asof, transform(*optionletStripper));
439 } else if (config.strikeInterpolation() == "Cubic") {
440 if (includeAtm) {
441 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, Cubic>>(
442 optionletStripper, cftvc, discountCurve, volType, shift);
443 }
444 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Cubic>>(
445 asof, transform(*optionletStripper));
446 } else if (config.strikeInterpolation() == "CubicFlat") {
447 if (includeAtm) {
448 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, CubicFlat>>(
449 optionletStripper, cftvc, discountCurve, volType, shift);
450 }
451 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, CubicFlat>>(
452 asof, transform(*optionletStripper));
453 } else if (tryParse(
454 config.strikeInterpolation(), sabrModelVariant,
455 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
456 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
457 if (includeAtm) {
458 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, Linear>>(
459 optionletStripper, cftvc, discountCurve, volType, shift);
460 }
461 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<LinearFlat>>(
462 asof, transform(*optionletStripper), sabrModelVariant, LinearFlat(), boost::none,
463 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
464 } else {
465 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected strike interpolation "
466 << config.strikeInterpolation());
467 }
468 } else if (config.timeInterpolation() == "BackwardFlat") {
469 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<BackwardFlat>>(
470 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
471 BackwardFlat(),
474 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
475 config.rateComputationPeriod(), config.onCapSettlementDays());
476 if (config.strikeInterpolation() == "Linear") {
477 if (includeAtm) {
478 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<BackwardFlat, Linear>>(
479 optionletStripper, cftvc, discountCurve, volType, shift);
480 }
481 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Linear>>(
482 asof, transform(*optionletStripper));
483 } else if (config.strikeInterpolation() == "LinearFlat") {
484 if (includeAtm) {
485 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<BackwardFlat, LinearFlat>>(
486 optionletStripper, cftvc, discountCurve, volType, shift);
487 }
488 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, LinearFlat>>(
489 asof, transform(*optionletStripper));
490 } else if (config.strikeInterpolation() == "Cubic") {
491 if (includeAtm) {
492 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<BackwardFlat, Cubic>>(
493 optionletStripper, cftvc, discountCurve, volType, shift);
494 }
495 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Cubic>>(
496 asof, transform(*optionletStripper));
497 } else if (config.strikeInterpolation() == "CubicFlat") {
498 if (includeAtm) {
499 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<BackwardFlat, CubicFlat>>(
500 optionletStripper, cftvc, discountCurve, volType, shift);
501 }
502 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, CubicFlat>>(
503 asof, transform(*optionletStripper));
504 } else if (tryParse(
505 config.strikeInterpolation(), sabrModelVariant,
506 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
507 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
508 if (includeAtm) {
509 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<BackwardFlat, Linear>>(
510 optionletStripper, cftvc, discountCurve, volType, shift);
511 }
512 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<Linear>>(
513 asof, transform(*optionletStripper), sabrModelVariant, Linear(), boost::none,
514 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
515 } else {
516 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected strike interpolation "
517 << config.strikeInterpolation());
518 }
519 } else if (config.timeInterpolation() == "Cubic") {
520 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<Cubic>>(
521 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
522 Cubic(),
525 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
526 config.rateComputationPeriod(), config.onCapSettlementDays());
527 if (config.strikeInterpolation() == "Linear") {
528 if (includeAtm) {
529 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, Linear>>(
530 optionletStripper, cftvc, discountCurve, volType, shift);
531 }
532 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Linear>>(
533 asof, transform(*optionletStripper));
534 } else if (config.strikeInterpolation() == "LinearFlat") {
535 if (includeAtm) {
536 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, LinearFlat>>(
537 optionletStripper, cftvc, discountCurve, volType, shift);
538 }
539 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, LinearFlat>>(
540 asof, transform(*optionletStripper));
541 } else if (config.strikeInterpolation() == "Cubic") {
542 if (includeAtm) {
543 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, Cubic>>(
544 optionletStripper, cftvc, discountCurve, volType, shift);
545 }
546 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Cubic>>(
547 asof, transform(*optionletStripper));
548 } else if (config.strikeInterpolation() == "CubicFlat") {
549 if (includeAtm) {
550 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, CubicFlat>>(
551 optionletStripper, cftvc, discountCurve, volType, shift);
552 }
553 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, CubicFlat>>(
554 asof, transform(*optionletStripper));
555 } else if (tryParse(
556 config.strikeInterpolation(), sabrModelVariant,
557 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
558 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
559 if (includeAtm) {
560 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, Linear>>(
561 optionletStripper, cftvc, discountCurve, volType, shift);
562 }
563 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<Cubic>>(
564 asof, transform(*optionletStripper), sabrModelVariant, Cubic(), boost::none, initialModelParameters,
565 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
566 }
567 {
568 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected strike interpolation "
569 << config.strikeInterpolation());
570 }
571 } else if (config.timeInterpolation() == "CubicFlat") {
572 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<CubicFlat>>(
573 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
574 CubicFlat(),
577 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
578 config.rateComputationPeriod(), config.onCapSettlementDays());
579 if (config.strikeInterpolation() == "Linear") {
580 if (includeAtm) {
581 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, Linear>>(
582 optionletStripper, cftvc, discountCurve, volType, shift);
583 }
584 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Linear>>(
585 asof, transform(*optionletStripper));
586 } else if (config.strikeInterpolation() == "LinearFlat") {
587 if (includeAtm) {
588 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, LinearFlat>>(
589 optionletStripper, cftvc, discountCurve, volType, shift);
590 }
591 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, LinearFlat>>(
592 asof, transform(*optionletStripper));
593 } else if (config.strikeInterpolation() == "Cubic") {
594 if (includeAtm) {
595 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, Cubic>>(
596 optionletStripper, cftvc, discountCurve, volType, shift);
597 }
598 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Cubic>>(
599 asof, transform(*optionletStripper));
600 } else if (config.strikeInterpolation() == "CubicFlat") {
601 if (includeAtm) {
602 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, CubicFlat>>(
603 optionletStripper, cftvc, discountCurve, volType, shift);
604 }
605 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, CubicFlat>>(
606 asof, transform(*optionletStripper));
607 } else if (tryParse(
608 config.strikeInterpolation(), sabrModelVariant,
609 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
610 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
611 if (includeAtm) {
612 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, Linear>>(
613 optionletStripper, cftvc, discountCurve, volType, shift);
614 }
615 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<CubicFlat>>(
616 asof, transform(*optionletStripper), sabrModelVariant, CubicFlat(), boost::none,
617 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
618 } else {
619 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected strike interpolation "
620 << config.strikeInterpolation());
621 }
622 } else {
623 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected time interpolation "
624 << config.timeInterpolation());
625 }
626 } else {
627 // Legacy method where we interpolate on the term volatilities.
628 // We don't need time interpolation in this instance - we just use the term volatility interpolation.
629 if (config.interpolationMethod() == CftvsInterp::BicubicSpline) {
630 if (config.flatExtrapolation()) {
631 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<CubicFlat>>(
632 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
633 CubicFlat(),
636 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
637 config.rateComputationPeriod(), config.onCapSettlementDays());
638 if (includeAtm) {
639 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<CubicFlat, CubicFlat>>(
640 optionletStripper, cftvc, discountCurve, volType, shift);
641 }
642 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, CubicFlat>>(
643 asof, transform(*optionletStripper));
644 } else {
645 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<Cubic>>(
646 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
647 Cubic(),
650 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
651 config.rateComputationPeriod(), config.onCapSettlementDays());
652 if (includeAtm) {
653 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Cubic, Cubic>>(
654 optionletStripper, cftvc, discountCurve, volType, shift);
655 }
656 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Cubic>>(
657 asof, transform(*optionletStripper));
658 }
659 } else if (config.interpolationMethod() == CftvsInterp::Bilinear) {
660 if (config.flatExtrapolation()) {
661 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<LinearFlat>>(
662 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
663 LinearFlat(),
666 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
667 config.rateComputationPeriod(), config.onCapSettlementDays());
668 if (includeAtm) {
669 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<LinearFlat, LinearFlat>>(
670 optionletStripper, cftvc, discountCurve, volType, shift);
671 }
672 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, LinearFlat>>(
673 asof, transform(*optionletStripper));
674 } else {
675 optionletStripper = QuantLib::ext::make_shared<PiecewiseOptionletStripper<Linear>>(
676 cftvs, index, discountCurve, flatFirstPeriod, volType, shift, optVolType, optDisplacement, onOpt,
677 Linear(),
680 accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor, minFactor, dontThrowSteps),
681 config.rateComputationPeriod(), config.onCapSettlementDays());
682 if (includeAtm) {
683 optionletStripper = QuantLib::ext::make_shared<OptionletStripperWithAtm<Linear, Linear>>(
684 optionletStripper, cftvc, discountCurve, volType, shift);
685 }
686 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(
687 asof, transform(*optionletStripper));
688 }
689 } else {
690 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected interpolation method "
691 << static_cast<int>(config.interpolationMethod()));
692 }
693 }
694}
695
696void CapFloorVolCurve::optAtmOptCurve(const Date& asof, CapFloorVolatilityCurveConfig& config, const Loader& loader,
697 QuantLib::ext::shared_ptr<IborIndex> index, Handle<YieldTermStructure> discountCurve,
698 Real shift) {
699 QL_REQUIRE(config.optionalQuotes() == false, "Optional quotes for optionlet volatilities are not supported.");
700 // Load optionlet atm vol curve
701 bool tenorRelevant = false;
702 bool wildcardTenor = false;
703
704 Period tenor = parsePeriod(config.indexTenor()); // 1D, 1M, 3M, 6M, 12M
705 string currency = config.currency();
706 vector<Period> configTenors;
707 std::map<Period, Real> capfloorVols;
708 if (config.atmTenors()[0] == "*") {
709 wildcardTenor = true;
710 } else {
711 configTenors = parseVectorOfValues<Period>(config.atmTenors(), &parsePeriod);
712 }
713 std::ostringstream ss;
714 ss << MarketDatum::InstrumentType::CAPFLOOR << "/" << config.quoteType() << "/" << currency << "/";
715 if (config.quoteIncludesIndexName())
716 ss << config.index() << "/";
717 ss << "*";
718 Wildcard w(ss.str());
719 for (const auto& md : loader.get(w, asof)) {
720 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
721 QuantLib::ext::shared_ptr<CapFloorQuote> cfq = QuantLib::ext::dynamic_pointer_cast<CapFloorQuote>(md);
722 QL_REQUIRE(cfq, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CapFloorQuote");
723 QL_REQUIRE(cfq->ccy() == currency,
724 "CapFloorQuote ccy '" << cfq->ccy() << "' <> config ccy '" << currency << "'");
725 if (cfq->underlying() == tenor && cfq->atm()) {
726 auto findTenor = std::find(configTenors.begin(), configTenors.end(), cfq->term());
727 if (wildcardTenor) {
728 tenorRelevant = true;
729 } else {
730 tenorRelevant = findTenor != configTenors.end();
731 }
732 if (tenorRelevant) {
733 // Check duplicate quotes
734 auto k = capfloorVols.find(cfq->term());
735 if (k != capfloorVols.end()) {
736 if (config.quoteIncludesIndexName())
737 QL_FAIL("Duplicate optionlet atm vol quote in config "
738 << config.curveID() << ", with underlying tenor " << tenor << " ,currency "
739 << currency << " and index " << config.index() << ", for tenor " << cfq->term());
740 else
741 QL_FAIL("Duplicate optionlet atm vol quote in config "
742 << config.curveID() << ", with underlying tenor " << tenor << " and currency "
743 << currency << ", for tenor " << cfq->term());
744 }
745 // Store the vols into a map to sort the wildcard tenors
746 capfloorVols[cfq->term()]= cfq->quote()->value();
747 }
748 }
749 }
750 vector<Real> vols_tenor;
751 auto tenor_itr = configTenors.begin();
752 for (auto const& vols_outer : capfloorVols) {
753 // Check if all tenor is available
754 if (wildcardTenor) {
755 configTenors.push_back(vols_outer.first);
756 } else {
757 QL_REQUIRE(*tenor_itr == vols_outer.first, "Quote with tenor " << *tenor_itr
758 << " not loaded for optionlet vol config "
759 << config.curveID());
760 tenor_itr++;
761 }
762 vols_tenor.push_back(vols_outer.second);
763 }
764 // Find the fixing date of the term quotes
765 vector<Date> fixingDates = populateFixingDates(asof, config, index, configTenors);
766 DLOG("Found " << capfloorVols.size() << " quotes for optionlet vol surface " << config.curveID());
767 // This is not pretty but can't think of a better way (with template functions and or classes)
768 // Note: second template argument in StrippedOptionletAdapter doesn't matter so just use Linear here.
769 if (config.timeInterpolation() == "Linear") {
770 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(
771 asof, transform(asof, fixingDates, vols_tenor, config.settleDays(),
772 config.calendar(), config.businessDayConvention(), index, config.dayCounter(),
773 volatilityType(config.volatilityType()), shift));
774 } else if (config.timeInterpolation() == "LinearFlat") {
775 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Linear>>(
776 asof, transform(asof, fixingDates, vols_tenor, config.settleDays(), config.calendar(),
777 config.businessDayConvention(), index, config.dayCounter(),
778 volatilityType(config.volatilityType()), shift));
779 } else if (config.timeInterpolation() == "BackwardFlat") {
780 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Linear>>(
781 asof, transform(asof, fixingDates, vols_tenor, config.settleDays(), config.calendar(),
782 config.businessDayConvention(), index, config.dayCounter(),
783 volatilityType(config.volatilityType()), shift));
784 } else if (config.timeInterpolation() == "Cubic") {
785 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Linear>>(
786 asof, transform(asof, fixingDates, vols_tenor, config.settleDays(), config.calendar(),
787 config.businessDayConvention(), index, config.dayCounter(),
788 volatilityType(config.volatilityType()), shift));
789 } else if (config.timeInterpolation() == "CubicFlat") {
790 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Linear>>(
791 asof, transform(asof, fixingDates, vols_tenor, config.settleDays(), config.calendar(),
792 config.businessDayConvention(), index, config.dayCounter(),
793 volatilityType(config.volatilityType()), shift));
794 } else {
795 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected time interpolation "
796 << config.timeInterpolation());
797 }
798}
799
800void CapFloorVolCurve::optOptSurface(const QuantLib::Date& asof, CapFloorVolatilityCurveConfig& config,
801 const Loader& loader, QuantLib::ext::shared_ptr<QuantLib::IborIndex> iborIndex,
802 QuantLib::Handle<QuantLib::YieldTermStructure> discountCurve,
803 QuantLib::Real shift) {
804 QL_REQUIRE(config.optionalQuotes() == false, "Optional quotes for optionlet volatilities are not supported.");
805
806 // Get configuration values for parametric smile
807 std::vector<std::vector<std::pair<Real,bool>>> initialModelParameters;
808 Size maxCalibrationAttempts = 10;
809 Real exitEarlyErrorThreshold = 0.005;
810 Real maxAcceptableError = 0.05;
811 if (config.parametricSmileConfiguration()) {
812 auto alpha = config.parametricSmileConfiguration()->parameter("alpha");
813 auto beta = config.parametricSmileConfiguration()->parameter("beta");
814 auto nu = config.parametricSmileConfiguration()->parameter("nu");
815 auto rho = config.parametricSmileConfiguration()->parameter("rho");
816 QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
817 alpha.initialValue.size() == nu.initialValue.size() &&
818 alpha.initialValue.size() == rho.initialValue.size(),
819 "CapFloorVolCurve: parametric smile config: alpha size ("
820 << alpha.initialValue.size() << ") beta size (" << beta.initialValue.size() << ") nu size ("
821 << nu.initialValue.size() << ") rho size (" << rho.initialValue.size() << ") must match");
822 for (Size i = 0; i < alpha.initialValue.size(); ++i) {
823 initialModelParameters.push_back(std::vector<std::pair<Real, bool>>());
824 initialModelParameters.back().push_back(std::make_pair(alpha.initialValue[i], alpha.isFixed));
825 initialModelParameters.back().push_back(std::make_pair(beta.initialValue[i], beta.isFixed));
826 initialModelParameters.back().push_back(std::make_pair(nu.initialValue[i], nu.isFixed));
827 initialModelParameters.back().push_back(std::make_pair(rho.initialValue[i], rho.isFixed));
828 }
829 maxCalibrationAttempts = config.parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
830 exitEarlyErrorThreshold = config.parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
831 maxAcceptableError = config.parametricSmileConfiguration()->calibration().maxAcceptableError;
832 }
833
834 // Load optionlet vol surface
835 Size quoteCounter = 0;
836 bool quoteRelevant = false;
837 bool tenorRelevant = false;
838 bool strikeRelevant = false;
839 bool wildcardTenor = false;
840 bool atmTenorRelevant = false;
841 bool atmWildcardTenor = false;
842
843 Period tenor = parsePeriod(config.indexTenor()); // 1D, 1M, 3M, 6M, 12M
844 bool includeAtm = config.includeAtm();
845 string currency = config.currency();
846 vector<Period> configTenors;
847 std::map<Period, std::map<Real, Real>> capfloorVols;
848 vector<Period> atmConfigTenors;
849 std::map<Period, Real> atmCapFloorVols;
850 VolatilityType volType = volatilityType(config.volatilityType());
851 bool isOis = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(iborIndex) != nullptr;
852
853 if (config.tenors()[0] == "*") {
854 wildcardTenor = true;
855 } else {
856 configTenors = parseVectorOfValues<Period>(config.tenors(), &parsePeriod);
857 }
858 if (includeAtm) {
859 if (config.atmTenors()[0] == "*") {
860 atmWildcardTenor = true;
861 } else {
862 atmConfigTenors = parseVectorOfValues<Period>(config.atmTenors(), &parsePeriod);
863 }
864 }
865 std::ostringstream ss;
866 ss << MarketDatum::InstrumentType::CAPFLOOR << "/" << config.quoteType() << "/" << currency << "/";
867 if (config.quoteIncludesIndexName())
868 ss << config.index() << "/";
869 ss << "*";
870 Wildcard w(ss.str());
871 for (const auto& md : loader.get(w, asof)) {
872 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
873 QuantLib::ext::shared_ptr<CapFloorQuote> cfq = QuantLib::ext::dynamic_pointer_cast<CapFloorQuote>(md);
874 QL_REQUIRE(cfq, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CapFloorQuote");
875 QL_REQUIRE(cfq->ccy() == currency,
876 "CapFloorQuote ccy '" << cfq->ccy() << "' <> config ccy '" << currency << "'");
877 if (cfq->underlying() == tenor) {
878 // Surface quotes
879 if (!cfq->atm()) {
880 auto findTenor = std::find(configTenors.begin(), configTenors.end(), cfq->term());
881 if (wildcardTenor) {
882 tenorRelevant = true;
883 } else {
884 tenorRelevant = findTenor != configTenors.end();
885 }
886
887 Real strike = cfq->strike();
888 auto i = std::find_if(config.strikes().begin(), config.strikes().end(),
889 [&strike](const std::string& x) { return close_enough(parseReal(x), strike); });
890 strikeRelevant = i != config.strikes().end();
891
892 quoteRelevant = strikeRelevant && tenorRelevant;
893
894 if (quoteRelevant) {
895 quoteCounter++;
896 // Check duplicate quotes
897 auto k = capfloorVols.find(cfq->term());
898 if (k != capfloorVols.end()) {
899 if (k->second.find(cfq->strike()) != k->second.end()) {
900 if (config.quoteIncludesIndexName())
901 QL_FAIL("Duplicate optionlet vol quote in config "
902 << config.curveID() << ", with underlying tenor " << tenor << " ,currency "
903 << currency << " and index " << config.index() << ", for tenor " << cfq->term()
904 << " and strike " << cfq->strike());
905 else
906 QL_FAIL("Duplicate optionlet vol quote in config "
907 << config.curveID() << ", with underlying tenor " << tenor << " and currency "
908 << currency << ", for tenor " << cfq->term() << " and strike "
909 << cfq->strike());
910 }
911 }
912 // Store the vols into a map to sort the wildcard tenors
913 capfloorVols[cfq->term()][cfq->strike()] = cfq->quote()->value();
914 }
915 } else if (includeAtm) {
916 // atm quotes
917 auto findTenor = std::find(atmConfigTenors.begin(), atmConfigTenors.end(), cfq->term());
918 if (atmWildcardTenor) {
919 atmTenorRelevant = true;
920 } else {
921 atmTenorRelevant = findTenor != atmConfigTenors.end();
922 }
923 if (atmTenorRelevant) {
924 quoteCounter++;
925 // Check duplicate quotes
926 auto k = atmCapFloorVols.find(cfq->term());
927 if (k != atmCapFloorVols.end()) {
928 if (config.quoteIncludesIndexName())
929 QL_FAIL("Duplicate optionlet atm vol quote in config "
930 << config.curveID() << ", with underlying tenor " << tenor << " ,currency "
931 << currency << " and index " << config.index() << ", for tenor " << cfq->term());
932 else
933 QL_FAIL("Duplicate optionlet atm vol quote in config "
934 << config.curveID() << ", with underlying tenor " << tenor << " and currency "
935 << currency << ", for tenor " << cfq->term());
936 }
937 // Store the vols into a map to sort the wildcard tenors
938 atmCapFloorVols[cfq->term()] = cfq->quote()->value();
939 }
940 }
941 }
942 }
943 vector<Rate> strikes = parseVectorOfValues<Real>(config.strikes(), &parseReal);
944 auto tenor_itr = configTenors.begin();
945 for (auto const& vols_outer: capfloorVols) {
946 // Check if all tenors are available
947 if (!wildcardTenor) {
948 QL_REQUIRE(*tenor_itr == vols_outer.first, "Quote with tenor " << *tenor_itr
949 << " not loaded for optionlet vol config "
950 << config.curveID());
951 tenor_itr++;
952 }
953 // Check if all strikes are available
954 for (Size j = 0; j < strikes.size(); j++) {
955 auto it = vols_outer.second.find(strikes[j]);
956 QL_REQUIRE(it != vols_outer.second.end(),
957 "Quote with tenor " << vols_outer.first << " and strike " << strikes[j]
958 << " not loaded for optionlet vol config "
959 << config.curveID());
960 }
961 }
962 std::map<Date, Date> tenorMap;
963 if (includeAtm) {
964 // Check if all tenor for atm quotes exists
965 if (!atmWildcardTenor) {
966 auto tenor_itr = atmConfigTenors.begin();
967 for (auto const& vols_outer : atmCapFloorVols) {
968 QL_REQUIRE(*tenor_itr == vols_outer.first, "Quote with tenor "
969 << *tenor_itr << " not loaded for optionlet vol config "
970 << config.curveID());
971 tenor_itr++;
972 }
973 }
974 // Add tenor to the mapping
975 for (auto const& vols_outer : atmCapFloorVols) {
976 capfloorVols[vols_outer.first];
977 }
978 }
979 // Find the fixing date of the term quotes
980 vector<Period> tenors;
981 for (auto const& vols_outer : capfloorVols) {
982 tenors.push_back(vols_outer.first);
983 }
984 // Find the fixing date of the term quotes
985 vector<Date> fixingDates = populateFixingDates(asof, config, iborIndex, tenors);
986
987 // populate strikes for atm quotes
988 if (includeAtm){
989 Rate atmStrike;
990 for (auto const& vols_outer : atmCapFloorVols) {
991 auto it = find(tenors.begin(), tenors.end(), vols_outer.first);
992 Size ind = it - tenors.begin();
993 if (isOis) {
994 atmStrike = getOisAtmLevel(QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(iborIndex), fixingDates[ind],
995 config.rateComputationPeriod());
996 } else {
997 atmStrike = iborIndex->forecastFixing(fixingDates[ind]);
998 }
999 if (capfloorVols[vols_outer.first].find(atmStrike) == capfloorVols[vols_outer.first].end()) {
1000 capfloorVols[vols_outer.first][atmStrike] = vols_outer.second;
1001 }
1002 }
1003 }
1004 vector<vector<Rate>> strikes_vec;
1005 vector<Rate> strikes_tenor;
1006 vector<vector<Handle<Quote>>> vols_vec;
1007 vector<Handle<Quote>> vols_tenor;
1008 for (auto const& vols_outer : capfloorVols) {
1009 for (auto const& vols_inner : vols_outer.second) {
1010 vols_tenor.push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(vols_inner.second)));
1011 strikes_tenor.push_back(vols_inner.first);
1012 }
1013 tenors.push_back(vols_outer.first);
1014 strikes_vec.push_back(strikes_tenor);
1015 strikes_tenor.clear();
1016 vols_vec.push_back(vols_tenor);
1017 vols_tenor.clear();
1018 }
1019 DLOG("Found " << quoteCounter << " quotes for optionlet vol surface " << config.curveID());
1020 QuantLib::ext::shared_ptr<StrippedOptionlet> optionletSurface;
1021
1022 // Return for the cap floor term volatility surface
1023 optionletSurface = QuantLib::ext::make_shared<StrippedOptionlet>(
1024 config.settleDays(), config.calendar(), config.businessDayConvention(), iborIndex, fixingDates, strikes_vec,
1025 vols_vec, config.dayCounter(), volType, shift);
1026
1027 SabrParametricVolatility::ModelVariant sabrModelVariant;
1028
1029 // This is not pretty but can't think of a better way (with template functions and or classes)
1030 if (config.timeInterpolation() == "Linear") {
1031 if (config.strikeInterpolation() == "Linear") {
1032 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Linear>>(asof, optionletSurface);
1033 } else if (config.strikeInterpolation() == "LinearFlat") {
1034 capletVol_ =
1035 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, LinearFlat>>(asof, optionletSurface);
1036 } else if (config.strikeInterpolation() == "Cubic") {
1037 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, Cubic>>(asof, optionletSurface);
1038 } else if (config.strikeInterpolation() == "CubicFlat") {
1039 capletVol_ =
1040 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Linear, CubicFlat>>(asof, optionletSurface);
1041 } else if (tryParse(config.strikeInterpolation(), sabrModelVariant,
1042 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
1043 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
1044 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<Linear>>(
1045 asof, optionletSurface, sabrModelVariant, Linear(), boost::none, initialModelParameters,
1046 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
1047 } else {
1048 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected strike interpolation "
1049 << config.strikeInterpolation());
1050 }
1051 } else if (config.timeInterpolation() == "LinearFlat") {
1052 if (config.strikeInterpolation() == "Linear") {
1053 capletVol_ =
1054 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Linear>>(asof, optionletSurface);
1055 } else if (config.strikeInterpolation() == "LinearFlat") {
1056 capletVol_ =
1057 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, LinearFlat>>(asof, optionletSurface);
1058 } else if (config.strikeInterpolation() == "Cubic") {
1059 capletVol_ =
1060 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, Cubic>>(asof, optionletSurface);
1061 } else if (config.strikeInterpolation() == "CubicFlat") {
1062 capletVol_ =
1063 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, CubicFlat>>(asof, optionletSurface);
1064 } else if (tryParse(config.strikeInterpolation(), sabrModelVariant,
1065 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
1066 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
1067 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<LinearFlat>>(
1068 asof, optionletSurface, sabrModelVariant, LinearFlat(), boost::none, initialModelParameters,
1069 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
1070 } else {
1071 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected strike interpolation "
1072 << config.strikeInterpolation());
1073 }
1074 } else if (config.timeInterpolation() == "BackwardFlat") {
1075 if (config.strikeInterpolation() == "Linear") {
1076 capletVol_ =
1077 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Linear>>(asof, optionletSurface);
1078 } else if (config.strikeInterpolation() == "LinearFlat") {
1079 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, LinearFlat>>(
1080 asof, optionletSurface);
1081 } else if (config.strikeInterpolation() == "Cubic") {
1082 capletVol_ =
1083 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, Cubic>>(asof, optionletSurface);
1084 } else if (config.strikeInterpolation() == "CubicFlat") {
1085 capletVol_ =
1086 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<BackwardFlat, CubicFlat>>(asof, optionletSurface);
1087 } else if (tryParse(config.strikeInterpolation(), sabrModelVariant,
1088 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
1089 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
1090 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<BackwardFlat>>(
1091 asof, optionletSurface, sabrModelVariant, BackwardFlat(), boost::none, initialModelParameters,
1092 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
1093 } else {
1094 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected strike interpolation "
1095 << config.strikeInterpolation());
1096 }
1097 } else if (config.timeInterpolation() == "Cubic") {
1098 if (config.strikeInterpolation() == "Linear") {
1099 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Linear>>(asof, optionletSurface);
1100 } else if (config.strikeInterpolation() == "LinearFlat") {
1101 capletVol_ =
1102 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, LinearFlat>>(asof, optionletSurface);
1103 } else if (config.strikeInterpolation() == "Cubic") {
1104 capletVol_ = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, Cubic>>(asof, optionletSurface);
1105 } else if (config.strikeInterpolation() == "CubicFlat") {
1106 capletVol_ =
1107 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<Cubic, CubicFlat>>(asof, optionletSurface);
1108 } else if (tryParse(config.strikeInterpolation(), sabrModelVariant,
1109 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
1110 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
1111 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<Cubic>>(
1112 asof, optionletSurface, sabrModelVariant, Cubic(), boost::none, initialModelParameters,
1113 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
1114 } else {
1115 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected strike interpolation "
1116 << config.strikeInterpolation());
1117 }
1118 } else if (config.timeInterpolation() == "CubicFlat") {
1119 if (config.strikeInterpolation() == "Linear") {
1120 capletVol_ =
1121 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Linear>>(asof, optionletSurface);
1122 } else if (config.strikeInterpolation() == "LinearFlat") {
1123 capletVol_ =
1124 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, LinearFlat>>(asof, optionletSurface);
1125 } else if (config.strikeInterpolation() == "Cubic") {
1126 capletVol_ =
1127 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, Cubic>>(asof, optionletSurface);
1128 } else if (config.strikeInterpolation() == "CubicFlat") {
1129 capletVol_ =
1130 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<CubicFlat, CubicFlat>>(asof, optionletSurface);
1131 } else if (tryParse(config.strikeInterpolation(), sabrModelVariant,
1132 std::function<QuantExt::SabrParametricVolatility::ModelVariant(const std::string&)>(
1133 [](const std::string& s) { return parseSabrParametricVolatilityModelVariant(s); }))) {
1134 capletVol_ = QuantLib::ext::make_shared<QuantExt::SabrStrippedOptionletAdapter<CubicFlat>>(
1135 asof, optionletSurface, sabrModelVariant, CubicFlat(), boost::none, initialModelParameters,
1136 maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
1137 } else {
1138 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected strike interpolation "
1139 << config.strikeInterpolation());
1140 }
1141 } else {
1142 QL_FAIL("Optionlet vol config " << config.curveID() << " has unexpected time interpolation "
1143 << config.timeInterpolation());
1144 }
1145}
1146
1147QuantLib::ext::shared_ptr<QuantExt::CapFloorTermVolSurface>
1148CapFloorVolCurve::capSurface(const Date& asof, CapFloorVolatilityCurveConfig& config, const Loader& loader) const {
1149
1150 // Map to store the quote values that we load with key (period, strike) where strike
1151 // needs a custom comparator to avoid == with double
1152 auto comp = [](const pair<Period, Rate>& a, const pair<Period, Rate>& b) {
1153 return (a.first < b.first) || (!(b.first < a.first) && (a.second < b.second && !close(a.second, b.second)));
1154 };
1155 map<pair<Period, Rate>, Real, decltype(comp)> volQuotes(comp);
1156
1157 bool optionalQuotes = config.optionalQuotes();
1158 Size quoteCounter = 0;
1159 bool quoteRelevant = false;
1160 bool tenorRelevant = false;
1161 bool strikeRelevant = false;
1162
1163 vector<Real> qtStrikes;
1164 vector<Real> qtData;
1165 vector<Period> qtTenors;
1166 Period tenor = parsePeriod(config.indexTenor()); // 1D, 1M, 3M, 6M, 12M
1167 string currency = config.currency();
1168 vector<Period> configTenors = parseVectorOfValues<Period>(config.tenors(), &parsePeriod);
1169
1170 std::ostringstream ss;
1171 ss << MarketDatum::InstrumentType::CAPFLOOR << "/" << config.quoteType() << "/" << currency << "/";
1172 if (config.quoteIncludesIndexName())
1173 ss << config.index() << "/";
1174 ss << "*";
1175 Wildcard w(ss.str());
1176 for (const auto& md : loader.get(w, asof)) {
1177 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
1178 QuantLib::ext::shared_ptr<CapFloorQuote> cfq = QuantLib::ext::dynamic_pointer_cast<CapFloorQuote>(md);
1179 QL_REQUIRE(cfq, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CapFloorQuote");
1180 QL_REQUIRE(cfq->ccy() == currency, "CapFloorQuote ccy '" << cfq->ccy() << "' <> config ccy '" << currency << "'");
1181 if (cfq->underlying() == tenor && !cfq->atm()) {
1182 auto j = std::find(configTenors.begin(), configTenors.end(), cfq->term());
1183 tenorRelevant = j != configTenors.end();
1184
1185 Real strike = cfq->strike();
1186 auto i = std::find_if(config.strikes().begin(), config.strikes().end(),
1187 [&strike](const std::string& x) {
1188 return close_enough(parseReal(x), strike);
1189 });
1190 strikeRelevant = i != config.strikes().end();
1191
1192 quoteRelevant = strikeRelevant && tenorRelevant;
1193
1194 if (quoteRelevant) {
1195 // if we have optional quotes we just make vectors of all quotes and let the sparse surface
1196 // handle them
1197 quoteCounter++;
1198 if (optionalQuotes) {
1199 qtStrikes.push_back(cfq->strike());
1200 qtTenors.push_back(cfq->term());
1201 qtData.push_back(cfq->quote()->value());
1202 }
1203 auto key = make_pair(cfq->term(), cfq->strike());
1204 auto r = volQuotes.insert(make_pair(key, cfq->quote()->value()));
1205 if (config.quoteIncludesIndexName())
1206 QL_REQUIRE(r.second, "Duplicate cap floor quote in config " << config.curveID() << ", with underlying tenor " << tenor <<
1207 " ,currency " << currency << " and index " << config.index() << ", for tenor " << key.first << " and strike " << key.second);
1208 else
1209 QL_REQUIRE(r.second, "Duplicate cap floor quote in config " << config.curveID() << ", with underlying tenor " << tenor <<
1210 " and currency " << currency << ", for tenor " << key.first << " and strike " << key.second);
1211 }
1212 }
1213 }
1214
1215 Size totalQuotes = config.tenors().size() * config.strikes().size();
1216 if (quoteCounter < totalQuotes) {
1217 WLOG("Found only " << quoteCounter << " out of " << totalQuotes << " quotes for CapFloor surface " << config.curveID());
1218 }
1219
1220 vector<Period> tenors = parseVectorOfValues<Period>(config.tenors(), &parsePeriod);
1221 vector<Rate> strikes = parseVectorOfValues<Real>(config.strikes(), &parseReal);
1222 Matrix vols(tenors.size(), strikes.size());
1223 for (Size i = 0; i < tenors.size(); i++) {
1224 for (Size j = 0; j < strikes.size(); j++) {
1225 auto key = make_pair(tenors[i], strikes[j]);
1226 auto it = volQuotes.find(key);
1227 if (!optionalQuotes) {
1228 QL_REQUIRE(it != volQuotes.end(), "Quote with tenor " << key.first << " and strike " << key.second
1229 << " not loaded for cap floor config "
1230 << config.curveID());
1231 // Organise the values in to a square matrix
1232 vols[i][j] = it->second;
1233 } else {
1234 if (it == volQuotes.end()) {
1235 DLOG("Could not find quote with tenor " << key.first << " and strike " << key.second <<
1236 " for cap floor config " << config.curveID());
1237 }
1238 }
1239 }
1240 }
1241
1242 DLOG("Found " << quoteCounter << " quotes for capfloor surface " << config.curveID());
1243 if (optionalQuotes) {
1244 QL_REQUIRE(quoteCounter > 0, "No Quotes provided for CapFloor surface " << config.curveID());
1246 return QuantLib::ext::make_shared<QuantExt::CapFloorTermVolSurfaceSparse<Linear, Linear>>(
1247 config.settleDays(), config.calendar(), config.businessDayConvention(), config.dayCounter(), qtTenors,
1248 qtStrikes, qtData, true, true);
1250 return QuantLib::ext::make_shared<QuantExt::CapFloorTermVolSurfaceSparse<Cubic, Cubic>>(
1251 config.settleDays(), config.calendar(), config.businessDayConvention(), config.dayCounter(), qtTenors,
1252 qtStrikes, qtData, true, true);
1253 } else {
1254 QL_FAIL("Invalid Interpolation method for capfloor surface " << config.curveID() << ", must be either "
1256 }
1257 } else {
1258 // Return for the cap floor term volatility surface
1259 return QuantLib::ext::make_shared<QuantExt::CapFloorTermVolSurfaceExact>(config.settleDays(), config.calendar(),
1260 config.businessDayConvention(), tenors, strikes, vols,
1261 config.dayCounter(), config.interpolationMethod());
1262 }
1263}
1264
1265QuantLib::ext::shared_ptr<QuantExt::CapFloorTermVolCurve>
1266CapFloorVolCurve::atmCurve(const Date& asof, CapFloorVolatilityCurveConfig& config, const Loader& loader) const {
1267
1268 // Map to store the quote values
1269 map<Period, Handle<Quote>> volQuotes;
1270
1271 bool optionalQuotes = config.optionalQuotes();
1272 Period tenor = parsePeriod(config.indexTenor()); // 1D, 1M, 3M, 6M, 12M
1273 string currency = config.currency();
1274 // Load the relevant quotes
1275 std::ostringstream ss;
1276 ss << MarketDatum::InstrumentType::CAPFLOOR << "/" << config.quoteType() << "/" << currency << "/*";
1277 Wildcard w(ss.str());
1278 for (const auto& md : loader.get(w, asof)) {
1279 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
1280 QuantLib::ext::shared_ptr<CapFloorQuote> cfq = QuantLib::ext::dynamic_pointer_cast<CapFloorQuote>(md);
1281 QL_REQUIRE(cfq, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CapFloorQuote");
1282 QL_REQUIRE(cfq->ccy() == currency, "CapFloorQuote ccy '" << cfq->ccy() << "' <> config ccy '" << currency << "'");
1283 if (cfq->underlying() == tenor && cfq->atm()) {
1284 auto j = std::find(config.atmTenors().begin(), config.atmTenors().end(), to_string(cfq->term()));
1285 if (j != config.atmTenors().end()) {
1286 auto r = volQuotes.insert(make_pair(cfq->term(), cfq->quote()));
1287 QL_REQUIRE(r.second, "Duplicate ATM cap floor quote in config " << config.curveID() << " for tenor "
1288 << cfq->term());
1289 }
1290 }
1291 }
1292
1293 // Check that the loaded quotes cover all of the configured ATM tenors
1294 vector<Period> tenors = parseVectorOfValues<Period>(config.atmTenors(), &parsePeriod);
1295 vector<Handle<Quote>> vols;
1296 vector<Period> quoteTenors;
1297 if (!optionalQuotes) {
1298 vols.resize(tenors.size());
1299 quoteTenors = tenors;
1300 }
1301 for (Size i = 0; i < tenors.size(); i++) {
1302 auto it = volQuotes.find(tenors[i]);
1303 if (!optionalQuotes) {
1304 QL_REQUIRE(it != volQuotes.end(),
1305 "ATM cap floor quote in config " << config.curveID() << " for tenor " << tenors[i] << " not found ");
1306 vols[i] = it->second;
1307 } else {
1308 if (it == volQuotes.end()) {
1309 DLOG("Could not find ATM cap floor quote with tenor " << tenors[i] << " for cap floor config " << config.curveID());
1310 } else {
1311 vols.push_back(it->second);
1312 quoteTenors.push_back(it->first);
1313 }
1314 }
1315 }
1316
1317 if (optionalQuotes) {
1318 QL_REQUIRE(vols.size() > 0, "No ATM cap floor quotes found for cap floor config " << config.curveID());
1319 if (vols.size() == 1)
1320 WLOG("Only one ATM cap floor quote found for cap floor config " << config.curveID() << ", using constant volatility");
1321 }
1322
1323 // Return for the cap floor ATM term volatility curve
1324 // The interpolation here is also based on the interpolation method parameter in the configuration
1325 // Flat first period is true by default (see ctor)
1326 if (config.interpolationMethod() == CftvsInterp::BicubicSpline) {
1327 if (config.flatExtrapolation()) {
1328 return QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<CubicFlat>>(
1329 config.settleDays(), config.calendar(), config.businessDayConvention(), quoteTenors, vols,
1330 config.dayCounter());
1331 } else {
1332 return QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic>>(config.settleDays(), config.calendar(),
1333 config.businessDayConvention(), quoteTenors,
1334 vols, config.dayCounter());
1335 }
1336 } else if (config.interpolationMethod() == CftvsInterp::Bilinear) {
1337 if (config.flatExtrapolation()) {
1338 return QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<LinearFlat>>(
1339 config.settleDays(), config.calendar(), config.businessDayConvention(), quoteTenors, vols,
1340 config.dayCounter());
1341 } else {
1342 return QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear>>(config.settleDays(), config.calendar(),
1343 config.businessDayConvention(), quoteTenors,
1344 vols, config.dayCounter());
1345 }
1346 } else {
1347 QL_FAIL("Cap floor config " << config.curveID() << " has unexpected interpolation method "
1348 << static_cast<int>(config.interpolationMethod()));
1349 }
1350}
1351
1352Real CapFloorVolCurve::shiftQuote(const QuantLib::Date& asof, CapFloorVolatilityCurveConfig& config,
1353 const Loader& loader) const {
1354
1355 QL_REQUIRE(config.volatilityType() == CfgVolType::ShiftedLognormal,
1356 "Method shiftQuote should not be called with a config who's volatility type is not ShiftedLognormal");
1357
1358 // Search for the shift quote in the configured quotes
1359 for (const string& quoteId : config.quotes()) {
1360
1361 QuantLib::ext::shared_ptr<MarketDatum> md = loader.get(quoteId, asof);
1362
1363 // If it is a shift quote
1364 if (QuantLib::ext::shared_ptr<CapFloorShiftQuote> sq = QuantLib::ext::dynamic_pointer_cast<CapFloorShiftQuote>(md)) {
1365 return sq->quote()->value();
1366 }
1367 }
1368
1369 QL_FAIL("Could not find a shift quote for cap floor config " << config.curveID());
1370}
1371
1372// This method is used to pull out the stripped optionlets from the QuantExt::OptionletStripper instance that is
1373// bootstrapped. The reason is that we do not want all of the cap floor helpers and their coupons in scope during
1374// a potential XVA run as this leads to delays when fixings are updated.
1375QuantLib::ext::shared_ptr<StrippedOptionlet> CapFloorVolCurve::transform(const QuantExt::OptionletStripper& os) const {
1376
1377 vector<vector<Handle<Quote>>> vols(os.optionletFixingDates().size());
1378 for (Size i = 0; i < os.optionletFixingDates().size(); i++) {
1379 for (Size j = 0; j < os.optionletVolatilities(i).size(); j++) {
1380 vols[i].push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(os.optionletVolatilities(i)[j])));
1381 }
1382 }
1383
1384 vector<vector<Real>> optionletStrikes;
1385 for (Size i = 0; i < os.optionletFixingDates().size(); i++) {
1386 optionletStrikes.push_back(os.optionletStrikes(i));
1387 }
1388
1389 QuantLib::ext::shared_ptr<StrippedOptionlet> res = QuantLib::ext::make_shared<StrippedOptionlet>(
1391 optionletStrikes, vols, os.dayCounter(), os.volatilityType(), os.displacement(), os.atmOptionletRates());
1392
1393 res->unregisterWithAll();
1394
1395 return res;
1396}
1397
1398QuantLib::ext::shared_ptr<StrippedOptionlet>
1399CapFloorVolCurve::transform(const Date& asof, vector<Date> dates, const vector<Volatility>& volatilities,
1400 Natural settleDays, const Calendar& cal, BusinessDayConvention bdc,
1401 QuantLib::ext::shared_ptr<IborIndex> index, const DayCounter& dc, VolatilityType type,
1402 Real displacement) const {
1403
1404 vector<vector<Handle<Quote>>> vols(dates.size());
1405 for (Size i = 0; i < dates.size(); i++) {
1406 vols[i].push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(volatilities[i])));
1407 }
1408
1409 // Temp fix because QuantLib::StrippedOptionlet requires optionlet dates strictly greater than
1410 // the evaluation date. Would rather relax this to '>=' in QuantLib::StrippedOptionlet
1411 if (dates[0] == asof) {
1412 dates[0]++;
1413 }
1414
1415 vector<Rate> strikes = {0.0};
1416 QuantLib::ext::shared_ptr<StrippedOptionlet> res = QuantLib::ext::make_shared<StrippedOptionlet>(
1417 settleDays, cal, bdc, index, dates, strikes, vols, dc, type, displacement);
1418
1419 res->unregisterWithAll();
1420
1421 return res;
1422}
1423
1424vector<Date> CapFloorVolCurve::populateFixingDates(const QuantLib::Date& asof, CapFloorVolatilityCurveConfig& config,
1425 QuantLib::ext::shared_ptr<QuantLib::IborIndex> iborIndex, const vector<Period>& configTenors) {
1426 // Find the fixing date of the term quotes
1427 vector<Date> fixingDates;
1428 bool isOis = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(iborIndex) != nullptr;
1429 QuantLib::ext::shared_ptr<BlackCapFloorEngine> dummyEngine =
1430 QuantLib::ext::make_shared<BlackCapFloorEngine>(iborIndex->forwardingTermStructure(), 0.20, config.dayCounter());
1431 for (Size i = 0; i < configTenors.size(); i++) {
1432 if (isOis) {
1433 Leg dummyCap =
1434 MakeOISCapFloor(CapFloor::Cap, configTenors[i], QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(iborIndex),
1435 config.rateComputationPeriod(), 0.04)
1437 .withSettlementDays(config.settleDays());
1438 auto lastCoupon = QuantLib::ext::dynamic_pointer_cast<CappedFlooredOvernightIndexedCoupon>(dummyCap.back());
1439 QL_REQUIRE(lastCoupon, "OptionletStripper::populateDates(): expected CappedFlooredOvernightIndexedCoupon");
1440 fixingDates.push_back(std::max(asof + 1, lastCoupon->underlying()->fixingDates().front()));
1441 } else {
1442 CapFloor dummyCap =
1443 MakeCapFloor(CapFloor::Cap, configTenors[i], iborIndex, 0.04, 0 * Days).withPricingEngine(dummyEngine);
1444 QuantLib::ext::shared_ptr<FloatingRateCoupon> lastCoupon = dummyCap.lastFloatingRateCoupon();
1445 fixingDates.push_back(std::max(asof + 1, lastCoupon->fixingDate()));
1446 }
1447 }
1448 return fixingDates;
1449}
1451 const QuantLib::ext::shared_ptr<CapFloorVolatilityCurveConfig> config,
1452 const QuantLib::ext::shared_ptr<IborIndex>& index) {
1453 DLOG("Building calibration info for cap floor vols");
1454
1455 ReportConfig rc = effectiveReportConfig(curveConfigs.reportConfigIrCapFloorVols(), config->reportConfig());
1456 bool reportOnStrikeGrid = *rc.reportOnStrikeGrid();
1457 bool reportOnStrikeSpreadGrid = *rc.reportOnStrikeSpreadGrid();
1458 std::vector<Real> strikes = *rc.strikes();
1459 std::vector<Real> strikeSpreads = *rc.strikeSpreads();
1460 std::vector<Period> expiries = *rc.expiries();
1461 std::vector<Period> underlyingTenorsReport(1, index->tenor());
1462
1463 calibrationInfo_ = QuantLib::ext::make_shared<IrVolCalibrationInfo>();
1464
1465 calibrationInfo_->dayCounter = config->dayCounter().empty() ? "na" : config->dayCounter().name();
1466 calibrationInfo_->calendar = config->calendar().empty() ? "na" : config->calendar().name();
1467 calibrationInfo_->volatilityType = ore::data::to_string(capletVol_->volatilityType());
1468 calibrationInfo_->underlyingTenors = underlyingTenorsReport;
1469
1470 bool isOis = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(index) != nullptr;
1471
1472 Size onSettlementDays = 0;
1473 if (isOis) {
1474 onSettlementDays = config->onCapSettlementDays();
1475 }
1476
1477 std::vector<Real> times; // fixing times of caplets
1478 std::vector<std::vector<Real>> forwards; // fair rates of caplets
1479 for (auto const& p : expiries) {
1480 Date fixingDate;
1481 Real forward;
1482 if (isOis) {
1483 Leg dummyCap = MakeOISCapFloor(CapFloor::Cap, p, QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(index),
1484 config->rateComputationPeriod(), 0.04)
1486 .withSettlementDays(onSettlementDays);
1487 if (dummyCap.empty())
1488 continue;
1489 auto lastCoupon = QuantLib::ext::dynamic_pointer_cast<CappedFlooredOvernightIndexedCoupon>(dummyCap.back());
1490 QL_REQUIRE(lastCoupon, "OptionletStripper::populateDates(): expected CappedFlooredOvernightIndexedCoupon");
1491 fixingDate = std::max(asof + 1, lastCoupon->underlying()->fixingDates().front());
1492 forward = lastCoupon->underlying()->rate();
1493 } else {
1494 CapFloor dummyCap = MakeCapFloor(CapFloor::Cap, p, index, 0.04, 0 * Days);
1495 if (dummyCap.floatingLeg().empty())
1496 continue;
1497 QuantLib::ext::shared_ptr<FloatingRateCoupon> lastCoupon = dummyCap.lastFloatingRateCoupon();
1498 fixingDate = lastCoupon->fixingDate();
1499 forward = index->fixing(fixingDate);
1500 }
1501 calibrationInfo_->expiryDates.push_back(fixingDate);
1502 times.push_back(capletVol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, fixingDate)
1503 : capletVol_->timeFromReference(fixingDate));
1504 forwards.push_back(std::vector<Real>(1, forward));
1505 }
1506
1507 calibrationInfo_->times = times;
1508 calibrationInfo_->forwards = forwards;
1509
1510 std::vector<std::vector<std::vector<Real>>> callPricesStrike(
1511 times.size(),
1512 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikes.size(), 0.0)));
1513 std::vector<std::vector<std::vector<Real>>> callPricesStrikeSpread(
1514 times.size(),
1515 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikeSpreads.size(), 0.0)));
1516
1517 calibrationInfo_->isArbitrageFree = true;
1518
1519 if (reportOnStrikeGrid) {
1520 calibrationInfo_->strikes = strikes;
1521 calibrationInfo_->strikeGridStrikes = std::vector<std::vector<std::vector<Real>>>(
1522 times.size(),
1523 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikes.size(), 0.0)));
1524 calibrationInfo_->strikeGridProb = std::vector<std::vector<std::vector<Real>>>(
1525 times.size(),
1526 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikes.size(), 0.0)));
1527 calibrationInfo_->strikeGridImpliedVolatility = std::vector<std::vector<std::vector<Real>>>(
1528 times.size(),
1529 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikes.size(), 0.0)));
1530 calibrationInfo_->strikeGridCallSpreadArbitrage = std::vector<std::vector<std::vector<bool>>>(
1531 times.size(),
1532 std::vector<std::vector<bool>>(underlyingTenorsReport.size(), std::vector<bool>(strikes.size(), true)));
1533 calibrationInfo_->strikeGridButterflyArbitrage = std::vector<std::vector<std::vector<bool>>>(
1534 times.size(),
1535 std::vector<std::vector<bool>>(underlyingTenorsReport.size(), std::vector<bool>(strikes.size(), true)));
1536 TLOG("Strike surface arbitrage analysis result:");
1537 for (Size u = 0; u < underlyingTenorsReport.size(); ++u) {
1538 TLOG("Underlying tenor " << underlyingTenorsReport[u]);
1539 for (Size i = 0; i < times.size(); ++i) {
1540 Real t = times[i];
1541 Real shift = capletVol_->volatilityType() == Normal ? 0.0 : capletVol_->displacement();
1542 bool validSlice = true;
1543 for (Size j = 0; j < strikes.size(); ++j) {
1544 try {
1545 Real stddev = 0.0;
1546 if (capletVol_->volatilityType() == ShiftedLognormal) {
1547 if ((strikes[j] > -shift || close_enough(strikes[j], -shift)) &&
1548 (forwards[i][u] > -shift || close_enough(forwards[i][u], -shift))) {
1549 stddev =
1550 std::sqrt(capletVol_->blackVariance(t, strikes[j]));
1551 callPricesStrike[i][u][j] =
1552 blackFormula(Option::Type::Call, strikes[j], forwards[i][u], stddev);
1553 }
1554 } else {
1555 stddev = std::sqrt(capletVol_->blackVariance(t, strikes[j]));
1556 callPricesStrike[i][u][j] =
1557 bachelierBlackFormula(Option::Type::Call, strikes[j], forwards[i][u], stddev);
1558 }
1559 calibrationInfo_->strikeGridStrikes[i][u][j] = strikes[j];
1560 calibrationInfo_->strikeGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
1561 } catch (const std::exception& e) {
1562 validSlice = false;
1563 TLOG("error for time " << t << " strike " << strikes[j] << ": " << e.what());
1564 }
1565 }
1566 if (validSlice) {
1567 try {
1569 forwards[i][u], callPricesStrike[i][u],
1570 capletVol_->volatilityType(), shift);
1571 calibrationInfo_->strikeGridCallSpreadArbitrage[i][u] = cm.callSpreadArbitrage();
1572 calibrationInfo_->strikeGridButterflyArbitrage[i][u] = cm.butterflyArbitrage();
1573 if (!cm.arbitrageFree())
1574 calibrationInfo_->isArbitrageFree = false;
1575 calibrationInfo_->strikeGridProb[i][u] = cm.density();
1577 } catch (const std::exception& e) {
1578 TLOG("error for time " << t << ": " << e.what());
1579 calibrationInfo_->isArbitrageFree = false;
1580 TLOGGERSTREAM("..(invalid slice)..");
1581 }
1582 } else {
1583 TLOGGERSTREAM("..(invalid slice)..");
1584 }
1585 }
1586 }
1587 TLOG("Strike cube arbitrage analysis completed.");
1588 }
1589
1590 if (reportOnStrikeSpreadGrid) {
1591 calibrationInfo_->strikeSpreads = strikeSpreads;
1592 calibrationInfo_->strikeSpreadGridStrikes = std::vector<std::vector<std::vector<Real>>>(
1593 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
1594 std::vector<Real>(strikeSpreads.size(), 0.0)));
1595 calibrationInfo_->strikeSpreadGridProb = std::vector<std::vector<std::vector<Real>>>(
1596 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
1597 std::vector<Real>(strikeSpreads.size(), 0.0)));
1598 calibrationInfo_->strikeSpreadGridImpliedVolatility = std::vector<std::vector<std::vector<Real>>>(
1599 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
1600 std::vector<Real>(strikeSpreads.size(), 0.0)));
1601 calibrationInfo_->strikeSpreadGridCallSpreadArbitrage = std::vector<std::vector<std::vector<bool>>>(
1602 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
1603 std::vector<bool>(strikeSpreads.size(), true)));
1604 calibrationInfo_->strikeSpreadGridButterflyArbitrage = std::vector<std::vector<std::vector<bool>>>(
1605 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
1606 std::vector<bool>(strikeSpreads.size(), true)));
1607 TLOG("Strike Spread surface arbitrage analysis result:");
1608 for (Size u = 0; u < underlyingTenorsReport.size(); ++u) {
1609 TLOG("Underlying tenor " << underlyingTenorsReport[u]);
1610 for (Size i = 0; i < times.size(); ++i) {
1611 Real t = times[i];
1612 Real shift = capletVol_->volatilityType() == Normal ? 0.0 : capletVol_->displacement();
1613 bool validSlice = true;
1614 for (Size j = 0; j < strikeSpreads.size(); ++j) {
1615 Real strike = forwards[i][u] + strikeSpreads[j];
1616 try {
1617 Real stddev = 0.0;
1618 if (capletVol_->volatilityType() == ShiftedLognormal) {
1619 if ((strike > -shift || close_enough(strike, -shift)) &&
1620 (forwards[i][u] > -shift || close_enough(forwards[i][u], -shift))) {
1621 stddev = std::sqrt(capletVol_->blackVariance(t, strike));
1622 callPricesStrikeSpread[i][u][j] =
1623 blackFormula(Option::Type::Call, strike, forwards[i][u], stddev);
1624 }
1625 } else {
1626 stddev = std::sqrt(capletVol_->blackVariance(t, strike));
1627 callPricesStrikeSpread[i][u][j] =
1628 bachelierBlackFormula(Option::Type::Call, strike, forwards[i][u], stddev);
1629 }
1630 calibrationInfo_->strikeSpreadGridStrikes[i][u][j] = strike;
1631 calibrationInfo_->strikeSpreadGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
1632 } catch (const std::exception& e) {
1633 validSlice = false;
1634 TLOG("error for time " << t << " strike spread " << strikeSpreads[j] << " strike " << strike
1635 << ": " << e.what());
1636 }
1637 }
1638 if (validSlice) {
1639 try {
1641 calibrationInfo_->strikeSpreadGridStrikes[i][u], forwards[i][u],
1642 callPricesStrikeSpread[i][u], capletVol_->volatilityType(), shift);
1643 calibrationInfo_->strikeSpreadGridCallSpreadArbitrage[i][u] = cm.callSpreadArbitrage();
1644 calibrationInfo_->strikeSpreadGridButterflyArbitrage[i][u] = cm.butterflyArbitrage();
1645 if (!cm.arbitrageFree())
1646 calibrationInfo_->isArbitrageFree = false;
1647 calibrationInfo_->strikeSpreadGridProb[i][u] = cm.density();
1649 } catch (const std::exception& e) {
1650 TLOG("error for time " << t << ": " << e.what());
1651 calibrationInfo_->isArbitrageFree = false;
1652 TLOGGERSTREAM("..(invalid slice)..");
1653 }
1654 } else {
1655 TLOGGERSTREAM("..(invalid slice)..");
1656 }
1657 }
1658 }
1659 TLOG("Strike Spread cube arbitrage analysis completed.");
1660 }
1661
1662 // output SABR calibration to log, if SABR was used
1663
1664 QuantLib::ext::shared_ptr<QuantExt::SabrParametricVolatility> p;
1665 if (auto s = QuantLib::ext::dynamic_pointer_cast<SabrStrippedOptionletAdapter<Linear>>(capletVol_))
1666 p = QuantExt::ext::dynamic_pointer_cast<SabrParametricVolatility>(s->parametricVolatility());
1667 else if (auto s = QuantLib::ext::dynamic_pointer_cast<SabrStrippedOptionletAdapter<LinearFlat>>(capletVol_))
1668 p = QuantExt::ext::dynamic_pointer_cast<SabrParametricVolatility>(s->parametricVolatility());
1669 else if (auto s = QuantLib::ext::dynamic_pointer_cast<SabrStrippedOptionletAdapter<Cubic>>(capletVol_))
1670 p = QuantExt::ext::dynamic_pointer_cast<SabrParametricVolatility>(s->parametricVolatility());
1671 else if (auto s = QuantLib::ext::dynamic_pointer_cast<SabrStrippedOptionletAdapter<CubicFlat>>(capletVol_))
1672 p = QuantExt::ext::dynamic_pointer_cast<SabrParametricVolatility>(s->parametricVolatility());
1673 else if (auto s = QuantLib::ext::dynamic_pointer_cast<SabrStrippedOptionletAdapter<BackwardFlat>>(capletVol_))
1674 p = QuantExt::ext::dynamic_pointer_cast<SabrParametricVolatility>(s->parametricVolatility());
1675
1676 if (p) {
1677 DLOG("SABR parameters:");
1678 DLOG("alpha (pls ignore second row, this is present for technical reasons):");
1679 DLOGGERSTREAM(p->alpha());
1680 DLOG("beta (pls ignore second row, this is present for technical reasons):");
1681 DLOGGERSTREAM(p->beta());
1682 DLOG("nu (pls ignore second row, this is present for technical reasons):");
1683 DLOGGERSTREAM(p->nu());
1684 DLOG("rho (pls ignore second row, this is present for technical reasons):");
1685 DLOGGERSTREAM(p->rho());
1686 DLOG("lognormal shift (pls ignore second row, this is present for technical reasons):");
1687 DLOGGERSTREAM(p->lognormalShift());
1688 DLOG("calibration attempts:");
1689 DLOGGERSTREAM(p->numberOfCalibrationAttempts());
1690 DLOG("calibration error:");
1691 DLOGGERSTREAM(p->calibrationError());
1692 DLOG("isInterpolated (1 means calibration failed and point is interpolated):");
1693 DLOGGERSTREAM(p->isInterpolated());
1694 }
1695
1696 DLOG("Building calibration info cap floor vols completed.");
1697}
1698
1699} // namespace data
1700} // namespace ore
ore::data::CapFloorVolatilityCurveConfig::VolatilityType CfgVolType
QuantExt::CapFloorTermVolSurfaceExact::InterpolationMethod CftvsInterp
ore::data::CapFloorVolatilityCurveConfig::Type CfgType
Build optionlet volatility structures from cap floor configurations.
const std::vector< bool > & butterflyArbitrage() const
const std::vector< bool > & callSpreadArbitrage() const
const std::vector< Real > & density() const
MakeOISCapFloor & withSettlementDays(Natural settlementDays)
MakeOISCapFloor & withTelescopicValueDates(bool telescopicValueDates)
Calendar calendar() const override
const std::vector< Date > & optionletFixingDates() const override
const std::vector< Rate > & optionletStrikes(Size i) const override
const std::vector< Volatility > & optionletVolatilities(Size i) const override
const std::vector< Rate > & atmOptionletRates() const override
VolatilityType volatilityType() const override
Natural settlementDays() const override
DayCounter dayCounter() const override
BusinessDayConvention businessDayConvention() const override
Real displacement() const override
ext::shared_ptr< IborIndex > index() const
QuantLib::Real maxFactor() const
QuantLib::Size dontThrowSteps() const
QuantLib::Real globalAccuracy() const
QuantLib::Real accuracy() const
QuantLib::Real minFactor() const
QuantLib::Size maxAttempts() const
Serializable cap, floor, collar.
Definition: capfloor.hpp:37
Real shiftQuote(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader) const
Get a shift quote value from the configured quotes.
QuantLib::ext::shared_ptr< IrVolCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< QuantLib::StrippedOptionlet > transform(const QuantExt::OptionletStripper &os) const
Transform QuantExt::OptionletStripper to QuantLib::StrippedOptionlet.
QuantLib::ext::shared_ptr< QuantExt::CapFloorTermVolSurface > capSurface(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader) const
Build a cap floor term volatility surface.
CapFloorVolCurve()
Default constructor.
void buildCalibrationInfo(const Date &asof, const CurveConfigurations &curveConfigs, const QuantLib::ext::shared_ptr< CapFloorVolatilityCurveConfig > config, const QuantLib::ext::shared_ptr< IborIndex > &iborIndex)
Build calibration info.
QuantLib::ext::shared_ptr< QuantExt::CapFloorTermVolCurve > atmCurve(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader) const
Build an ATM cap floor term volatility curve.
void termOptSurface(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader, QuantLib::ext::shared_ptr< QuantLib::IborIndex > iborIndex, QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve, QuantLib::Real shift)
Build optionlet surface from term vol.
void optOptSurface(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader, QuantLib::ext::shared_ptr< QuantLib::IborIndex > iborIndex, QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve, QuantLib::Real shift)
Build optionlet surface from optionlet vol.
void termAtmOptCurve(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader, QuantLib::ext::shared_ptr< QuantLib::IborIndex > iborIndex, QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve, QuantLib::Real shift)
Build ATM optionlet curve from term vol.
void optAtmOptCurve(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, const Loader &loader, QuantLib::ext::shared_ptr< QuantLib::IborIndex > iborIndex, QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve, QuantLib::Real shift)
Build ATM optionlet curve from optionlet vol.
QuantLib::ext::shared_ptr< QuantLib::OptionletVolatilityStructure > capletVol_
vector< Date > populateFixingDates(const QuantLib::Date &asof, CapFloorVolatilityCurveConfig &config, QuantLib::ext::shared_ptr< QuantLib::IborIndex > iborIndex, const vector< Period > &configTenors)
Generate fixing days from end date for optionlet vol.
void buildProxyCurve(const CapFloorVolatilityCurveConfig &config, const QuantLib::ext::shared_ptr< IborIndex > &sourceIndex, const QuantLib::ext::shared_ptr< IborIndex > &targetIndex, const std::map< std::string, std::pair< QuantLib::ext::shared_ptr< ore::data::CapFloorVolCurve >, std::pair< std::string, QuantLib::Period > > > &requiredCapFloorVolCurves)
Build proxy curve.
Type
The type of structure that has been configured.
const boost::optional< ParametricSmileConfiguration > parametricSmileConfiguration() const
const QuantLib::Natural & settleDays() const
const std::vector< std::string > & tenors() const
const QuantLib::Period & proxyTargetRateComputationPeriod() const
const QuantLib::BusinessDayConvention & businessDayConvention() const
const QuantLib::Calendar & calendar() const
const QuantLib::DayCounter & dayCounter() const
VolatilityType
The type of volatility quotes that have been configured.
const std::vector< std::string > & atmTenors() const
const BootstrapConfig & bootstrapConfig() const
const QuantLib::Period & proxySourceRateComputationPeriod() const
const VolatilityType & volatilityType() const
const QuantLib::Period & rateComputationPeriod() const
QuantExt::CapFloorTermVolSurfaceExact::InterpolationMethod interpolationMethod() const
const std::vector< std::string > & strikes() const
Cap/Floor Volatility curve description.
Definition: curvespec.hpp:239
const string & curveID() const
Definition: curveconfig.hpp:54
virtual const vector< string > & quotes()
Return all the market quotes required for this config.
Definition: curveconfig.hpp:69
Container class for all Curve Configurations.
Market data loader base class.
Definition: loader.hpp:47
virtual QuantLib::ext::shared_ptr< MarketDatum > get(const std::string &name, const QuantLib::Date &d) const
get quote by its unique name, throws if not existent, override in derived classes for performance
Definition: loader.cpp:24
const boost::optional< std::vector< Real > > & strikes() const
const boost::optional< bool > reportOnStrikeSpreadGrid() const
const boost::optional< bool > reportOnStrikeGrid() const
const boost::optional< std::vector< Period > > & expiries() const
const boost::optional< std::vector< Real > > & strikeSpreads() const
bool tryParse(const std::string &str, T &obj, std::function< T(const std::string &)> parser)
Definition: parsers.hpp:427
SabrParametricVolatility::ModelVariant parseSabrParametricVolatilityModelVariant(const std::string &s)
Parse SabrParametricVolatility::ModelVariant.
Definition: parsers.cpp:1458
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Definition: parsers.cpp:171
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define TLOGGERSTREAM(text)
Definition: log.hpp:633
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
#define DLOGGERSTREAM(text)
Definition: log.hpp:632
Market Datum parser.
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::string arbitrageAsString(const CarrMadanMarginalProbabilityClass &cm)
Real getOisAtmLevel(const boost::shared_ptr< OvernightIndex > &on, const Date &fixingDate, const Period &rateComputationPeriod)
ReportConfig effectiveReportConfig(const ReportConfig &globalConfig, const ReportConfig &localConfig)
VolatilityType volatilityType(CapFloorVolatilityCurveConfig::VolatilityType type)
Imply QuantLib::VolatilityType from CapFloorVolatilityCurveConfig::VolatilityType.
Size size(const ValueType &v)
Definition: value.cpp:145
bool interpOnOpt(CapFloorVolatilityCurveConfig &config)
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
vector< Real > strikes
vector< string > curveConfigs
string conversion utilities