28#include <qle/termstructures/swaptionsabrcube.hpp>
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>
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));
51 return shortSwapIndexBase->clone(swapTenor)->fixing(shortSwapIndexBase->fixingCalendar().adjust(optionD));
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)>&
64 const std::function<
bool(
const QuantLib::ext::shared_ptr<MarketDatum>& md, Period& term)>& matchShiftQuote,
65 const bool buildCalibrationInfo) {
68 QuantLib::ext::shared_ptr<SwapIndex> swapIndexBase;
69 QuantLib::ext::shared_ptr<SwapIndex> shortSwapIndexBase;
71 if (!config->proxySourceCurveId().empty()) {
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.");
84 QuantLib::ext::shared_ptr<SwapIndex> sourceSwapIndexBase;
85 QuantLib::ext::shared_ptr<SwapIndex> sourceShortSwapIndexBase;
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;
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;
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;
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;
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() <<
"'");
116 vol_ = QuantLib::ext::make_shared<QuantExt::ProxySwaptionVolatility>(
117 Handle<SwaptionVolatilityStructure>(it2->second->volTermStructure()), sourceSwapIndexBase,
118 sourceShortSwapIndexBase, swapIndexBase, shortSwapIndexBase);
128 switch (config->volatilityType()) {
139 QL_FAIL(
"unexpected volatility type");
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);
148 for (
auto& p : config->quotes()) {
150 QuantLib::ext::shared_ptr<MarketDatum> md = loader.
get(std::make_pair(p,
true), asof);
154 if (md->quoteType() ==
volatilityType && matchAtmQuote(md, expiry, term)) {
156 Size i = std::find(optionTenors.begin(), optionTenors.end(), expiry) - optionTenors.begin();
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();
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();
176 LOG(
"GenericYieldVolCurve: read " << quotesRead <<
" vols and " << shiftQuotesRead <<
" shift quotes");
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;
187 if (isSln && shifts[i][j] == Null<Real>()) {
188 ALOG(
"missing shift for " << config->optionTenors()[i] <<
" / "
189 << config->underlyingTenors()[j]);
190 haveAllAtmValues =
false;
194 QL_REQUIRE(haveAllAtmValues,
"Did not find all required quotes to build ATM surface");
196 if (!config->swapIndexBase().empty()) {
197 auto it = requiredSwapIndices.find(config->swapIndexBase());
198 if (it != requiredSwapIndices.end())
199 swapIndexBase = it->second;
201 if (!config->shortSwapIndexBase().empty()) {
202 auto it = requiredSwapIndices.find(config->shortSwapIndexBase());
203 if (it != requiredSwapIndices.end())
204 shortSwapIndexBase = it->second;
207 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atm;
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(),
218 : QuantLib::ShiftedLognormal,
219 isSln ? shifts : Matrix(vols.rows(), vols.columns(), 0.0)));
221 atm->enableExtrapolation(config->extrapolation() ==
223 TLOG(
"built atm surface with vols:");
226 TLOG(
"built atm surface with shifts:");
231 atm = QuantLib::ext::shared_ptr<SwaptionVolatilityStructure>(
new ConstantSwaptionVolatility(
232 asof, config->calendar(), config->businessDayConvention(), vols[0][0], config->dayCounter(),
235 : QuantLib::ShiftedLognormal,
236 !shifts.empty() ? shifts[0][0] : 0.0));
241 LOG(
"Returning ATM surface for config " << config->curveID());
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);
252 if (std::find_if(spreads.begin(), spreads.end(), [](
const Real x) { return close_enough(x, 0.0); }) ==
254 spreads.push_back(0.0);
255 std::sort(spreads.begin(), spreads.end());
257 vector<vector<bool>> zero(smileOptionTenors.size() * smileUnderlyingTenors.size(),
258 std::vector<bool>(spreads.size(),
true));
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");
266 Size n = smileOptionTenors.size() * smileUnderlyingTenors.size();
267 vector<vector<Handle<Quote>>> volSpreadHandles(n, vector<Handle<Quote>>(spreads.size()));
268 for (
auto& i : volSpreadHandles)
270 j = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0));
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());
276 Size spreadQuotesRead = 0;
277 for (
auto& p : config->quotes()) {
280 QuantLib::ext::shared_ptr<MarketDatum> md = loader.
get(std::make_pair(p,
true), asof);
285 if (md->quoteType() ==
volatilityType && matchSmileQuote(md, expiry, term, strike)) {
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();
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");
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);
308 LOG(
"Read " << spreadQuotesRead <<
" quotes for VolCube.");
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");
320 if (zero[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k] &&
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");
329 if (!zero[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k]) {
330 lastNonZeroValue = q->value();
337 for (Size i = 0; i < smileOptionTenors.size(); ++i) {
338 for (Size j = 0; j < smileUnderlyingTenors.size(); ++j) {
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)
345 DLOG(
"Vols for " << smileOptionTenors[i] <<
"/" << smileUnderlyingTenors[j] <<
": " << o.str());
350 QL_REQUIRE(swapIndexBase,
"Unable to find SwapIndex " << config->swapIndexBase());
351 QL_REQUIRE(shortSwapIndexBase,
"Unable to find ShortSwapIndex " << config->shortSwapIndexBase());
353 Handle<SwaptionVolatilityStructure> hATM(atm);
354 QuantLib::ext::shared_ptr<QuantLib::SwaptionVolatilityCube> cube;
356 cube = QuantLib::ext::make_shared<QuantExt::SwaptionVolCube2>(
357 hATM, smileOptionTenors, smileUnderlyingTenors, spreads, volSpreadHandles, swapIndexBase,
358 shortSwapIndexBase,
false,
360 cube->enableExtrapolation();
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;
396 maxCalibrationAttempts =
397 config->parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
398 exitEarlyErrorThreshold =
399 config->parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
400 maxAcceptableError = config->parametricSmileConfiguration()->calibration().maxAcceptableError;
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()),
408 : QuantLib::ShiftedLognormal,
409 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
413 vol_ = QuantLib::ext::make_shared<QuantExt::SwaptionVolCubeWithATM>(cube);
419 if (buildCalibrationInfo) {
421 DLOG(
"Building calibration info for generic yield vols");
423 if (swapIndexBase ==
nullptr || shortSwapIndexBase ==
nullptr) {
424 WLOG(
"no swap indexes given for " << config->curveID() <<
", skip building calibraiton info");
434 std::vector<Period> expiries = *rc.
expiries();
439 calibrationInfo_->dayCounter = config->dayCounter().empty() ?
"na" : config->dayCounter().name();
440 calibrationInfo_->calendar = config->calendar().empty() ?
"na" : config->calendar().name();
444 std::vector<Real> times;
445 std::vector<std::vector<Real>> forwards;
446 for (
auto const& p : expiries) {
447 Date d =
vol_->optionDateFromTenor(p);
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));
460 std::vector<std::vector<std::vector<Real>>> callPricesStrike(
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)));
469 if (reportOnStrikeGrid) {
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) {
491 Real shift =
vol_->volatilityType() == Normal
493 :
vol_->shift(expiries[i], underlyingTenorsReport[u]);
494 bool validSlice =
true;
495 for (Size j = 0; j <
strikes.size(); ++j) {
498 if (
vol_->volatilityType() == ShiftedLognormal) {
500 (forwards[i][u] > -shift ||
close_enough(forwards[i][u], -shift))) {
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);
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);
513 calibrationInfo_->strikeGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
514 }
catch (
const std::exception& e) {
516 TLOG(
"error for time " << t <<
" strike " <<
strikes[j] <<
": " << e.what());
522 calibrationInfo_->strikeGridStrikes[i][u], forwards[i][u], callPricesStrike[i][u],
523 vol_->volatilityType(), shift);
530 }
catch (
const std::exception& e) {
531 TLOG(
"error for time " << t <<
": " << e.what());
540 TLOG(
"Strike cube arbitrage analysis completed.");
543 if (reportOnStrikeSpreadGrid) {
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) {
565 Real shift =
vol_->volatilityType() == Normal
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];
573 if (
vol_->volatilityType() == ShiftedLognormal) {
574 if ((strike > -shift ||
close_enough(strike, -shift)) &&
575 (forwards[i][u] > -shift ||
close_enough(forwards[i][u], -shift))) {
577 vol_->blackVariance(expiries[i], underlyingTenorsReport[u], strike));
578 callPricesStrikeSpread[i][u][j] =
579 blackFormula(Option::Type::Call, strike, forwards[i][u], 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);
588 calibrationInfo_->strikeSpreadGridImpliedVolatility[i][u][j] = stddev / std::sqrt(t);
589 }
catch (
const std::exception& e) {
591 TLOG(
"error for time " << t <<
" strike spread " << strikeSpreads[j] <<
" strike "
592 << strike <<
": " << e.what());
599 callPricesStrikeSpread[i][u],
vol_->volatilityType(), shift);
606 }
catch (
const std::exception& e) {
607 TLOG(
"error for time " << t <<
": " << e.what());
616 TLOG(
"Strike Spread cube arbitrage analysis completed.");
619 DLOG(
"Building calibration info generic yield vols completed.");
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):");
630 DLOG(
"beta (rows = option tenors, cols = underlying lengths):");
632 DLOG(
"nu (rows = option tenors, cols = underlying lengths):");
634 DLOG(
"rho (rows = option tenors, cols = underlying lengths):");
636 DLOG(
"lognormal shift (rows = option tenors, cols = underlying lengths):");
638 DLOG(
"calibration attempts (rows = option tenors, cols = underlying lengths):");
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, "
644 DLOG(
"isInterpolated (rows = option tenors, cols = underlying lengths, 1 means calibration "
645 "failed and point is interpolated):");
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());
656 QL_FAIL(
"generic yield vol curve building failed: unknown error");
const std::vector< bool > & butterflyArbitrage() const
bool arbitrageFree() 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.
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
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.
Real parseReal(const string &s)
Convert text to Real.
Classes and functions for log message handling.
#define LOG(text)
Logging Macro (Level = Notice)
#define DLOG(text)
Logging Macro (Level = Debug)
#define ALOG(text)
Logging Macro (Level = Alert)
#define TLOGGERSTREAM(text)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
#define DLOGGERSTREAM(text)
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)
std::string to_string(const LocationInfo &l)
Serializable Credit Default Swap.
Map text representations to QuantLib/QuantExt types.
md report and arbitrage check configuration
vector< string > curveConfigs
string conversion utilities