Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
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.