Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
genericyieldvolcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
25
28#include <qle/termstructures/swaptionsabrcube.hpp>
31
32#include <ql/pricingengines/blackformula.hpp>
33#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
34#include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp>
35#include <ql/time/daycounters/actual365fixed.hpp>
36
37#include <algorithm>
38
39using namespace QuantLib;
40using namespace std;
41
42namespace ore {
43namespace data {
44
45namespace {
46Rate atmStrike(const Date& optionD, const Period& swapTenor, const QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase,
47 const QuantLib::ext::shared_ptr<SwapIndex> shortSwapIndexBase) {
48 if (swapTenor > shortSwapIndexBase->tenor()) {
49 return swapIndexBase->clone(swapTenor)->fixing(swapIndexBase->fixingCalendar().adjust(optionD));
50 } else {
51 return shortSwapIndexBase->clone(swapTenor)->fixing(shortSwapIndexBase->fixingCalendar().adjust(optionD));
52 }
53}
54} // namespace
55
57 const Date& asof, const Loader& loader, const CurveConfigurations& curveConfigs,
58 const QuantLib::ext::shared_ptr<GenericYieldVolatilityCurveConfig>& config,
59 const map<string, QuantLib::ext::shared_ptr<SwapIndex>>& requiredSwapIndices,
60 const map<string, QuantLib::ext::shared_ptr<GenericYieldVolCurve>>& requiredVolCurves,
61 const std::function<bool(const QuantLib::ext::shared_ptr<MarketDatum>& md, Period& expiry, Period& term)>& matchAtmQuote,
62 const std::function<bool(const QuantLib::ext::shared_ptr<MarketDatum>& md, Period& expiry, Period& term, Real& strike)>&
63 matchSmileQuote,
64 const std::function<bool(const QuantLib::ext::shared_ptr<MarketDatum>& md, Period& term)>& matchShiftQuote,
65 const bool buildCalibrationInfo) {
66
67 try {
68 QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase;
69 QuantLib::ext::shared_ptr<SwapIndex> shortSwapIndexBase;
70
71 if (!config->proxySourceCurveId().empty()) {
72
73 // Build proxy surface
74
75 QL_REQUIRE(!config->proxySourceShortSwapIndexBase().empty(),
76 "GenericYieldVolCurve: proxy curve requires Source / ShortSwapIndexBase in the curve config.");
77 QL_REQUIRE(!config->proxySourceSwapIndexBase().empty(),
78 "GenericYieldVolCurve: proxy curve requires Source / SwapIndexBase in the curve config.");
79 QL_REQUIRE(!config->proxyTargetShortSwapIndexBase().empty(),
80 "GenericYieldVolCurve: proxy curve requires Target / ShortSwapIndexBase in the curve config.");
81 QL_REQUIRE(!config->proxyTargetSwapIndexBase().empty(),
82 "GenericYieldVolCurve: proxy curve requires Target / SwapIndexBase in the curve config.");
83
84 QuantLib::ext::shared_ptr<SwapIndex> sourceSwapIndexBase;
85 QuantLib::ext::shared_ptr<SwapIndex> sourceShortSwapIndexBase;
86
87 auto it = requiredSwapIndices.find(config->proxySourceShortSwapIndexBase());
88 QL_REQUIRE(it != requiredSwapIndices.end(), "GenericYieldVolCurve: did not find swap index '"
89 << config->proxySourceShortSwapIndexBase()
90 << "' required for curve id '" << config->curveID() << "'");
91 sourceShortSwapIndexBase = it->second;
92
93 it = requiredSwapIndices.find(config->proxySourceSwapIndexBase());
94 QL_REQUIRE(it != requiredSwapIndices.end(), "GenericYieldVolCurve: did not find swap index '"
95 << config->proxySourceSwapIndexBase()
96 << "' required for curve id '" << config->curveID() << "'");
97 sourceSwapIndexBase = it->second;
98
99 it = requiredSwapIndices.find(config->proxyTargetShortSwapIndexBase());
100 QL_REQUIRE(it != requiredSwapIndices.end(), "GenericYieldVolCurve: did not find swap index '"
101 << config->proxyTargetShortSwapIndexBase()
102 << "' required for curve id '" << config->curveID() << "'");
103 shortSwapIndexBase = it->second;
104
105 it = requiredSwapIndices.find(config->proxyTargetSwapIndexBase());
106 QL_REQUIRE(it != requiredSwapIndices.end(), "GenericYieldVolCurve: did not find swap index '"
107 << config->proxyTargetSwapIndexBase()
108 << "' required for curve id '" << config->curveID() << "'");
109 swapIndexBase = it->second;
110
111 auto it2 = requiredVolCurves.find(config->proxySourceCurveId());
112 QL_REQUIRE(it2 != requiredVolCurves.end(), "GenericYieldVolCurve: did not find swaption vol curve '"
113 << config->proxySourceCurveId()
114 << "' required for curve id '" << config->curveID() << "'");
115
116 vol_ = QuantLib::ext::make_shared<QuantExt::ProxySwaptionVolatility>(
117 Handle<SwaptionVolatilityStructure>(it2->second->volTermStructure()), sourceSwapIndexBase,
118 sourceShortSwapIndexBase, swapIndexBase, shortSwapIndexBase);
119
120 } else {
121
122 // Build quote based surface
123
124 // We loop over all market data, looking for quotes that match the configuration
125 // until we found the whole matrix or do not have more quotes in the market data
126
128 switch (config->volatilityType()) {
131 break;
134 break;
137 break;
138 default:
139 QL_FAIL("unexpected volatility type");
140 }
142 Matrix vols(config->optionTenors().size(), config->underlyingTenors().size(), Null<Real>());
143 Matrix shifts(isSln ? vols.rows() : 0, isSln ? vols.columns() : 0, Null<Real>());
144 Size quotesRead = 0, shiftQuotesRead = 0;
145 vector<Period> optionTenors = parseVectorOfValues<Period>(config->optionTenors(), &parsePeriod);
146 vector<Period> underlyingTenors = parseVectorOfValues<Period>(config->underlyingTenors(), &parsePeriod);
147
148 for (auto& p : config->quotes()) {
149 // optional, because we do not require all spread quotes; we check below that we have all atm quotes
150 QuantLib::ext::shared_ptr<MarketDatum> md = loader.get(std::make_pair(p, true), asof);
151 if (md == nullptr)
152 continue;
153 Period expiry, term;
154 if (md->quoteType() == volatilityType && matchAtmQuote(md, expiry, term)) {
155 quotesRead++;
156 Size i = std::find(optionTenors.begin(), optionTenors.end(), expiry) - optionTenors.begin();
157 Size j =
158 std::find(underlyingTenors.begin(), underlyingTenors.end(), term) - underlyingTenors.begin();
159 QL_REQUIRE(i < config->optionTenors().size(),
160 "expiry " << expiry << " not in configuration, this is unexpected");
161 QL_REQUIRE(j < config->underlyingTenors().size(),
162 "term " << term << " not in configuration, this is unexpected");
163 vols[i][j] = md->quote()->value();
164 }
165 if (isSln && md->quoteType() == MarketDatum::QuoteType::SHIFT && matchShiftQuote(md, term)) {
166 shiftQuotesRead++;
167 Size j =
168 std::find(underlyingTenors.begin(), underlyingTenors.end(), term) - underlyingTenors.begin();
169 QL_REQUIRE(j < config->underlyingTenors().size(),
170 "term " << term << " not in configuration, this is unexpected");
171 for (Size i = 0; i < shifts.rows(); ++i)
172 shifts[i][j] = md->quote()->value();
173 }
174 }
175
176 LOG("GenericYieldVolCurve: read " << quotesRead << " vols and " << shiftQuotesRead << " shift quotes");
177
178 // check we have found all requires values
179 bool haveAllAtmValues = true;
180 for (Size i = 0; i < config->optionTenors().size(); ++i) {
181 for (Size j = 0; j < config->underlyingTenors().size(); ++j) {
182 if (vols[i][j] == Null<Real>()) {
183 ALOG("missing ATM vol for " << config->optionTenors()[i] << " / "
184 << config->underlyingTenors()[j]);
185 haveAllAtmValues = false;
186 }
187 if (isSln && shifts[i][j] == Null<Real>()) {
188 ALOG("missing shift for " << config->optionTenors()[i] << " / "
189 << config->underlyingTenors()[j]);
190 haveAllAtmValues = false;
191 }
192 }
193 }
194 QL_REQUIRE(haveAllAtmValues, "Did not find all required quotes to build ATM surface");
195
196 if (!config->swapIndexBase().empty()) {
197 auto it = requiredSwapIndices.find(config->swapIndexBase());
198 if (it != requiredSwapIndices.end())
199 swapIndexBase = it->second;
200 }
201 if (!config->shortSwapIndexBase().empty()) {
202 auto it = requiredSwapIndices.find(config->shortSwapIndexBase());
203 if (it != requiredSwapIndices.end())
204 shortSwapIndexBase = it->second;
205 }
206
207 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atm;
208
209 QL_REQUIRE(quotesRead > 0,
210 "GenericYieldVolCurve: did not read any quotes, are option and swap tenors defined?");
211 if (quotesRead > 1) {
212 atm = QuantLib::ext::shared_ptr<SwaptionVolatilityStructure>(new SwaptionVolatilityMatrix(
213 asof, config->calendar(), config->businessDayConvention(), optionTenors, underlyingTenors, vols,
214 config->dayCounter(),
217 ? QuantLib::Normal
218 : QuantLib::ShiftedLognormal,
219 isSln ? shifts : Matrix(vols.rows(), vols.columns(), 0.0)));
220
221 atm->enableExtrapolation(config->extrapolation() ==
223 TLOG("built atm surface with vols:");
224 TLOGGERSTREAM(vols);
225 if (isSln) {
226 TLOG("built atm surface with shifts:");
227 TLOGGERSTREAM(shifts);
228 }
229 } else {
230 // Constant volatility
231 atm = QuantLib::ext::shared_ptr<SwaptionVolatilityStructure>(new ConstantSwaptionVolatility(
232 asof, config->calendar(), config->businessDayConvention(), vols[0][0], config->dayCounter(),
234 ? QuantLib::Normal
235 : QuantLib::ShiftedLognormal,
236 !shifts.empty() ? shifts[0][0] : 0.0));
237 }
238
239 if (config->dimension() == GenericYieldVolatilityCurveConfig::Dimension::ATM) {
240 // Nothing more to do
241 LOG("Returning ATM surface for config " << config->curveID());
242 vol_ = atm;
243 } else {
244 LOG("Building Cube for config " << config->curveID());
245 vector<Period> smileOptionTenors =
246 parseVectorOfValues<Period>(config->smileOptionTenors(), &parsePeriod);
247 vector<Period> smileUnderlyingTenors =
248 parseVectorOfValues<Period>(config->smileUnderlyingTenors(), &parsePeriod);
249 vector<Spread> spreads = parseVectorOfValues<Real>(config->smileSpreads(), &parseReal);
250
251 // add smile spread 0, if not already existent and sort the spreads
252 if (std::find_if(spreads.begin(), spreads.end(), [](const Real x) { return close_enough(x, 0.0); }) ==
253 spreads.end())
254 spreads.push_back(0.0);
255 std::sort(spreads.begin(), spreads.end());
256
257 vector<vector<bool>> zero(smileOptionTenors.size() * smileUnderlyingTenors.size(),
258 std::vector<bool>(spreads.size(), true));
259
260 if (smileOptionTenors.size() == 0)
261 smileOptionTenors = parseVectorOfValues<Period>(config->optionTenors(), &parsePeriod);
262 if (smileUnderlyingTenors.size() == 0)
263 smileUnderlyingTenors = parseVectorOfValues<Period>(config->underlyingTenors(), &parsePeriod);
264 QL_REQUIRE(spreads.size() > 0, "Need at least 1 strike spread for a SwaptionVolCube");
265
266 Size n = smileOptionTenors.size() * smileUnderlyingTenors.size();
267 vector<vector<Handle<Quote>>> volSpreadHandles(n, vector<Handle<Quote>>(spreads.size()));
268 for (auto& i : volSpreadHandles)
269 for (auto& j : i)
270 j = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0));
271
272 LOG("vol cube smile option tenors " << smileOptionTenors.size());
273 LOG("vol cube smile swap tenors " << smileUnderlyingTenors.size());
274 LOG("vol cube strike spreads " << spreads.size());
275
276 Size spreadQuotesRead = 0;
277 for (auto& p : config->quotes()) {
278 // optional because we do not require all spreads
279 // we default them to zero instead and post process them below
280 QuantLib::ext::shared_ptr<MarketDatum> md = loader.get(std::make_pair(p, true), asof);
281 if (md == nullptr)
282 continue;
283 Period expiry, term;
284 Real strike;
285 if (md->quoteType() == volatilityType && matchSmileQuote(md, expiry, term, strike)) {
286
287 Size i = std::find(smileOptionTenors.begin(), smileOptionTenors.end(), expiry) -
288 smileOptionTenors.begin();
289 Size j = std::find(smileUnderlyingTenors.begin(), smileUnderlyingTenors.end(), term) -
290 smileUnderlyingTenors.begin();
291 // In the MarketDatum we call it a strike, but it's really a spread
292 Size k = std::find(spreads.begin(), spreads.end(), strike) - spreads.begin();
293 QL_REQUIRE(i < smileOptionTenors.size(),
294 "expiry " << expiry << " not in configuration, this is unexpected");
295 QL_REQUIRE(j < smileUnderlyingTenors.size(),
296 "term " << term << " not in configuration, this is unexpected");
297 QL_REQUIRE(k < spreads.size(),
298 "strike " << strike << " not in configuration, this is unexpected");
299
300 spreadQuotesRead++;
301 // Assume quotes are absolute vols by strike so construct the vol spreads here
302 Volatility atmVol = atm->volatility(smileOptionTenors[i], smileUnderlyingTenors[j], 0.0);
303 volSpreadHandles[i * smileUnderlyingTenors.size() + j][k] =
304 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(md->quote()->value() - atmVol));
305 zero[i * smileUnderlyingTenors.size() + j][k] = close_enough(md->quote()->value(), 0.0);
306 }
307 }
308 LOG("Read " << spreadQuotesRead << " quotes for VolCube.");
309
310 // post processing: extrapolate leftmost non-zero value flat to the left and overwrite
311 // zero values
312 for (Size i = 0; i < smileOptionTenors.size(); ++i) {
313 for (Size j = 0; j < smileUnderlyingTenors.size(); ++j) {
314 Real lastNonZeroValue = 0.0;
315 for (Size k = 0; k < spreads.size(); ++k) {
316 QuantLib::ext::shared_ptr<SimpleQuote> q = QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(
317 *volSpreadHandles[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k]);
318 QL_REQUIRE(q, "internal error: expected simple quote");
319 // do not overwrite vol spread for zero strike spread (ATM point)
320 if (zero[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k] &&
321 !close_enough(spreads[spreads.size() - 1 - k], 0.0)) {
322 q->setValue(lastNonZeroValue);
323 DLOG("Overwrite vol spread for " << config->curveID() << "/" << smileOptionTenors[i]
324 << "/" << smileUnderlyingTenors[j] << "/"
325 << spreads[spreads.size() - 1 - k] << " with "
326 << lastNonZeroValue << " since market quote is zero");
327 }
328 // update last non-zero value
329 if (!zero[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k]) {
330 lastNonZeroValue = q->value();
331 }
332 }
333 }
334 }
335
336 // log vols
337 for (Size i = 0; i < smileOptionTenors.size(); ++i) {
338 for (Size j = 0; j < smileUnderlyingTenors.size(); ++j) {
339 ostringstream o;
340 for (Size k = 0; k < spreads.size(); ++k) {
341 o << volSpreadHandles[i * smileUnderlyingTenors.size() + j][k]->value() +
342 atm->volatility(smileOptionTenors[i], smileUnderlyingTenors[j], 0.0)
343 << " ";
344 }
345 DLOG("Vols for " << smileOptionTenors[i] << "/" << smileUnderlyingTenors[j] << ": " << o.str());
346 }
347 }
348
349 // check we have swap indices
350 QL_REQUIRE(swapIndexBase, "Unable to find SwapIndex " << config->swapIndexBase());
351 QL_REQUIRE(shortSwapIndexBase, "Unable to find ShortSwapIndex " << config->shortSwapIndexBase());
352
353 Handle<SwaptionVolatilityStructure> hATM(atm);
354 QuantLib::ext::shared_ptr<QuantLib::SwaptionVolatilityCube> cube;
355 if (config->interpolation() == GenericYieldVolatilityCurveConfig::Interpolation::Linear) {
356 cube = QuantLib::ext::make_shared<QuantExt::SwaptionVolCube2>(
357 hATM, smileOptionTenors, smileUnderlyingTenors, spreads, volSpreadHandles, swapIndexBase,
358 shortSwapIndexBase, false,
360 cube->enableExtrapolation();
361 } else {
362 std::map<std::pair<QuantLib::Period, QuantLib::Period>, std::vector<std::pair<Real, bool>>>
363 initialModelParameters;
364 Size maxCalibrationAttempts = 10;
365 Real exitEarlyErrorThreshold = 0.005;
366 Real maxAcceptableError = 0.05;
367 if (config->parametricSmileConfiguration()) {
368 auto alpha = config->parametricSmileConfiguration()->parameter("alpha");
369 auto beta = config->parametricSmileConfiguration()->parameter("beta");
370 auto nu = config->parametricSmileConfiguration()->parameter("nu");
371 auto rho = config->parametricSmileConfiguration()->parameter("rho");
372 QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
373 alpha.initialValue.size() == nu.initialValue.size() &&
374 alpha.initialValue.size() == rho.initialValue.size(),
375 "GenericYieldVolCurve: parametric smile config: alpha size ("
376 << alpha.initialValue.size() << ") beta size (" << beta.initialValue.size()
377 << ") nu size (" << nu.initialValue.size() << ") rho size ("
378 << rho.initialValue.size() << ") must match");
379 QL_REQUIRE(alpha.initialValue.size() == 1 ||
380 alpha.initialValue.size() == optionTenors.size() * underlyingTenors.size(),
381 "GenericYieldVolCurve: parametric smile config: alpha, beta, nu, rho size ("
382 << alpha.initialValue.size() << ") must match product of option tenors ("
383 << optionTenors.size() << ") and swap tenors (" << underlyingTenors.size()
384 << ") = " << optionTenors.size() * underlyingTenors.size() << ")");
385 for (Size i = 0; i < optionTenors.size(); ++i) {
386 for (Size j = 0; j < underlyingTenors.size(); ++j) {
387 std::vector<std::pair<Real, bool>> tmp;
388 Size idx = alpha.initialValue.size() == 1 ? 0 : i * underlyingTenors.size() + j;
389 tmp.push_back(std::make_pair(alpha.initialValue[idx], alpha.isFixed));
390 tmp.push_back(std::make_pair(beta.initialValue[idx], beta.isFixed));
391 tmp.push_back(std::make_pair(nu.initialValue[idx], nu.isFixed));
392 tmp.push_back(std::make_pair(rho.initialValue[idx], rho.isFixed));
393 initialModelParameters[std::make_pair(optionTenors[i], underlyingTenors[j])] = tmp;
394 }
395 }
396 maxCalibrationAttempts =
397 config->parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
398 exitEarlyErrorThreshold =
399 config->parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
400 maxAcceptableError = config->parametricSmileConfiguration()->calibration().maxAcceptableError;
401 }
402 cube = QuantLib::ext::make_shared<QuantExt::SwaptionSabrCube>(
403 hATM, smileOptionTenors, smileUnderlyingTenors, optionTenors, underlyingTenors, spreads,
404 volSpreadHandles, swapIndexBase, shortSwapIndexBase,
405 QuantExt::SabrParametricVolatility::ModelVariant(config->interpolation()),
406 config->outputVolatilityType() == GenericYieldVolatilityCurveConfig::VolatilityType::Normal
407 ? QuantLib::Normal
408 : QuantLib::ShiftedLognormal,
409 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
410 }
411
412 // Wrap it in a SwaptionVolCubeWithATM
413 vol_ = QuantLib::ext::make_shared<QuantExt::SwaptionVolCubeWithATM>(cube);
414 }
415 }
416
417 // build calibration info
418
419 if (buildCalibrationInfo) {
420
421 DLOG("Building calibration info for generic yield vols");
422
423 if (swapIndexBase == nullptr || shortSwapIndexBase == nullptr) {
424 WLOG("no swap indexes given for " << config->curveID() << ", skip building calibraiton info");
425 return;
426 }
427
428 ReportConfig rc = effectiveReportConfig(curveConfigs.reportConfigIrSwaptionVols(), config->reportConfig());
429
430 bool reportOnStrikeGrid = *rc.reportOnStrikeGrid();
431 bool reportOnStrikeSpreadGrid = *rc.reportOnStrikeSpreadGrid();
432 std::vector<Real> strikes = *rc.strikes();
433 std::vector<Real> strikeSpreads = *rc.strikeSpreads();
434 std::vector<Period> expiries = *rc.expiries();
435 std::vector<Period> underlyingTenorsReport = *rc.underlyingTenors();
436
437 calibrationInfo_ = QuantLib::ext::make_shared<IrVolCalibrationInfo>();
438
439 calibrationInfo_->dayCounter = config->dayCounter().empty() ? "na" : config->dayCounter().name();
440 calibrationInfo_->calendar = config->calendar().empty() ? "na" : config->calendar().name();
441 calibrationInfo_->volatilityType = ore::data::to_string(vol_->volatilityType());
442 calibrationInfo_->underlyingTenors = underlyingTenorsReport;
443
444 std::vector<Real> times;
445 std::vector<std::vector<Real>> forwards;
446 for (auto const& p : expiries) {
447 Date d = vol_->optionDateFromTenor(p);
448 calibrationInfo_->expiryDates.push_back(d);
449 times.push_back(vol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, d)
450 : vol_->timeFromReference(d));
451 forwards.push_back(std::vector<Real>());
452 for (auto const& u : underlyingTenorsReport) {
453 forwards.back().push_back(atmStrike(d, u, swapIndexBase, shortSwapIndexBase));
454 }
455 }
456
457 calibrationInfo_->times = times;
458 calibrationInfo_->forwards = forwards;
459
460 std::vector<std::vector<std::vector<Real>>> callPricesStrike(
461 times.size(),
462 std::vector<std::vector<Real>>(underlyingTenorsReport.size(), std::vector<Real>(strikes.size(), 0.0)));
463 std::vector<std::vector<std::vector<Real>>> callPricesStrikeSpread(
464 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
465 std::vector<Real>(strikeSpreads.size(), 0.0)));
466
467 calibrationInfo_->isArbitrageFree = true;
468
469 if (reportOnStrikeGrid) {
470 calibrationInfo_->strikes = strikes;
471 calibrationInfo_->strikeGridStrikes = std::vector<std::vector<std::vector<Real>>>(
472 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
473 std::vector<Real>(strikes.size(), 0.0)));
474 calibrationInfo_->strikeGridProb = std::vector<std::vector<std::vector<Real>>>(
475 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
476 std::vector<Real>(strikes.size(), 0.0)));
477 calibrationInfo_->strikeGridImpliedVolatility = std::vector<std::vector<std::vector<Real>>>(
478 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
479 std::vector<Real>(strikes.size(), 0.0)));
480 calibrationInfo_->strikeGridCallSpreadArbitrage = std::vector<std::vector<std::vector<bool>>>(
481 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
482 std::vector<bool>(strikes.size(), true)));
483 calibrationInfo_->strikeGridButterflyArbitrage = std::vector<std::vector<std::vector<bool>>>(
484 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
485 std::vector<bool>(strikes.size(), true)));
486 TLOG("Strike cube arbitrage analysis result:");
487 for (Size u = 0; u < underlyingTenorsReport.size(); ++u) {
488 TLOG("Underlying tenor " << underlyingTenorsReport[u]);
489 for (Size i = 0; i < times.size(); ++i) {
490 Real t = times[i];
491 Real shift = vol_->volatilityType() == Normal
492 ? 0.0
493 : vol_->shift(expiries[i], underlyingTenorsReport[u]);
494 bool validSlice = true;
495 for (Size j = 0; j < strikes.size(); ++j) {
496 try {
497 Real stddev = 0.0;
498 if (vol_->volatilityType() == ShiftedLognormal) {
499 if ((strikes[j] > -shift || close_enough(strikes[j], -shift)) &&
500 (forwards[i][u] > -shift || close_enough(forwards[i][u], -shift))) {
501 stddev = std::sqrt(
502 vol_->blackVariance(expiries[i], underlyingTenorsReport[u], strikes[j]));
503 callPricesStrike[i][u][j] =
504 blackFormula(Option::Type::Call, strikes[j], forwards[i][u], stddev);
505 }
506 } else {
507 stddev = std::sqrt(
508 vol_->blackVariance(expiries[i], underlyingTenorsReport[u], strikes[j]));
509 callPricesStrike[i][u][j] =
510 bachelierBlackFormula(Option::Type::Call, strikes[j], forwards[i][u], stddev);
511 }
512 calibrationInfo_->strikeGridStrikes[i][u][j] = strikes[j];
513 calibrationInfo_->strikeGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
514 } catch (const std::exception& e) {
515 validSlice = false;
516 TLOG("error for time " << t << " strike " << strikes[j] << ": " << e.what());
517 }
518 }
519 if (validSlice) {
520 try {
522 calibrationInfo_->strikeGridStrikes[i][u], forwards[i][u], callPricesStrike[i][u],
523 vol_->volatilityType(), shift);
524 calibrationInfo_->strikeGridCallSpreadArbitrage[i][u] = cm.callSpreadArbitrage();
525 calibrationInfo_->strikeGridButterflyArbitrage[i][u] = cm.butterflyArbitrage();
526 if (!cm.arbitrageFree())
527 calibrationInfo_->isArbitrageFree = false;
528 calibrationInfo_->strikeGridProb[i][u] = cm.density();
530 } catch (const std::exception& e) {
531 TLOG("error for time " << t << ": " << e.what());
532 calibrationInfo_->isArbitrageFree = false;
533 TLOGGERSTREAM("..(invalid slice)..");
534 }
535 } else {
536 TLOGGERSTREAM("..(invalid slice)..");
537 }
538 }
539 }
540 TLOG("Strike cube arbitrage analysis completed.");
541 }
542
543 if (reportOnStrikeSpreadGrid) {
544 calibrationInfo_->strikeSpreads = strikeSpreads;
545 calibrationInfo_->strikeSpreadGridStrikes = std::vector<std::vector<std::vector<Real>>>(
546 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
547 std::vector<Real>(strikeSpreads.size(), 0.0)));
548 calibrationInfo_->strikeSpreadGridProb = std::vector<std::vector<std::vector<Real>>>(
549 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
550 std::vector<Real>(strikeSpreads.size(), 0.0)));
551 calibrationInfo_->strikeSpreadGridImpliedVolatility = std::vector<std::vector<std::vector<Real>>>(
552 times.size(), std::vector<std::vector<Real>>(underlyingTenorsReport.size(),
553 std::vector<Real>(strikeSpreads.size(), 0.0)));
554 calibrationInfo_->strikeSpreadGridCallSpreadArbitrage = std::vector<std::vector<std::vector<bool>>>(
555 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
556 std::vector<bool>(strikeSpreads.size(), true)));
557 calibrationInfo_->strikeSpreadGridButterflyArbitrage = std::vector<std::vector<std::vector<bool>>>(
558 times.size(), std::vector<std::vector<bool>>(underlyingTenorsReport.size(),
559 std::vector<bool>(strikeSpreads.size(), true)));
560 TLOG("Strike Spread cube arbitrage analysis result:");
561 for (Size u = 0; u < underlyingTenorsReport.size(); ++u) {
562 TLOG("Underlying tenor " << underlyingTenorsReport[u]);
563 for (Size i = 0; i < times.size(); ++i) {
564 Real t = times[i];
565 Real shift = vol_->volatilityType() == Normal
566 ? 0.0
567 : vol_->shift(expiries[i], underlyingTenorsReport[u]);
568 bool validSlice = true;
569 for (Size j = 0; j < strikeSpreads.size(); ++j) {
570 Real strike = forwards[i][u] + strikeSpreads[j];
571 try {
572 Real stddev = 0.0;
573 if (vol_->volatilityType() == ShiftedLognormal) {
574 if ((strike > -shift || close_enough(strike, -shift)) &&
575 (forwards[i][u] > -shift || close_enough(forwards[i][u], -shift))) {
576 stddev = std::sqrt(
577 vol_->blackVariance(expiries[i], underlyingTenorsReport[u], strike));
578 callPricesStrikeSpread[i][u][j] =
579 blackFormula(Option::Type::Call, strike, forwards[i][u], stddev);
580 }
581 } else {
582 stddev =
583 std::sqrt(vol_->blackVariance(expiries[i], underlyingTenorsReport[u], strike));
584 callPricesStrikeSpread[i][u][j] =
585 bachelierBlackFormula(Option::Type::Call, strike, forwards[i][u], stddev);
586 }
587 calibrationInfo_->strikeSpreadGridStrikes[i][u][j] = strike;
588 calibrationInfo_->strikeSpreadGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
589 } catch (const std::exception& e) {
590 validSlice = false;
591 TLOG("error for time " << t << " strike spread " << strikeSpreads[j] << " strike "
592 << strike << ": " << e.what());
593 }
594 }
595 if (validSlice) {
596 try {
598 calibrationInfo_->strikeSpreadGridStrikes[i][u], forwards[i][u],
599 callPricesStrikeSpread[i][u], vol_->volatilityType(), shift);
600 calibrationInfo_->strikeSpreadGridCallSpreadArbitrage[i][u] = cm.callSpreadArbitrage();
601 calibrationInfo_->strikeSpreadGridButterflyArbitrage[i][u] = cm.butterflyArbitrage();
602 if (!cm.arbitrageFree())
603 calibrationInfo_->isArbitrageFree = false;
604 calibrationInfo_->strikeSpreadGridProb[i][u] = cm.density();
606 } catch (const std::exception& e) {
607 TLOG("error for time " << t << ": " << e.what());
608 calibrationInfo_->isArbitrageFree = false;
609 TLOGGERSTREAM("..(invalid slice)..");
610 }
611 } else {
612 TLOGGERSTREAM("..(invalid slice)..");
613 }
614 }
615 }
616 TLOG("Strike Spread cube arbitrage analysis completed.");
617 }
618
619 DLOG("Building calibration info generic yield vols completed.");
620
621 // output SABR calibration to log, if SABR was used
622
623 if (auto sw = QuantLib::ext::dynamic_pointer_cast<QuantExt::SwaptionVolCubeWithATM>(vol_)) {
624 if (auto sabr = QuantLib::ext::dynamic_pointer_cast<QuantExt::SwaptionSabrCube>(sw->cube())) {
625 if (auto p = QuantLib::ext::dynamic_pointer_cast<QuantExt::SabrParametricVolatility>(
626 sabr->parametricVolatility())) {
627 DLOG("SABR parameters:");
628 DLOG("alpha (rows = option tenors, cols = underlying lengths):");
629 DLOGGERSTREAM(transpose(p->alpha()));
630 DLOG("beta (rows = option tenors, cols = underlying lengths):");
631 DLOGGERSTREAM(transpose(p->beta()));
632 DLOG("nu (rows = option tenors, cols = underlying lengths):");
633 DLOGGERSTREAM(transpose(p->nu()));
634 DLOG("rho (rows = option tenors, cols = underlying lengths):");
635 DLOGGERSTREAM(transpose(p->rho()));
636 DLOG("lognormal shift (rows = option tenors, cols = underlying lengths):");
637 DLOGGERSTREAM(transpose(p->lognormalShift()));
638 DLOG("calibration attempts (rows = option tenors, cols = underlying lengths):");
639 DLOGGERSTREAM(transpose(p->numberOfCalibrationAttempts()));
640 DLOG("calibration error (rows = option tenors, cols = underlying lengths, rmse of relative "
641 "errors w.r.t. max of sabr variant's preferred quotation type, i.e. nvol, slnvol, "
642 "premium:");
643 DLOGGERSTREAM(transpose(p->calibrationError()));
644 DLOG("isInterpolated (rows = option tenors, cols = underlying lengths, 1 means calibration "
645 "failed and point is interpolated):");
646 DLOGGERSTREAM(transpose(p->isInterpolated()));
647 }
648 }
649 }
650 }
651
652 } catch (std::exception& e) {
653 QL_FAIL("generic yield volatility curve building failed for curve " << config->curveID() << " on date "
654 << io::iso_date(asof) << ": " << e.what());
655 } catch (...) {
656 QL_FAIL("generic yield vol curve building failed: unknown error");
657 }
658}
659} // namespace data
660} // namespace ore
const std::vector< bool > & butterflyArbitrage() const
const std::vector< bool > & callSpreadArbitrage() const
const std::vector< Real > & density() const
Container class for all Curve Configurations.
QuantLib::ext::shared_ptr< IrVolCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > vol_
GenericYieldVolCurve()
Default constructor.
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
QuoteType
Supported market quote types.
const boost::optional< std::vector< Period > > & underlyingTenors() const
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
Swaption volatility curve configuration classes.
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 LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define ALOG(text)
Logging Macro (Level = Alert)
Definition: log.hpp:544
#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
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::string arbitrageAsString(const CarrMadanMarginalProbabilityClass &cm)
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
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.
md report and arbitrage check configuration
vector< Real > strikes
vector< string > curveConfigs
string conversion utilities