Detailed constructor.
65 {
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
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
123
124
125
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
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 }
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
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:");
225 if (isSln) {
226 TLOG(
"built atm surface with shifts:");
228 }
229 } else {
230
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
240
241 LOG(
"Returning ATM surface for config " << config->curveID());
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
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
279
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
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
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
311
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
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");
327 }
328
329 if (!zero[i * smileUnderlyingTenors.size() + j][spreads.size() - 1 - k]) {
330 lastNonZeroValue = q->value();
331 }
332 }
333 }
334 }
335
336
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
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;
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()),
407 ? QuantLib::Normal
408 : QuantLib::ShiftedLognormal,
409 initialModelParameters, maxCalibrationAttempts, exitEarlyErrorThreshold, maxAcceptableError);
410 }
411
412
413 vol_ = QuantLib::ext::make_shared<QuantExt::SwaptionVolCubeWithATM>(cube);
414 }
415 }
416
417
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
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
438
439 calibrationInfo_->dayCounter = config->dayCounter().empty() ?
"na" : config->dayCounter().name();
440 calibrationInfo_->calendar = config->calendar().empty() ?
"na" : config->calendar().name();
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);
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
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
468
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) {
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 }
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())
530 } catch (const std::exception& e) {
531 TLOG(
"error for time " << t <<
": " << e.what());
534 }
535 } else {
537 }
538 }
539 }
540 TLOG(
"Strike cube arbitrage analysis completed.");
541 }
542
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) {
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 }
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 {
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())
606 } catch (const std::exception& e) {
607 TLOG(
"error for time " << t <<
": " << e.what());
610 }
611 } else {
613 }
614 }
615 }
616 TLOG(
"Strike Spread cube arbitrage analysis completed.");
617 }
618
619 DLOG(
"Building calibration info generic yield vols completed.");
620
621
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):");
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, "
642 "premium:");
644 DLOG(
"isInterpolated (rows = option tenors, cols = underlying lengths, 1 means calibration "
645 "failed and point is interpolated):");
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}
QuantLib::ext::shared_ptr< IrVolCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > vol_
QuoteType
Supported market quote types.
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
#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)
vector< string > curveConfigs