Logo
Fully annotated reference manual - version 1.8.12
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
List of all members
GenericYieldVolCurve Class Reference

Wrapper class for building Generic yield volatility structures. More...

#include <ored/marketdata/genericyieldvolcurve.hpp>

+ Inheritance diagram for GenericYieldVolCurve:
+ Collaboration diagram for GenericYieldVolCurve:

Public Member Functions

Constructors
 GenericYieldVolCurve ()
 Default constructor. More...
 
virtual ~GenericYieldVolCurve ()
 dtor More...
 
 GenericYieldVolCurve (const Date &asof, const Loader &loader, const CurveConfigurations &curveConfigs, const QuantLib::ext::shared_ptr< GenericYieldVolatilityCurveConfig > &config, const map< string, QuantLib::ext::shared_ptr< SwapIndex > > &requiredSwapIndices, const map< string, QuantLib::ext::shared_ptr< GenericYieldVolCurve > > &requiredVolCurves, const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &expiry, Period &term)> &matchAtmQuote, const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &expiry, Period &term, Real &strike)> &matchSmileQuote, const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &term)> &matchShiftQuote, const bool buildCalibrationInfo)
 Detailed constructor. More...
 

Inspectors

QuantLib::ext::shared_ptr< SwaptionVolatilityStructurevol_
 
QuantLib::ext::shared_ptr< IrVolCalibrationInfocalibrationInfo_
 
const QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > & volTermStructure ()
 
QuantLib::ext::shared_ptr< IrVolCalibrationInfocalibrationInfo () const
 

Detailed Description

Wrapper class for building Generic yield volatility structures.

Definition at line 43 of file genericyieldvolcurve.hpp.

Constructor & Destructor Documentation

◆ GenericYieldVolCurve() [1/2]

Default constructor.

Definition at line 48 of file genericyieldvolcurve.hpp.

48{}

◆ ~GenericYieldVolCurve()

virtual ~GenericYieldVolCurve ( )
virtual

dtor

Definition at line 50 of file genericyieldvolcurve.hpp.

50{}

◆ GenericYieldVolCurve() [2/2]

GenericYieldVolCurve ( const Date &  asof,
const Loader loader,
const CurveConfigurations curveConfigs,
const QuantLib::ext::shared_ptr< GenericYieldVolatilityCurveConfig > &  config,
const map< string, QuantLib::ext::shared_ptr< SwapIndex > > &  requiredSwapIndices,
const map< string, QuantLib::ext::shared_ptr< GenericYieldVolCurve > > &  requiredVolCurves,
const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &expiry, Period &term)> &  matchAtmQuote,
const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &expiry, Period &term, Real &strike)> &  matchSmileQuote,
const std::function< bool(const QuantLib::ext::shared_ptr< MarketDatum > &md, Period &term)> &  matchShiftQuote,
const bool  buildCalibrationInfo 
)

Detailed constructor.

Definition at line 56 of file genericyieldvolcurve.cpp.

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 // 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}
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.
Definition: parsers.cpp:171
#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
vector< Real > strikes
vector< string > curveConfigs
+ Here is the call graph for this function:

Member Function Documentation

◆ volTermStructure()

const QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > & volTermStructure ( )

Definition at line 67 of file genericyieldvolcurve.hpp.

67{ return vol_; }

◆ calibrationInfo()

QuantLib::ext::shared_ptr< IrVolCalibrationInfo > calibrationInfo ( ) const

Definition at line 68 of file genericyieldvolcurve.hpp.

68{ return calibrationInfo_; }

Member Data Documentation

◆ vol_

QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> vol_
private

Definition at line 72 of file genericyieldvolcurve.hpp.

◆ calibrationInfo_

QuantLib::ext::shared_ptr<IrVolCalibrationInfo> calibrationInfo_
private

Definition at line 73 of file genericyieldvolcurve.hpp.