Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
equityvolcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 Copyright (C) 2022 Skandinaviska Enskilda Banken AB (publ)
4 All rights reserved.
5
6 This file is part of ORE, a free-software/open-source library
7 for transparent pricing and risk analysis - http://opensourcerisk.org
8
9 ORE is free software: you can redistribute it and/or modify it
10 under the terms of the Modified BSD License. You should have received a
11 copy of the license along with this program.
12 The license is also available online at <http://opensourcerisk.org>
13
14 This program is distributed on the basis that it will form a useful
15 contribution to risk analytics and model standardisation, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include <algorithm>
21#include <boost/algorithm/string/join.hpp>
22#include <boost/range/adaptor/indexed.hpp>
23#include <boost/range/adaptor/transformed.hpp>
31#include <ql/math/interpolations/loginterpolation.hpp>
32#include <ql/math/matrix.hpp>
33#include <ql/pricingengines/blackformula.hpp>
34#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
35#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
36#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
37#include <ql/time/calendars/weekendsonly.hpp>
38#include <ql/time/daycounters/actual365fixed.hpp>
49
50using namespace QuantLib;
51using namespace QuantExt;
52using namespace std;
53
54namespace ore {
55namespace data {
56
57EquityVolCurve::EquityVolCurve(Date asof, EquityVolatilityCurveSpec spec, const Loader& loader,
58 const CurveConfigurations& curveConfigs, const Handle<EquityIndex2>& eqIndex,
59 const map<string, QuantLib::ext::shared_ptr<EquityCurve>>& requiredEquityCurves,
60 const map<string, QuantLib::ext::shared_ptr<EquityVolCurve>>& requiredEquityVolCurves,
61 const map<string, QuantLib::ext::shared_ptr<FXVolCurve>>& requiredFxVolCurves,
62 const map<string, QuantLib::ext::shared_ptr<CorrelationCurve>>& requiredCorrelationCurves,
63 const Market* fxIndices, const bool buildCalibrationInfo) {
64
65 try {
66 LOG("EquityVolCurve: start building equity volatility structure with ID " << spec.curveConfigID());
67
68 auto config = *curveConfigs.equityVolCurveConfig(spec.curveConfigID());
69
70 // if calendar was omitted or left blank, use ccy calendar instead
71 if (config.calendar().empty())
72 calendar_ = parseCalendar(config.ccy());
73 else
74 calendar_ = parseCalendar(config.calendar());
75
76 dayCounter_ = parseDayCounter(config.dayCounter());
77
78 // loop over the volatility configs attempting to build in the order provided
79 DLOG("EquityVolCurve: Attempting to build equity vol curve from volatilityConfig, " << config.volatilityConfig().size() << " volatility configs provided.");
80 for (auto vc : config.volatilityConfig()) {
81 try {
82 // if the volatility config has it's own calendar, we use that.
83 if (!vc->calendar().empty())
84 calendar_ = vc->calendar();
85
86 if (auto eqvc = QuantLib::ext::dynamic_pointer_cast<ProxyVolatilityConfig>(vc)) {
87 buildVolatility(asof, spec, curveConfigs, *eqvc, requiredEquityCurves, requiredEquityVolCurves,
88 requiredFxVolCurves, requiredCorrelationCurves, fxIndices);
89 } else if (auto qvc = QuantLib::ext::dynamic_pointer_cast<QuoteBasedVolatilityConfig>(vc)) {
90 // if the config is quote based (all except proxy surfaces?), do some checks
91 QL_REQUIRE(qvc->quoteType() == MarketDatum::QuoteType::PRICE ||
92 qvc->quoteType() == MarketDatum::QuoteType::RATE_LNVOL,
93 "EquityVolCurve: Only lognormal volatilities and option premiums supported for equity "
94 "volatility surfaces.");
95 if (auto cvc = QuantLib::ext::dynamic_pointer_cast<ConstantVolatilityConfig>(vc)) {
96 buildVolatility(asof, config, *cvc, loader);
97 } else if (auto vcc = QuantLib::ext::dynamic_pointer_cast<VolatilityCurveConfig>(vc)) {
98 buildVolatility(asof, config, *vcc, loader);
99 } else if (auto vssc = QuantLib::ext::dynamic_pointer_cast<VolatilityStrikeSurfaceConfig>(vc)) {
100 buildVolatility(asof, config, *vssc, loader, eqIndex);
101 } else if (auto vmsc = QuantLib::ext::dynamic_pointer_cast<VolatilityMoneynessSurfaceConfig>(vc)) {
102 buildVolatility(asof, config, *vmsc, loader, eqIndex);
103 } else if (auto vdsc = QuantLib::ext::dynamic_pointer_cast<VolatilityDeltaSurfaceConfig>(vc)) {
104 buildVolatility(asof, config, *vdsc, loader, eqIndex);
105 } else if (auto vdsc = QuantLib::ext::dynamic_pointer_cast<VolatilityApoFutureSurfaceConfig>(vc)) {
106 QL_FAIL("EquityVolCurve: VolatilityApoFutureSurfaceConfig surface not supported for Equities");
107 } else {
108 QL_FAIL("EquityVolCurve: Unexpected VolatilityConfig");
109 }
110 } else {
111 QL_FAIL("EquityVolCurve: VolatilityConfig must be QuoteBased or a Proxy");
112 }
113 // if we've successfully built a surface, save the config and exit the loop
115 break;
116 }
117 catch (std::exception& e) {
118 DLOG("EquityVolCurve: equity vol curve building failed :" << e.what());
119 }
120 catch (...) {
121 DLOG("EquityVolCurve: equity vol curve building failed: unknown error");
122 }
123 }
124 if (!vol_)
125 QL_FAIL("EquityVolCurve: Failed to build equity volatility structure from " << config.volatilityConfig().size() << " volatility configs provided.");
126 LOG("EquityVolCurve: finished building equity volatility structure with ID " << spec.curveConfigID());
127
129 this->buildCalibrationInfo(asof, curveConfigs, config, eqIndex);
130 }
131
132 } catch (exception& e) {
133 QL_FAIL("Equity volatility curve building failed : " << e.what());
134 } catch (...) {
135 QL_FAIL("Equity volatility curve building failed: unknown error");
136 }
137}
138
139void EquityVolCurve::buildVolatility(const Date& asof, const EquityVolatilityCurveConfig& vc,
140 const ConstantVolatilityConfig& cvc, const Loader& loader) {
141 DLOG("EquityVolCurve: start building constant volatility structure");
142
143 QL_REQUIRE(cvc.quoteType() == MarketDatum::QuoteType::RATE_LNVOL ||
144 cvc.quoteType() == MarketDatum::QuoteType::RATE_SLNVOL ||
145 cvc.quoteType() == MarketDatum::QuoteType::RATE_NVOL,
146 "Quote for Equity Constant Volatility Config must be a Volatility");
147
148 const QuantLib::ext::shared_ptr<MarketDatum>& md = loader.get(cvc.quote(), asof);
149 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
150 QL_REQUIRE(md->instrumentType() == MarketDatum::InstrumentType::EQUITY_OPTION,
151 "MarketDatum instrument type '" << md->instrumentType() << "' <> 'MarketDatum::InstrumentType::EQUITY_OPTION'");
152 QuantLib::ext::shared_ptr<EquityOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
153 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
154 QL_REQUIRE(q->name() == cvc.quote(),
155 "EquityOptionQuote name '" << q->name() << "' <> ConstantVolatilityConfig quote '" << cvc.quote() << "'");
156 TLOG("Found the constant volatility quote " << q->name());
157 // convert quote from minor to major currency if needed
158 Real quoteValue = convertMinorToMajorCurrency(q->ccy(), q->quote()->value());
159
160 DLOG("Creating BlackConstantVol structure");
161 vol_ = QuantLib::ext::make_shared<BlackConstantVol>(asof, calendar_, quoteValue, dayCounter_);
162
163 DLOG("EquityVolCurve: finished building constant volatility structure");
164}
165
166void EquityVolCurve::buildVolatility(const Date& asof, const EquityVolatilityCurveConfig& vc,
167 const VolatilityCurveConfig& vcc, const Loader& loader) {
168
169 DLOG("EquityVolCurve: start building 1-D volatility curve");
170
171 QL_REQUIRE(vcc.quoteType() == MarketDatum::QuoteType::RATE_LNVOL ||
172 vcc.quoteType() == MarketDatum::QuoteType::RATE_SLNVOL ||
173 vcc.quoteType() == MarketDatum::QuoteType::RATE_NVOL,
174 "Quote for Equity Constant Volatility Config must be a Volatility");
175
176 // Must have at least one quote
177 QL_REQUIRE(vcc.quotes().size() > 0, "No quotes specified in config " << vc.curveID());
178
179 // Check if we are using a regular expression to select the quotes for the curve. If we are, the quotes should
180 // contain exactly one element.
181 auto wildcard = getUniqueWildcard(vcc.quotes());
182
183 // curveData will be populated with the expiry dates and volatility values.
184 map<Date, Real> curveData;
185
186 // Different approaches depending on whether we are using a regex or searching for a list of explicit quotes.
187 if (wildcard) {
188 DLOG("Have single quote with pattern " << wildcard->pattern());
189
190 // Loop over quotes and process equity option quotes matching pattern on asof
191 for (const auto& md : loader.get(*wildcard, asof)) {
192
193 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
194
195 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
196 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
197 QL_REQUIRE(q->quoteType() == vcc.quoteType(),
198 "EquityOptionQuote type '" << q->quoteType() << "' <> VolatilityCurveConfig quote type '" << vcc.quoteType() << "'");
199
200 TLOG("The quote " << q->name() << " matched the pattern");
201
202 Date expiryDate = getDateFromDateOrPeriod(q->expiry(), asof, calendar_);
203 if (expiryDate > asof) {
204 // Add the quote to the curve data
205 QL_REQUIRE(curveData.count(expiryDate) == 0, "Duplicate quote for the expiry date "
206 << io::iso_date(expiryDate)
207 << " provided by equity volatility config "
208 << vc.curveID());
209 // convert quote from minor to major currency if needed
210 curveData[expiryDate] = convertMinorToMajorCurrency(q->ccy(), q->quote()->value());
211
212 TLOG("Added quote " << q->name() << ": (" << io::iso_date(expiryDate) << "," << fixed
213 << setprecision(9) << q->quote()->value() << ")");
214 }
215 }
216 // Check that we have quotes in the end
217 QL_REQUIRE(curveData.size() > 0, "No quotes found matching regular expression " << vcc.quotes()[0]);
218
219 } else {
220
221 DLOG("Have " << vcc.quotes().size() << " explicit quotes");
222
223 Size excludedAlreadyExpired = 0;
224
225 // Loop over quotes and process equity option quotes that are explicitly specified in the config
226 std::ostringstream ss;
227 ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vcc.quoteType() << "/" << vc.equityId() << "/"
228 << vc.ccy() << "/*";
229 Wildcard w(ss.str());
230 for (const auto& md : loader.get(w, asof)) {
231
232 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
233
234 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
235 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
236
237 // Find quote name in configured quotes.
238 auto it = find(vcc.quotes().begin(), vcc.quotes().end(), q->name());
239
240 if (it != vcc.quotes().end()) {
241 TLOG("Found the configured quote " << q->name());
242
243 Date expiryDate = getDateFromDateOrPeriod(q->expiry(), asof, calendar_);
244 if (expiryDate <= asof) {
245 LOG("Warning Stale Marketdata: Equity volatility quote '"
246 << q->name() << "' has expired in the past (" << io::iso_date(expiryDate) << ") and is ignored");
247 ++excludedAlreadyExpired;
248 continue;
249 }
250 QL_REQUIRE(curveData.count(expiryDate) == 0, "Duplicate quote for the date "
251 << io::iso_date(expiryDate)
252 << " provided by equity volatility config "
253 << vc.curveID());
254 // convert quote from minor to major currency if needed
255 curveData[expiryDate] = convertMinorToMajorCurrency(q->ccy(), q->quote()->value());
256
257 TLOG("Added quote " << q->name() << ": (" << io::iso_date(expiryDate) << "," << fixed
258 << setprecision(9) << q->quote()->value() << ")");
259 }
260 }
261 QL_REQUIRE(curveData.size() > 0, "No 'live' quotes found");
262 // Check that we have found all of the explicitly configured quotes
263 QL_REQUIRE((curveData.size() - excludedAlreadyExpired) == vcc.quotes().size(),
264 "Found " << curveData.size() + excludedAlreadyExpired << " quotes, of which "
265 << excludedAlreadyExpired << " has been in the past but " << vcc.quotes().size()
266 << " quotes were given in config.");
267 }
268
269 // Create the dates and volatility vector
270 vector<Date> dates;
271 vector<Volatility> volatilities;
272 for (const auto& datum : curveData) {
273 dates.push_back(datum.first);
274 volatilities.push_back(datum.second);
275 TLOG("Added data point (" << io::iso_date(dates.back()) << "," << fixed << setprecision(9)
276 << volatilities.back() << ")");
277 }
278
279 DLOG("Creating BlackVarianceCurve object.");
280 auto tmp = QuantLib::ext::make_shared<BlackVarianceCurve>(asof, dates, volatilities, dayCounter_);
281
282 // set max expiry date (used in buildCalibrationInfo())
283 if (!dates.empty())
284 maxExpiry_ = dates.back();
285
286 // Set the interpolation.
287 if (vcc.interpolation() == "Linear") {
288 DLOG("Interpolation set to Linear.");
289 } else if (vcc.interpolation() == "Cubic") {
290 DLOG("Setting interpolation to Cubic.");
291 tmp->setInterpolation<Cubic>();
292 } else if (vcc.interpolation() == "LogLinear") {
293 DLOG("Setting interpolation to LogLinear.");
294 tmp->setInterpolation<LogLinear>();
295 } else {
296 DLOG("Interpolation " << vcc.interpolation() << " not recognised so leaving it Linear.");
297 }
298
299 // Set the volatility_ member after we have possibly updated the interpolation.
300 vol_ = tmp;
301
302 // Set the extrapolation
303 if (parseExtrapolation(vcc.extrapolation()) == Extrapolation::Flat) {
304 DLOG("Enabling BlackVarianceCurve flat volatility extrapolation.");
305 vol_->enableExtrapolation();
306 } else if (parseExtrapolation(vcc.extrapolation()) == Extrapolation::None) {
307 DLOG("Disabling BlackVarianceCurve extrapolation.");
308 vol_->disableExtrapolation();
309 } else if (parseExtrapolation(vcc.extrapolation()) == Extrapolation::UseInterpolator) {
310 DLOG("BlackVarianceCurve does not support using interpolator for extrapolation "
311 << "so default to flat volatility extrapolation.");
312 vol_->enableExtrapolation();
313 } else {
314 DLOG("Unexpected extrapolation so default to flat volatility extrapolation.");
315 vol_->enableExtrapolation();
316 }
317
318 DLOG("EquityVolCurve: finished building 1-D volatility curve");
319}
320
321void EquityVolCurve::buildVolatility(const Date& asof, EquityVolatilityCurveConfig& vc,
322 const VolatilityStrikeSurfaceConfig& vssc, const Loader& loader,
323 const QuantLib::Handle<EquityIndex2>& eqIndex) {
324
325 DLOG("EquityVolCurve: start building 2-D strike volatility surface");
326
327 QL_REQUIRE(vssc.expiries().size() > 0, "No expiries defined");
328 QL_REQUIRE(vssc.strikes().size() > 0, "No strikes defined");
329
330 // check for wild cards
331 bool expiriesWc = find(vssc.expiries().begin(), vssc.expiries().end(), "*") != vssc.expiries().end();
332 bool strikesWc = find(vssc.strikes().begin(), vssc.strikes().end(), "*") != vssc.strikes().end();
333 if (expiriesWc) {
334 QL_REQUIRE(vssc.expiries().size() == 1, "Wild card expiry specified but more expiries also specified.");
335 }
336 if (strikesWc) {
337 QL_REQUIRE(vssc.strikes().size() == 1, "Wild card strike specified but more strikes also specified.");
338 }
339 bool wildcard = strikesWc || expiriesWc;
340
341 vector<Real> callStrikes, putStrikes;
342 vector<Real> callData, putData;
343 vector<Date> callExpiries, putExpiries;
344
345 // In case of wild card we need the following granularity within the mkt data loop
346 bool strikeRelevant = strikesWc;
347 bool expiryRelevant = expiriesWc;
348 bool quoteRelevant = true;
349
350 // We loop over all market data, looking for quotes that match the configuration
351 Size callQuotesAdded = 0;
352 Size putQuotesAdded = 0;
353 Size excludedAlreadyExpired = 0;
354 std::ostringstream ss;
355 ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vssc.quoteType() << "/" << vc.equityId() << "/"
356 << vc.ccy() << "/*";
357 Wildcard w(ss.str());
358 for (const auto& md : loader.get(w, asof)) {
359 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
360 QuantLib::ext::shared_ptr<EquityOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
361 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
362 // todo - for now we will ignore ATM, ATMF quotes both for explicit strikes and in case of strike wild
363 // card. ----
364 auto absoluteStrike = QuantLib::ext::dynamic_pointer_cast<AbsoluteStrike>(q->strike());
365 if (!absoluteStrike)
366 continue;
367 if (!expiriesWc) {
368 auto j = std::find(vssc.expiries().begin(), vssc.expiries().end(), q->expiry());
369 expiryRelevant = j != vssc.expiries().end();
370 }
371 if (!strikesWc) {
372 auto i = std::find_if(vssc.strikes().begin(), vssc.strikes().end(),
373 [&absoluteStrike](const std::string& x) {
374 return close_enough(parseReal(x), absoluteStrike->strike());
375 });
376 strikeRelevant = i != vssc.strikes().end();
377 }
378 quoteRelevant = strikeRelevant && expiryRelevant;
379
380 // add quote to vectors, if relevant
381 // If a quote doesn't include a call/put flag (an Implied Vol for example), it
382 // defaults to a call. For an explicit surface we expect either a call and put
383 // for every point, or just a vol at every point
384 if (quoteRelevant) {
385 Date tmpDate = getDateFromDateOrPeriod(q->expiry(), asof, calendar_);
386 if (tmpDate <= asof) {
387 LOG("expired Equity volatility quote '"
388 << q->name() << "' ignored, expired on (" << io::iso_date(tmpDate)
389 << ")");
390 ++excludedAlreadyExpired;
391 continue;
392 }
393 // get values and strikes, convert from minor to major currency if needed
394 Real quoteValue = q->quote()->value();
395 if (vssc.quoteType() == MarketDatum::QuoteType::PRICE)
396 quoteValue = convertMinorToMajorCurrency(q->ccy(), quoteValue);
397 Real strikeValue = convertMinorToMajorCurrency(q->ccy(), absoluteStrike->strike());
398
399 if (q->isCall()) {
400 callStrikes.push_back(strikeValue);
401 callData.push_back(quoteValue);
402 callExpiries.push_back(tmpDate);
403 callQuotesAdded++;
404 } else {
405 putStrikes.push_back(strikeValue);
406 putData.push_back(quoteValue);
407 putExpiries.push_back(tmpDate);
408 putQuotesAdded++;
409 }
410 }
411 }
412
413 QL_REQUIRE(callQuotesAdded > 0, "No valid equity volatility quotes provided");
414 bool callSurfaceOnly = false;
415 if (callQuotesAdded > 0 && putQuotesAdded == 0) {
416 QL_REQUIRE(vssc.quoteType() != MarketDatum::QuoteType::PRICE,
417 "For Premium quotes, call and put quotes must be supplied.");
418 DLOG("EquityVolCurve: " << vc.curveID() << ": Only one set of quotes, can build surface directly");
419 callSurfaceOnly = true;
420 }
421 // Check loaded quotes
422 if (!wildcard) {
423 Size explicitGridSize = vssc.expiries().size() * vssc.strikes().size();
424
425 QL_REQUIRE(callQuotesAdded + excludedAlreadyExpired == explicitGridSize,
426 "EquityVolCurve: " << vc.curveID() << ": " << callQuotesAdded +excludedAlreadyExpired
427 << " quotes provided, of which " << excludedAlreadyExpired << "have been excluded, but "
428 << explicitGridSize << " expected.");
429 if (!callSurfaceOnly) {
430 QL_REQUIRE(callQuotesAdded == putQuotesAdded,
431 "Call and Put quotes must match for explicitly defined surface, "
432 << callQuotesAdded << " call quotes, and " << putQuotesAdded << " put quotes");
433 DLOG("EquityVolCurve: " << vc.curveID() << ": Complete set of " << callQuotesAdded
434 << ", call and put quotes found.");
435 }
436 }
437
438 QL_REQUIRE(callStrikes.size() == callData.size() && callData.size() == callExpiries.size(),
439 "Quotes loaded don't produce strike,vol,expiry vectors of equal length.");
440 QL_REQUIRE(putStrikes.size() == putData.size() && putData.size() == putExpiries.size(),
441 "Quotes loaded don't produce strike,vol,expiry vectors of equal length.");
442 DLOG("EquityVolCurve: " << vc.curveID() << ": Found " << callQuotesAdded << ", call quotes and "
443 << putQuotesAdded << " put quotes using wildcard.");
444
445 // Set the strike extrapolation which only matters if extrapolation is turned on for the whole surface.
446 bool flatStrikeExtrap = true;
447 bool flatTimeExtrap = true;
448 if (vssc.extrapolation()) {
449
450 auto strikeExtrapType = parseExtrapolation(vssc.strikeExtrapolation());
451 if (strikeExtrapType == Extrapolation::UseInterpolator) {
452 TLOG("EquityVolCurve: Strike extrapolation switched to using interpolator.");
453 flatStrikeExtrap = false;
454 } else if (strikeExtrapType == Extrapolation::None) {
455 TLOG("EquityVolCurve: Strike extrapolation cannot be turned off on its own so defaulting to flat.");
456 } else if (strikeExtrapType == Extrapolation::Flat) {
457 TLOG("EquityVolCurve: Strike extrapolation has been set to flat.");
458 } else {
459 TLOG("EquityVolCurve: Strike extrapolation " << strikeExtrapType << " not expected so default to flat.");
460 }
461
462 auto timeExtrapType = parseExtrapolation(vssc.timeExtrapolation());
463 if (timeExtrapType == Extrapolation::UseInterpolator) {
464 TLOG("EquityVolCurve: Time extrapolation switched to using interpolator.");
465 flatTimeExtrap = false;
466 } else if (timeExtrapType == Extrapolation::None) {
467 TLOG("EquityVolCurve: Time extrapolation cannot be turned off on its own so defaulting to flat.");
468 } else if (timeExtrapType == Extrapolation::Flat) {
469 TLOG("EquityVolCurve: Time extrapolation has been set to flat.");
470 } else {
471 TLOG("EquityVolCurve: Time extrapolation " << timeExtrapType << " not expected so default to flat.");
472 }
473
474 } else {
475 TLOG("EquityVolCurve: Extrapolation is turned off for the whole surface so the time and"
476 << " strike extrapolation settings are ignored");
477 }
478
479 // set max expiry date (used in buildCalibrationInfo())
480 maxExpiry_ = Date::minDate();
481 for (auto const& d : callExpiries)
482 maxExpiry_ = std::max(maxExpiry_, d);
483 for (auto const& d : putExpiries)
484 maxExpiry_ = std::max(maxExpiry_, d);
485 if (maxExpiry_ == Date::minDate())
486 maxExpiry_ = Date();
487
488 bool preferOutOfTheMoney = vc.preferOutOfTheMoney() ? *vc.preferOutOfTheMoney() : true;
489
490 if (vssc.quoteType() == MarketDatum::QuoteType::PRICE) {
491
492 // Create the 1D solver options used in the price stripping.
493 Solver1DOptions solverOptions = vc.solverConfig();
494
495 DLOG("EquityVolCurve: Building a option price surface for calls and puts");
496 QuantLib::ext::shared_ptr<OptionPriceSurface> callSurface =
497 QuantLib::ext::make_shared<OptionPriceSurface>(asof, callExpiries, callStrikes, callData, dayCounter_);
498 QuantLib::ext::shared_ptr<OptionPriceSurface> putSurface =
499 QuantLib::ext::make_shared<OptionPriceSurface>(asof, putExpiries, putStrikes, putData, dayCounter_);
500
501 DLOG("EquityVolCurve: CallSurface contains " << callSurface->expiries().size() << " expiries.");
502
503 DLOG("EquityVolCurve: Stripping equity volatility surface from the option premium surfaces");
504 QuantLib::ext::shared_ptr<EquityOptionSurfaceStripper> eoss = QuantLib::ext::make_shared<EquityOptionSurfaceStripper>(
505 eqIndex, callSurface, putSurface, calendar_, dayCounter_, vssc.exerciseType(), flatStrikeExtrap,
506 flatStrikeExtrap, flatTimeExtrap, preferOutOfTheMoney, solverOptions);
507 vol_ = eoss->volSurface();
508
509 } else if (vssc.quoteType() == MarketDatum::QuoteType::RATE_LNVOL) {
510
511 if (callExpiries.size() == 1 && callStrikes.size() == 1) {
512 DLOG("EquityVolCurve: Building BlackConstantVol");
513 vol_ = QuantLib::ext::shared_ptr<BlackVolTermStructure>(
514 new BlackConstantVol(asof, Calendar(), callData[0], dayCounter_));
515 } else {
516 // create a vol surface from the calls
517 QuantLib::ext::shared_ptr<BlackVarianceSurfaceSparse> callSurface =
518 QuantLib::ext::make_shared<BlackVarianceSurfaceSparse>(asof, calendar_, callExpiries, callStrikes, callData,
519 dayCounter_, flatStrikeExtrap, flatStrikeExtrap,
520 flatTimeExtrap);
521
522 if (callSurfaceOnly) {
523 // if only a call surface provided use that
524 vol_ = callSurface;
525 } else {
526 // otherwise create a vol surface from puts and strip for a final surface
527 QuantLib::ext::shared_ptr<BlackVarianceSurfaceSparse> putSurface =
528 QuantLib::ext::make_shared<BlackVarianceSurfaceSparse>(asof, calendar_, putExpiries, putStrikes,
529 putData, dayCounter_, flatStrikeExtrap,
530 flatStrikeExtrap, flatTimeExtrap);
531
532 QuantLib::ext::shared_ptr<EquityOptionSurfaceStripper> eoss =
533 QuantLib::ext::make_shared<EquityOptionSurfaceStripper>(
534 eqIndex, callSurface, putSurface, calendar_, dayCounter_, Exercise::European,
535 flatStrikeExtrap, flatStrikeExtrap, flatTimeExtrap, preferOutOfTheMoney);
536 vol_ = eoss->volSurface();
537 }
538 }
539 } else {
540 QL_FAIL("EquityVolCurve: Invalid quote type provided.");
541 }
542 DLOG("EquityVolCurve: Setting BlackVarianceSurfaceSparse extrapolation to " << to_string(vssc.extrapolation()));
543 vol_->enableExtrapolation(vssc.extrapolation());
544
545 DLOG("EquityVolCurve: EquityVolCurve: finished building 2-D strike volatility surface");
546}
547
548namespace {
549vector<Real> checkMoneyness(const vector<string>& strMoneynessLevels) {
550
551 using boost::adaptors::transformed;
552 using boost::algorithm::join;
553
554 vector<Real> moneynessLevels = parseVectorOfValues<Real>(strMoneynessLevels, &parseReal);
555 sort(moneynessLevels.begin(), moneynessLevels.end(), [](Real x, Real y) { return !close(x, y) && x < y; });
556 QL_REQUIRE(adjacent_find(moneynessLevels.begin(), moneynessLevels.end(),
557 [](Real x, Real y) { return close(x, y); }) == moneynessLevels.end(),
558 "The configured moneyness levels contain duplicates");
559 DLOG("EquityVolCurve: Parsed " << moneynessLevels.size() << " unique configured moneyness levels.");
560 DLOG("EquityVolCurve: The moneyness levels are: " << join(
561 moneynessLevels | transformed([](Real d) { return ore::data::to_string(d); }), ","));
562
563 return moneynessLevels;
564}
565} // namespace
566
567void EquityVolCurve::buildVolatility(const Date& asof, EquityVolatilityCurveConfig& vc,
568 const VolatilityMoneynessSurfaceConfig& vmsc, const Loader& loader,
569 const QuantLib::Handle<EquityIndex2>& eqIndex) {
570
571 LOG("EquityVolCurve: start building 2-D volatility moneyness strike surface");
572 using boost::adaptors::transformed;
573 using boost::algorithm::join;
574
575 // Check that the quote type is volatility, we do not support price
576 QL_REQUIRE(vmsc.quoteType() == MarketDatum::QuoteType::RATE_LNVOL,
577 "EquityVolCurve: Equity Moneyness Surface supports lognormal volatility quotes only");
578
579 // Parse, sort and check the vector of configured moneyness levels
580 vector<Real> moneynessLevels = checkMoneyness(vmsc.moneynessLevels());
581
582 // Expiries may be configured with a wildcard or given explicitly
583 bool expWc = false;
584 if (find(vmsc.expiries().begin(), vmsc.expiries().end(), "*") != vmsc.expiries().end()) {
585 expWc = true;
586 QL_REQUIRE(vmsc.expiries().size() == 1, "EquityVolCurve: Wild card expiry specified but more expiries also specified.");
587 DLOG("EquityVolCurve: Have expiry wildcard pattern " << vmsc.expiries()[0]);
588 }
589
590 // Map to hold the rows of the volatility matrix. The keys are the expiry dates and the values are the
591 // vectors of volatilities, one for each configured moneyness.
592 map<Date, vector<Real>> surfaceData;
593
594 // Count the number of quotes added. We check at the end that we have added all configured quotes.
595 Size quotesAdded = 0;
596
597 // Configured moneyness type.
598 MoneynessStrike::Type moneynessType = parseMoneynessType(vmsc.moneynessType());
599
600 // Populate the configured strikes.
601 vector<QuantLib::ext::shared_ptr<BaseStrike>> strikes;
602 for (const auto& moneynessLevel : moneynessLevels) {
603 strikes.push_back(QuantLib::ext::make_shared<MoneynessStrike>(moneynessType, moneynessLevel));
604 }
605
606 // Read the quotes to fill the expiry dates and vols matrix.
607 std::ostringstream ss;
608 ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vmsc.quoteType() << "/" << vc.equityId() << "/"
609 << vc.ccy() << "/*";
610 Wildcard w(ss.str());
611 for (const auto& md : loader.get(w, asof)) {
612
613 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
614
615 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
616 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
617 QL_REQUIRE(q->eqName() == vc.equityId(), "EquityOptionQuote eqName '"
618 << q->eqName() << "' <> EquityVolatilityCurveConfig equityId '"
619 << vc.equityId() << "'");
620 QL_REQUIRE(q->ccy() == vc.ccy(),
621 "EquityOptionQuote ccy '" << q->ccy() << "' <> EquityVolatilityCurveConfig ccy '" << vc.ccy() << "'");
622 QL_REQUIRE(q->quoteType() == vmsc.quoteType(),
623 "EquityOptionQuote quoteType '" << q->quoteType() << "' <> VolatilityMoneynessSurfaceConfig quoteType '" << vmsc.quoteType() << "'");
624
625 // Iterator to one of the configured strikes.
626 vector<QuantLib::ext::shared_ptr<BaseStrike>>::iterator strikeIt;
627
628 if (expWc) {
629 // Check if quote's strike is in the configured strikes and continue if it is not.
630 strikeIt = find_if(strikes.begin(), strikes.end(),
631 [&q](QuantLib::ext::shared_ptr<BaseStrike> s) { return *s == *q->strike(); });
632 if (strikeIt == strikes.end())
633 continue;
634 } else {
635 // If we have explicitly configured expiries and the quote is not in the configured quotes continue.
636 auto it = find(vc.quotes().begin(), vc.quotes().end(), q->name());
637 if (it == vc.quotes().end())
638 continue;
639
640 // Check if quote's strike is in the configured strikes and continue if it is not.
641 strikeIt = find_if(strikes.begin(), strikes.end(),
642 [&q](QuantLib::ext::shared_ptr<BaseStrike> s) { return *s == *q->strike(); });
643 if (strikeIt == strikes.end())
644 continue;
645 }
646
647 // Position of quote in vector of strikes
648 Size pos = std::distance(strikes.begin(), strikeIt);
649
650 // Process the quote
651 Date eDate = getDateFromDateOrPeriod(q->expiry(), asof, calendar_);
652
653 // Add quote to surface
654 if (surfaceData.count(eDate) == 0)
655 surfaceData[eDate] = vector<Real>(moneynessLevels.size(), Null<Real>());
656
657 QL_REQUIRE(surfaceData[eDate][pos] == Null<Real>(),
658 "EquityVolCurve: Quote " << q->name() << " provides a duplicate quote for the date " << io::iso_date(eDate)
659 << " and strike " << *q->strike());
660 surfaceData[eDate][pos] = q->quote()->value();
661 quotesAdded++;
662
663 TLOG("EquityVolCurve: Added quote " << q->name() << ": (" << io::iso_date(eDate) << "," << *q->strike() << "," << fixed
664 << setprecision(9) << "," << q->quote()->value() << ")");
665 }
666
667 DLOG("EquityVolCurve: added " << quotesAdded << " quotes in building moneyness strike surface.");
668
669 // Check the data gathered.
670 if (expWc) {
671 // check we have non-empty surface data
672 QL_REQUIRE(!surfaceData.empty(), "EquityVolCurve: Moneyness Surface Data is empty");
673 // If the expiries were configured via a wildcard, check that no surfaceData element has a Null<Real>().
674 for (const auto& kv : surfaceData) {
675 for (Size j = 0; j < moneynessLevels.size(); j++) {
676 QL_REQUIRE(kv.second[j] != Null<Real>(), "EquityVolCurve: Volatility for expiry date "
677 << io::iso_date(kv.first) << " and strike " << *strikes[j]
678 << " not found. Cannot proceed with a sparse matrix.");
679 }
680 }
681 } else {
682 // If expiries were configured explicitly, the number of configured quotes should equal the
683 // number of quotes added.
684 QL_REQUIRE(vc.quotes().size() == quotesAdded,
685 "EquityVolCurve: Found " << quotesAdded << " quotes, but " << vc.quotes().size() << " quotes required by config.");
686 }
687
688 // Populate the volatility quotes and the expiry times.
689 // Rows are moneyness levels and columns are expiry times - this is what the ctor needs below.
690 vector<Date> expiryDates(surfaceData.size());
691 vector<Time> expiryTimes(surfaceData.size());
692 vector<vector<Handle<Quote>>> vols(moneynessLevels.size());
693 for (const auto row : surfaceData | boost::adaptors::indexed(0)) {
694 expiryDates[row.index()] = row.value().first;
695 expiryTimes[row.index()] = dayCounter_.yearFraction(asof, row.value().first);
696 for (Size i = 0; i < row.value().second.size(); i++) {
697 vols[i].push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(row.value().second[i])));
698 }
699 }
700
701 // set max expiry date (used in buildCalibrationInfo())
702 if (!expiryDates.empty())
703 maxExpiry_ = expiryDates.back();
704
705 // Set the strike extrapolation which only matters if extrapolation is turned on for the whole surface.
706 // BlackVarianceSurfaceMoneyness time extrapolation is hard-coded to constant in volatility.
707 bool flatExtrapolation = true;
708 if (vmsc.extrapolation()) {
709
710 auto strikeExtrapType = parseExtrapolation(vmsc.strikeExtrapolation());
711 if (strikeExtrapType == Extrapolation::UseInterpolator) {
712 TLOG("EquityVolCurve: Strike extrapolation switched to using interpolator.");
713 flatExtrapolation = false;
714 } else if (strikeExtrapType == Extrapolation::None) {
715 TLOG("EquityVolCurve: Strike extrapolation cannot be turned off on its own so defaulting to flat.");
716 } else if (strikeExtrapType == Extrapolation::Flat) {
717 TLOG("EquityVolCurve: Strike extrapolation has been set to flat.");
718 } else {
719 TLOG("EquityVolCurve: Strike extrapolation " << strikeExtrapType << " not expected so default to flat.");
720 }
721
722 auto timeExtrapType = parseExtrapolation(vmsc.timeExtrapolation());
723 if (timeExtrapType != Extrapolation::Flat) {
724 TLOG("EquityVolCurve: BlackVarianceSurfaceMoneyness only supports flat volatility extrapolation in the time direction");
725 }
726 } else {
727 TLOG("EquityVolCurve: Extrapolation is turned off for the whole surface so the time and"
728 << " strike extrapolation settings are ignored");
729 }
730
731 // Time interpolation
732 if (vmsc.timeInterpolation() != "Linear") {
733 TLOG("EquityVolCurve: BlackVarianceSurfaceMoneyness only supports linear time interpolation in variance.");
734 }
735
736 // Strike interpolation
737 if (vmsc.strikeInterpolation() != "Linear") {
738 TLOG("EquityVolCurve: BlackVarianceSurfaceMoneyness only supports linear strike interpolation in variance.");
739 }
740
741 // Both moneyness surfaces need a spot quote.
742
743 // The choice of false here is important for forward moneyness. It means that we use the cpts and yts in the
744 // BlackVarianceSurfaceMoneynessForward to get the forward value at all times and in particular at times that
745 // are after the last expiry time. If we set it to true, BlackVarianceSurfaceMoneynessForward uses a linear
746 // interpolated forward curve on the expiry times internally which is poor.
747 bool stickyStrike = false;
748
749 if (moneynessType == MoneynessStrike::Type::Forward) {
750 DLOG("EquityVolCurve: Creating BlackVarianceSurfaceMoneynessForward object");
751 vol_ = QuantLib::ext::make_shared<BlackVarianceSurfaceMoneynessForward>(
752 calendar_, eqIndex->equitySpot(), expiryTimes, moneynessLevels, vols, dayCounter_,
753 eqIndex->equityDividendCurve(), eqIndex->equityForecastCurve(), stickyStrike, flatExtrapolation);
754
755 } else {
756 DLOG("EquityVolCurve: Creating BlackVarianceSurfaceMoneynessSpot object");
757 vol_ = QuantLib::ext::make_shared<BlackVarianceSurfaceMoneynessSpot>(calendar_, eqIndex->equitySpot(), expiryTimes,
758 moneynessLevels, vols, dayCounter_, stickyStrike,
759 flatExtrapolation);
760 }
761
762 DLOG("EquityVolCurve: Setting BlackVarianceSurfaceMoneyness extrapolation to " << to_string(vmsc.extrapolation()));
763 vol_->enableExtrapolation(vmsc.extrapolation());
764
765 DLOG("EquityVolCurve: EquityVolCurve: finished building 2-D volatility moneyness strike surface");
766}
767
769 const VolatilityDeltaSurfaceConfig& vdsc, const Loader& loader,
770 const QuantLib::Handle<QuantExt::EquityIndex2>& eqIndex) {
771
772 using boost::adaptors::transformed;
773 using boost::algorithm::join;
774
775 DLOG("EquityVolCurve: start building 2-D volatility delta strike surface");
776
778 "EquityVolCurve: only quote type"
779 << " RATE_LNVOL is currently supported for a 2-D volatility delta strike surface.");
780
781 // Parse, sort and check the vector of configured put deltas
782 vector<Real> putDeltas = parseVectorOfValues<Real>(vdsc.putDeltas(), &parseReal);
783 sort(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return !close(x, y) && x < y; });
784 QL_REQUIRE(adjacent_find(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
785 putDeltas.end(),
786 "EquityVolCurve: The configured put deltas contain duplicates");
787 DLOG("EquityVolCurve: Parsed " << putDeltas.size() << " unique configured put deltas");
788 DLOG("EquityVolCurve: Put deltas are: " << join(putDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
789
790 // Parse, sort descending and check the vector of configured call deltas
791 vector<Real> callDeltas = parseVectorOfValues<Real>(vdsc.callDeltas(), &parseReal);
792 sort(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return !close(x, y) && x > y; });
793 QL_REQUIRE(adjacent_find(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
794 callDeltas.end(),
795 "EquityVolCurve: The configured call deltas contain duplicates");
796 DLOG("EquityVolCurve: Parsed " << callDeltas.size() << " unique configured call deltas");
797 DLOG("EquityVolCurve: Call deltas are: " << join(callDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
798
799 // Expiries may be configured with a wildcard or given explicitly
800 bool expWc = false;
801 if (find(vdsc.expiries().begin(), vdsc.expiries().end(), "*") != vdsc.expiries().end()) {
802 expWc = true;
803 QL_REQUIRE(vdsc.expiries().size() == 1, "Wild card expiry specified but more expiries also specified.");
804 DLOG("EquityVolCurve: Have expiry wildcard pattern " << vdsc.expiries()[0]);
805 }
806
807 // Map to hold the rows of the equity volatility matrix. The keys are the expiry dates and the values are the
808 // vectors of volatilities, one for each configured delta.
809 map<Date, vector<Real>> surfaceData;
810
811 // Number of strikes = number of put deltas + ATM + number of call deltas
812 Size numStrikes = putDeltas.size() + 1 + callDeltas.size();
813
814 // Count the number of quotes added. We check at the end that we have added all configured quotes.
815 Size quotesAdded = 0;
816
817 // Configured delta and Atm types.
818 DeltaVolQuote::DeltaType deltaType = parseDeltaType(vdsc.deltaType());
819 DeltaVolQuote::AtmType atmType = parseAtmType(vdsc.atmType());
820 boost::optional<DeltaVolQuote::DeltaType> atmDeltaType;
821 if (!vdsc.atmDeltaType().empty()) {
822 atmDeltaType = parseDeltaType(vdsc.atmDeltaType());
823 }
824
825 // Populate the configured strikes.
826 vector<QuantLib::ext::shared_ptr<BaseStrike>> strikes;
827 for (const auto& pd : putDeltas) {
828 strikes.push_back(QuantLib::ext::make_shared<DeltaStrike>(deltaType, Option::Put, pd));
829 }
830 strikes.push_back(QuantLib::ext::make_shared<AtmStrike>(atmType, atmDeltaType));
831 for (const auto& cd : callDeltas) {
832 strikes.push_back(QuantLib::ext::make_shared<DeltaStrike>(deltaType, Option::Call, cd));
833 }
834
835 // Read the quotes to fill the expiry dates and vols matrix.
836 std::ostringstream ss;
837 ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vdsc.quoteType() << "/" << vc.equityId() << "/"
838 << vc.ccy() << "/*";
839 Wildcard w(ss.str());
840 for (const auto& md : loader.get(w, asof)) {
841
842 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
843
844 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
845 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
846 QL_REQUIRE(q->eqName() == vc.equityId(), "EquityOptionQuote eqName '"
847 << q->eqName() << "' <> EquityVolatilityCurveConfig equityId '"
848 << vc.equityId() << "'");
849 QL_REQUIRE(q->ccy() == vc.ccy(),
850 "EquityOptionQuote ccy '" << q->ccy() << "' <> EquityVolatilityCurveConfig ccy '" << vc.ccy() << "'");
851 QL_REQUIRE(q->quoteType() == vdsc.quoteType(),
852 "EquityOptionQuote quoteType '" << q->quoteType() << "' <> VolatilityMoneynessSurfaceConfig quoteType '" << vdsc.quoteType() << "'");
853
854 // Iterator to one of the configured strikes.
855 vector<QuantLib::ext::shared_ptr<BaseStrike>>::iterator strikeIt;
856
857 if (expWc) {
858 // Check if quote's strike is in the configured strikes and continue if it is not.
859 strikeIt = find_if(strikes.begin(), strikes.end(),
860 [&q](QuantLib::ext::shared_ptr<BaseStrike> s) { return *s == *q->strike(); });
861 if (strikeIt == strikes.end())
862 continue;
863 } else {
864 // If we have explicitly configured expiries and the quote is not in the configured quotes continue.
865 auto it = find(vc.quotes().begin(), vc.quotes().end(), q->name());
866 if (it == vc.quotes().end())
867 continue;
868
869 // Check if quote's strike is in the configured strikes.
870 // It should be as we have selected from the explicitly configured quotes in the last step.
871 strikeIt = find_if(strikes.begin(), strikes.end(),
872 [&q](QuantLib::ext::shared_ptr<BaseStrike> s) { return *s == *q->strike(); });
873 QL_REQUIRE(strikeIt != strikes.end(),
874 "EquityVolCurve: The quote '"
875 << q->name()
876 << "' is in the list of configured quotes but does not match any of the configured strikes");
877 }
878
879 // Position of quote in vector of strikes
880 Size pos = std::distance(strikes.begin(), strikeIt);
881
882 // Process the quote
883 Date eDate;
884 QuantLib::ext::shared_ptr<Expiry> expiry = parseExpiry(q->expiry());
885 if (auto expiryDate = QuantLib::ext::dynamic_pointer_cast<ExpiryDate>(expiry)) {
886 eDate = expiryDate->expiryDate();
887 } else if (auto expiryPeriod = QuantLib::ext::dynamic_pointer_cast<ExpiryPeriod>(expiry)) {
888 // We may need more conventions here eventually.
889 eDate = calendar_.adjust(asof + expiryPeriod->expiryPeriod());
890 }
891
892 // Add quote to surface
893 if (surfaceData.count(eDate) == 0)
894 surfaceData[eDate] = vector<Real>(numStrikes, Null<Real>());
895
896 QL_REQUIRE(surfaceData[eDate][pos] == Null<Real>(),
897 "EquityVolCurve: Quote " << q->name() << " provides a duplicate quote for the date " << io::iso_date(eDate)
898 << " and strike " << *q->strike());
899 surfaceData[eDate][pos] = q->quote()->value();
900 quotesAdded++;
901
902 TLOG("EquityVolCurve: Added quote " << q->name() << ": (" << io::iso_date(eDate) << "," << *q->strike() << "," << fixed
903 << setprecision(9) << "," << q->quote()->value() << ")");
904 }
905
906 DLOG("EquityVolCurve: EquityVolCurve: added " << quotesAdded << " quotes in building delta strike surface.");
907
908 // Check the data gathered.
909 if (expWc) {
910 // If the expiries were configured via a wildcard, check that no surfaceData element has a Null<Real>().
911 for (const auto& kv : surfaceData) {
912 for (Size j = 0; j < numStrikes; j++) {
913 QL_REQUIRE(kv.second[j] != Null<Real>(), "EquityVolCurve: Volatility for expiry date "
914 << io::iso_date(kv.first) << " and strike " << *strikes[j]
915 << " not found. Cannot proceed with a sparse matrix.");
916 }
917 }
918 } else {
919 // If expiries were configured explicitly, the number of configured quotes should equal the
920 // number of quotes added.
921 QL_REQUIRE(vc.quotes().size() == quotesAdded,
922 "EquityVolCurve: Found " << quotesAdded << " quotes, but " << vc.quotes().size() << " quotes required by config.");
923 }
924
925 // Populate the matrix of volatilities and the expiry dates.
926 vector<Date> expiryDates;
927 Matrix vols(surfaceData.size(), numStrikes);
928 for (const auto row : surfaceData | boost::adaptors::indexed(0)) {
929 expiryDates.push_back(row.value().first);
930 copy(row.value().second.begin(), row.value().second.end(), vols.row_begin(row.index()));
931 }
932
933 // Need to multiply each put delta value by -1 before passing it to the BlackVolatilitySurfaceDelta ctor
934 // i.e. a put delta of 0.25 that is passed in to the config must be -0.25 when passed to the ctor.
935 transform(putDeltas.begin(), putDeltas.end(), putDeltas.begin(), [](Real pd) { return -1.0 * pd; });
936 DLOG("EquityVolCurve: Multiply put deltas by -1.0 before creating BlackVolatilitySurfaceDelta object.");
937 DLOG("EquityVolCurve: Put deltas are: " << join(putDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
938
939 // Set the strike extrapolation which only matters if extrapolation is turned on for the whole surface.
940 // BlackVolatilitySurfaceDelta time extrapolation is hard-coded to constant in volatility.
941 bool flatExtrapolation = true;
942 if (vdsc.extrapolation()) {
943
944 auto strikeExtrapType = parseExtrapolation(vdsc.strikeExtrapolation());
945 if (strikeExtrapType == Extrapolation::UseInterpolator) {
946 TLOG("EquityVolCurve: Strike extrapolation switched to using interpolator.");
947 flatExtrapolation = false;
948 } else if (strikeExtrapType == Extrapolation::None) {
949 TLOG("EquityVolCurve: Strike extrapolation cannot be turned off on its own so defaulting to flat.");
950 } else if (strikeExtrapType == Extrapolation::Flat) {
951 TLOG("EquityVolCurve: Strike extrapolation has been set to flat.");
952 } else {
953 TLOG("EquityVolCurve: Strike extrapolation " << strikeExtrapType << " not expected so default to flat.");
954 }
955
956 auto timeExtrapType = parseExtrapolation(vdsc.timeExtrapolation());
957 if (timeExtrapType != Extrapolation::Flat) {
958 TLOG("EquityVolCurve: BlackVolatilitySurfaceDelta only supports flat volatility extrapolation in the time direction");
959 }
960 } else {
961 TLOG("EquityVolCurve: Extrapolation is turned off for the whole surface so the time and"
962 << " strike extrapolation settings are ignored");
963 }
964
965 // Time interpolation
966 if (vdsc.timeInterpolation() != "Linear") {
967 TLOG("EquityVolCurve: BlackVolatilitySurfaceDelta only supports linear time interpolation.");
968 }
969
970 // Strike interpolation
972 if (vdsc.strikeInterpolation() == "Linear") {
973 im = InterpolatedSmileSection::InterpolationMethod::Linear;
974 } else if (vdsc.strikeInterpolation() == "NaturalCubic") {
975 im = InterpolatedSmileSection::InterpolationMethod::NaturalCubic;
976 } else if (vdsc.strikeInterpolation() == "FinancialCubic") {
977 im = InterpolatedSmileSection::InterpolationMethod::FinancialCubic;
978 } else if (vdsc.strikeInterpolation() == "CubicSpline") {
979 im = InterpolatedSmileSection::InterpolationMethod::CubicSpline;
980 } else {
981 im = InterpolatedSmileSection::InterpolationMethod::Linear;
982 DLOG("EquityVolCurve: BlackVolatilitySurfaceDelta does not support strike interpolation '" << vdsc.strikeInterpolation()
983 << "' so setting it to linear.");
984 }
985
986 // set max expiry date (used in buildCalibrationInfo())
987 if (!expiryDates.empty())
988 maxExpiry_ = expiryDates.back();
989
990 DLOG("EquityVolCurve: Creating BlackVolatilitySurfaceDelta object");
991 bool hasAtm = true;
992 vol_ = QuantLib::ext::make_shared<BlackVolatilitySurfaceDelta>(
993 asof, expiryDates, putDeltas, callDeltas, hasAtm, vols, dayCounter_, calendar_, eqIndex->equitySpot(),
994 eqIndex->equityForecastCurve(), eqIndex->equityDividendCurve(), deltaType, atmType, atmDeltaType, 0 * Days,
995 deltaType, atmType, atmDeltaType, im, flatExtrapolation);
996
997 DLOG("EquityVolCurve: Setting BlackVolatilitySurfaceDelta extrapolation to " << to_string(vdsc.extrapolation()));
998 vol_->enableExtrapolation(vdsc.extrapolation());
999
1000 DLOG("EquityVolCurve: finished building 2-D volatility delta strike surface");
1001}
1002
1003void EquityVolCurve::buildVolatility(const QuantLib::Date& asof, const EquityVolatilityCurveSpec& spec,
1005 const map<string, QuantLib::ext::shared_ptr<EquityCurve>>& eqCurves,
1006 const map<string, QuantLib::ext::shared_ptr<EquityVolCurve>>& eqVolCurves,
1007 const map<string, QuantLib::ext::shared_ptr<FXVolCurve>>& fxVolCurves,
1008 const map<string, QuantLib::ext::shared_ptr<CorrelationCurve>>& requiredCorrelationCurves,
1009 const Market* fxIndices) {
1010
1011 DLOG("EquityVolCurve: start building proxy vol surface");
1012 // get all the configurations and the curve needed for proxying
1013 auto config = *curveConfigs.equityVolCurveConfig(spec.curveConfigID());
1014
1015 auto proxy = epvc.proxyVolatilityCurve();
1016 auto eqConfig = *curveConfigs.equityCurveConfig(spec.curveConfigID());
1017 auto proxyConfig = *curveConfigs.equityCurveConfig(proxy);
1018 auto proxyVolConfig = *curveConfigs.equityVolCurveConfig(proxy);
1019
1020 // create dummy specs to look up the required curves
1021 EquityCurveSpec eqSpec(eqConfig.currency(), spec.curveConfigID());
1022 EquityCurveSpec proxySpec(proxyConfig.currency(), proxy);
1023 EquityVolatilityCurveSpec proxyVolSpec(proxyVolConfig.ccy(), proxy);
1024
1025 // Get all necessary curves
1026 auto curve = eqCurves.find(eqSpec.name());
1027 QL_REQUIRE(curve != eqCurves.end(), "EquityVolCurve: Failed to find equity curve, when building equity vol curve " << spec.name());
1028 auto proxyCurve = eqCurves.find(proxySpec.name());
1029 QL_REQUIRE(proxyCurve != eqCurves.end(), "EquityVolCurve: Failed to find equity curve for proxy "
1030 << proxySpec.name() << ", when building equity vol curve "
1031 << spec.name());
1032 auto proxyVolCurve = eqVolCurves.find(proxyVolSpec.name());
1033 QL_REQUIRE(proxyVolCurve != eqVolCurves.end(), "EquityVolCurve: Failed to find equity vol curve for proxy "
1034 << proxyVolSpec.name() << ", when building equity vol curve "
1035 << spec.name());
1036
1037 // check the currency against the proxy surface currrency
1038
1039 QuantLib::ext::shared_ptr<BlackVolTermStructure> fxSurface;
1040 QuantLib::ext::shared_ptr<FxIndex> fxIndex;
1041 QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure> correlation;
1042 if (config.ccy() != proxyVolConfig.ccy() && fxIndices != nullptr) {
1043 QL_REQUIRE(!epvc.fxVolatilityCurve().empty(), "EquityVolCurve: FXVolatilityCurve must be provided for Equity vol config " <<
1044 spec.curveConfigID() << " as proxy currencies if different from equity currency.");
1045 QL_REQUIRE(!epvc.correlationCurve().empty(), "EquityVolCurve: CorrelationCurve must be provided for Equity vol config " <<
1046 spec.curveConfigID() << " as proxy currencies if different from equity currency.");
1047
1048 // get the fx vol surface
1049 QL_REQUIRE(epvc.fxVolatilityCurve().size() == 6, "EquityVolCurve: FXVolatilityCurve provided " << epvc.fxVolatilityCurve() <<
1050 " for Equity vol config " << spec.curveConfigID() << " must be of length 6, and of form CC1CCY2 e.g EURUSD");
1051 string proxyVolForCcy = epvc.fxVolatilityCurve().substr(0, 3);
1052 string proxyVolDomCcy = epvc.fxVolatilityCurve().substr(3, 3);
1053 FXVolatilityCurveSpec fxSpec(proxyVolForCcy, proxyVolDomCcy, epvc.fxVolatilityCurve());
1054 auto volIt = fxVolCurves.find(fxSpec.name());
1055 if (volIt == fxVolCurves.end())
1056 QL_FAIL("EquityVolCurve: cannot find required Fx volatility surface " << fxSpec.name() << " to build proxy vol surface for " << eqSpec.name());
1057 fxSurface = volIt->second->volTermStructure();
1058
1059 // check if the fx vol surface needs to be inverted
1060 if (proxyVolForCcy != proxyVolConfig.ccy()) {
1061 Handle<BlackVolTermStructure> hFx(fxSurface);
1062 fxSurface = QuantLib::ext::make_shared<QuantExt::BlackInvertedVolTermStructure>(hFx);
1063 fxSurface->enableExtrapolation();
1064 }
1065
1066 fxIndex = fxIndices->fxIndex(proxyVolConfig.ccy() + config.ccy()).currentLink();
1067
1068 CorrelationCurveSpec corrSpec(epvc.correlationCurve());
1069 auto corrIt = requiredCorrelationCurves.find(corrSpec.name());
1070 if (corrIt == requiredCorrelationCurves.end())
1071 QL_FAIL("EquityVolCurve: cannot find required correlation curve " << epvc.correlationCurve() << " to build proxy vol surface for " << eqSpec.name());
1072 correlation = corrIt->second->corrTermStructure();
1073 }
1074
1075 vol_ = QuantLib::ext::make_shared<BlackVolatilitySurfaceProxy>(proxyVolCurve->second->volTermStructure(), curve->second->equityIndex(),
1076 proxyCurve->second->equityIndex(), fxSurface, fxIndex, correlation);
1077}
1078
1080 const EquityVolatilityCurveConfig& config,
1081 const Handle<EquityIndex2>& eqIndex) {
1082
1083 DLOG("EquityVolCurve: Building calibration info for eq vol surface");
1084
1085 try {
1086
1087 ReportConfig rc = effectiveReportConfig(curveConfigs.reportConfigEqVols(), config.reportConfig());
1088
1089 bool reportOnDeltaGrid = *rc.reportOnDeltaGrid();
1090 bool reportOnMoneynessGrid = *rc.reportOnMoneynessGrid();
1091 std::vector<Real> moneyness = *rc.moneyness();
1092 std::vector<std::string> deltas = *rc.deltas();
1093 std::vector<Period> expiries = *rc.expiries();
1094
1095 calibrationInfo_ = QuantLib::ext::make_shared<FxEqCommVolCalibrationInfo>();
1096
1097 DeltaVolQuote::AtmType atmType = DeltaVolQuote::AtmType::AtmDeltaNeutral;
1098 DeltaVolQuote::DeltaType deltaType = DeltaVolQuote::DeltaType::Fwd;
1099
1100 if (auto vdsc = QuantLib::ext::dynamic_pointer_cast<VolatilityDeltaSurfaceConfig>(volatilityConfig_)) {
1101 atmType = parseAtmType(vdsc->atmType());
1102 deltaType = parseDeltaType(vdsc->deltaType());
1103 }
1104
1105 calibrationInfo_->dayCounter = config.dayCounter().empty() ? "na" : config.dayCounter();
1106 calibrationInfo_->calendar = config.calendar().empty() ? "na" : config.calendar();
1107 calibrationInfo_->atmType = ore::data::to_string(atmType);
1108 calibrationInfo_->deltaType = ore::data::to_string(deltaType);
1109 calibrationInfo_->longTermAtmType = ore::data::to_string(atmType);
1110 calibrationInfo_->longTermDeltaType = ore::data::to_string(deltaType);
1111 calibrationInfo_->switchTenor = "na";
1112 calibrationInfo_->riskReversalInFavorOf = "na";
1113 calibrationInfo_->butterflyStyle = "na";
1114
1115 std::vector<Real> times, forwards, rfDisc, divDisc;
1116 for (auto const& p : expiries) {
1117 Date d = vol_->optionDateFromTenor(p);
1118 calibrationInfo_->expiryDates.push_back(d);
1119 times.push_back(vol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, d)
1120 : vol_->timeFromReference(d));
1121 forwards.push_back(eqIndex->forecastFixing(d));
1122 rfDisc.push_back(eqIndex->equityForecastCurve()->discount(d));
1123 divDisc.push_back(eqIndex->equityDividendCurve()->discount(d));
1124 }
1125
1126 calibrationInfo_->times = times;
1127 calibrationInfo_->forwards = forwards;
1128
1129 std::vector<std::vector<Real>> callPricesDelta(times.size(), std::vector<Real>(deltas.size(), 0.0));
1130 std::vector<std::vector<Real>> callPricesMoneyness(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1131
1132 calibrationInfo_->isArbitrageFree = true;
1133
1134 if (reportOnDeltaGrid) {
1135 calibrationInfo_->deltas = deltas;
1136 calibrationInfo_->deltaCallPrices =
1137 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1138 calibrationInfo_->deltaPutPrices =
1139 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1140 calibrationInfo_->deltaGridStrikes =
1141 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1142 calibrationInfo_->deltaGridProb =
1143 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1144 calibrationInfo_->deltaGridImpliedVolatility =
1145 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1146 calibrationInfo_->deltaGridCallSpreadArbitrage =
1147 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(deltas.size(), true));
1148 calibrationInfo_->deltaGridButterflyArbitrage =
1149 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(deltas.size(), true));
1150 TLOG("EquityVolCurve: Delta surface arbitrage analysis result (no calendar spread arbitrage included):");
1151 Real maxTime = QL_MAX_REAL;
1152 if (maxExpiry_ != Date()) {
1153 if (vol_->dayCounter().empty())
1154 maxTime = Actual365Fixed().yearFraction(asof, maxExpiry_);
1155 else
1156 maxTime = vol_->timeFromReference(maxExpiry_);
1157 }
1158 DeltaVolQuote::AtmType at;
1159 DeltaVolQuote::DeltaType dt;
1160 for (Size i = 0; i < times.size(); ++i) {
1161 Real t = times[i];
1162 at = atmType;
1163 dt = deltaType;
1164 // for times after the last quoted expiry we use artificial conventions to avoid problems with strike
1165 // from delta conversions: we use fwd delta always and ATM DNS
1166 if (t > maxTime) {
1167 at = DeltaVolQuote::AtmDeltaNeutral;
1168 dt = DeltaVolQuote::Fwd;
1169 }
1170 bool validSlice = true;
1171 for (Size j = 0; j < deltas.size(); ++j) {
1172 DeltaString d(deltas[j]);
1173 try {
1174 Real strike;
1175 if (d.isAtm()) {
1176 strike = QuantExt::getAtmStrike(dt, at, eqIndex->equitySpot()->value(), rfDisc[i],
1177 divDisc[i], vol_, t);
1178 } else if (d.isCall()) {
1179 strike = QuantExt::getStrikeFromDelta(Option::Call, d.delta(), dt,
1180 eqIndex->equitySpot()->value(), rfDisc[i], divDisc[i],
1181 vol_, t);
1182 } else {
1183 strike =
1184 QuantExt::getStrikeFromDelta(Option::Put, d.delta(), dt, eqIndex->equitySpot()->value(),
1185 rfDisc[i], divDisc[i], vol_, t);
1186 }
1187 Real stddev = std::sqrt(vol_->blackVariance(t, strike));
1188 callPricesDelta[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev);
1189
1190 if (d.isPut()) {
1191 calibrationInfo_->deltaPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, rfDisc[i]);
1192 } else {
1193 calibrationInfo_->deltaCallPrices[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev, rfDisc[i]);
1194 }
1195
1196 calibrationInfo_->deltaGridStrikes[i][j] = strike;
1197 calibrationInfo_->deltaGridImpliedVolatility[i][j] = stddev / std::sqrt(t);
1198 } catch (const std::exception& e) {
1199 validSlice = false;
1200 TLOG("EquityVolCurve: error for time " << t << " delta " << deltas[j] << ": " << e.what());
1201 }
1202 }
1203 if (validSlice) {
1204 try {
1205 QuantExt::CarrMadanMarginalProbability cm(calibrationInfo_->deltaGridStrikes[i], forwards[i],
1206 callPricesDelta[i]);
1207 calibrationInfo_->deltaGridCallSpreadArbitrage[i] = cm.callSpreadArbitrage();
1208 calibrationInfo_->deltaGridButterflyArbitrage[i] = cm.butterflyArbitrage();
1209 if (!cm.arbitrageFree())
1210 calibrationInfo_->isArbitrageFree = false;
1211 calibrationInfo_->deltaGridProb[i] = cm.density();
1213 } catch (const std::exception& e) {
1214 TLOG("error for time " << t << ": " << e.what());
1215 calibrationInfo_->isArbitrageFree = false;
1216 TLOGGERSTREAM("..(invalid slice)..");
1217 }
1218 } else {
1219 calibrationInfo_->isArbitrageFree = false;
1220 TLOGGERSTREAM("..(invalid slice)..");
1221 }
1222 }
1223 TLOG("EquityVolCurve: Delta surface arbitrage analysis completed.");
1224 }
1225
1226 if (reportOnMoneynessGrid) {
1227 calibrationInfo_->moneyness = moneyness;
1228 calibrationInfo_->moneynessCallPrices =
1229 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1230 calibrationInfo_->moneynessPutPrices =
1231 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1232 calibrationInfo_->moneynessGridStrikes =
1233 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1234 calibrationInfo_->moneynessGridProb =
1235 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1236 calibrationInfo_->moneynessGridImpliedVolatility =
1237 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1238 calibrationInfo_->moneynessGridCallSpreadArbitrage =
1239 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(), true));
1240 calibrationInfo_->moneynessGridButterflyArbitrage =
1241 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(), true));
1242 calibrationInfo_->moneynessGridCalendarArbitrage =
1243 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(), true));
1244 for (Size i = 0; i < times.size(); ++i) {
1245 Real t = times[i];
1246 for (Size j = 0; j < moneyness.size(); ++j) {
1247 try {
1248 Real strike = moneyness[j] * forwards[i];
1249 calibrationInfo_->moneynessGridStrikes[i][j] = strike;
1250 Real stddev = std::sqrt(vol_->blackVariance(t, strike));
1251 callPricesMoneyness[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev);
1252 calibrationInfo_->moneynessGridImpliedVolatility[i][j] = stddev / std::sqrt(t);
1253 if (moneyness[j] >= 1) {
1254 calibrationInfo_->moneynessCallPrices[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev, rfDisc[i]);
1255 } else {
1256 calibrationInfo_->moneynessPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, rfDisc[i]);
1257 };
1258 } catch (const std::exception& e) {
1259 TLOG("EquityVolCurve: error for time " << t << " moneyness " << moneyness[j] << ": " << e.what());
1260 }
1261 }
1262 }
1263 if (!times.empty() && !moneyness.empty()) {
1264 try {
1265 QuantExt::CarrMadanSurface cm(times, moneyness, eqIndex->equitySpot()->value(), forwards,
1266 callPricesMoneyness);
1267 for (Size i = 0; i < times.size(); ++i) {
1268 calibrationInfo_->moneynessGridProb[i] = cm.timeSlices()[i].density();
1269 }
1270 calibrationInfo_->moneynessGridCallSpreadArbitrage = cm.callSpreadArbitrage();
1271 calibrationInfo_->moneynessGridButterflyArbitrage = cm.butterflyArbitrage();
1272 calibrationInfo_->moneynessGridCalendarArbitrage = cm.calendarArbitrage();
1273 if (!cm.arbitrageFree())
1274 calibrationInfo_->isArbitrageFree = false;
1275 TLOG("EquityVolCurve: Moneyness surface Arbitrage analysis result:");
1277 } catch (const std::exception& e) {
1278 TLOG("EquityVolCurve: error: " << e.what());
1279 calibrationInfo_->isArbitrageFree = false;
1280 }
1281 TLOG("EquityVolCurve: Moneyness surface Arbitrage analysis completed:");
1282 }
1283 }
1284
1285 DLOG("EquityVolCurve: Building calibration info for eq vol surface completed.");
1286
1287 } catch (std::exception& e) {
1288 QL_FAIL("EquityVolCurve: calibration info building failed: " << e.what());
1289 } catch (...) {
1290 QL_FAIL("EquityVolCurve: calibration info building failed: unknown error");
1291 }
1292}
1293
1294} // namespace data
1295} // namespace ore
const std::vector< bool > & butterflyArbitrage() const
const std::vector< bool > & callSpreadArbitrage() const
const std::vector< Real > & density() const
const std::vector< std::vector< bool > > & butterflyArbitrage() const
const std::vector< std::vector< bool > > & calendarArbitrage() const
const std::vector< std::vector< bool > > & callSpreadArbitrage() const
const std::vector< CarrMadanMarginalProbability > & timeSlices() const
Correlation curve description.
Definition: curvespec.hpp:467
virtual const vector< string > & quotes()
Return all the market quotes required for this config.
Definition: curveconfig.hpp:69
Container class for all Curve Configurations.
const std::string & curveConfigID() const
Definition: curvespec.hpp:83
string name() const
returns the unique curve name
Definition: curvespec.hpp:78
Utility class for handling delta strings ATM, 10P, 25C, ... used e.g. for FX Surfaces.
Definition: strike.hpp:81
bool isCall() const
Definition: strike.hpp:86
bool isAtm() const
Definition: strike.hpp:84
Real delta() const
Definition: strike.hpp:87
bool isPut() const
Definition: strike.hpp:85
Equity curve description.
Definition: curvespec.hpp:349
QuantLib::ext::shared_ptr< VolatilityConfig > volatilityConfig_
void buildCalibrationInfo(const QuantLib::Date &asof, const CurveConfigurations &curveConfigs, const EquityVolatilityCurveConfig &config, const Handle< QuantExt::EquityIndex2 > &eqIndex)
Build the calibration info.
QuantLib::ext::shared_ptr< FxEqCommVolCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< BlackVolTermStructure > vol_
EquityVolCurve()
Default constructor.
QuantLib::Calendar calendar_
void buildVolatility(const QuantLib::Date &asof, const EquityVolatilityCurveConfig &vc, const ConstantVolatilityConfig &cvc, const Loader &loader)
Build a volatility structure from a single constant volatility quote.
const EquityVolatilityCurveSpec & spec() const
QuantLib::DayCounter dayCounter_
Equity volatility structure configuration.
const ReportConfig & reportConfig() const
Equity Volatility curve description.
Definition: curvespec.hpp:375
FX Volatility curve description.
Definition: curvespec.hpp:288
Market data loader base class.
Definition: loader.hpp:47
virtual QuantLib::ext::shared_ptr< MarketDatum > get(const std::string &name, const QuantLib::Date &d) const
get quote by its unique name, throws if not existent, override in derived classes for performance
Definition: loader.cpp:24
QuantLib::Handle< QuantExt::FxIndex > fxIndex(const string &fxIndex, const string &configuration=Market::defaultConfiguration) const
Definition: market.cpp:151
const std::string & correlationCurve() const
const std::string & fxVolatilityCurve() const
const std::string & proxyVolatilityCurve() const
const MarketDatum::QuoteType & quoteType() const
const boost::optional< bool > reportOnDeltaGrid() const
const boost::optional< std::vector< std::string > > & deltas() const
const boost::optional< bool > reportOnMoneynessGrid() const
const boost::optional< std::vector< Period > > & expiries() const
const boost::optional< std::vector< Real > > & moneyness() const
const std::vector< std::string > & putDeltas() const
const std::vector< std::string > & callDeltas() const
const std::vector< std::string > & expiries() const
const std::string & atmDeltaType() const
const std::string & timeInterpolation() const
const std::string & strikeInterpolation() const
const std::string & strikeExtrapolation() const
const std::string & timeExtrapolation() const
Wrapper class for building Equity volatility structures.
Date getDateFromDateOrPeriod(const string &token, Date asof, QuantLib::Calendar cal, QuantLib::BusinessDayConvention bdc)
Get a date from a date string or period.
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Definition: parsers.cpp:157
QuantLib::Real convertMinorToMajorCurrency(const std::string &s, QuantLib::Real value)
Convert a value from a minor ccy to major.
Definition: parsers.cpp:324
DeltaVolQuote::AtmType parseAtmType(const std::string &s)
Convert text to QuantLib::DeltaVolQuote::AtmType.
Definition: parsers.cpp:746
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
Definition: parsers.cpp:209
DeltaVolQuote::DeltaType parseDeltaType(const std::string &s)
Convert text to QuantLib::DeltaVolQuote::DeltaType.
Definition: parsers.cpp:763
Map text representations to QuantLib/QuantExt types.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define TLOGGERSTREAM(text)
Definition: log.hpp:633
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
Market Datum parser.
Real getAtmStrike(DeltaVolQuote::DeltaType dt, DeltaVolQuote::AtmType at, Real spot, Real domDiscount, Real forDiscount, boost::shared_ptr< BlackVolTermStructure > vol, Real t, Real accuracy, Size maxIterations)
std::string arbitrageAsString(const CarrMadanMarginalProbabilityClass &cm)
Real getStrikeFromDelta(Option::Type optionType, Real delta, DeltaVolQuote::DeltaType dt, Real spot, Real domDiscount, Real forDiscount, boost::shared_ptr< BlackVolTermStructure > vol, Real t, Real accuracy, Size maxIterations)
MoneynessStrike::Type parseMoneynessType(const string &type)
Parse MoneynessStrike::Type from type.
Definition: strike.cpp:252
ReportConfig effectiveReportConfig(const ReportConfig &globalConfig, const ReportConfig &localConfig)
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
Extrapolation parseExtrapolation(const string &s)
Parse Extrapolation from string.
Definition: parsers.cpp:778
QuantLib::ext::shared_ptr< Expiry > parseExpiry(const string &strExpiry)
Parse an Expiry from its string representation, strExpiry.
Definition: expiry.cpp:110
boost::optional< Wildcard > getUniqueWildcard(const C &c)
checks if at most one element in C has a wild card and returns it in this case
Definition: wildcard.hpp:65
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
vector< Real > strikes
vector< string > curveConfigs
string conversion utilities
utilities for wildcard handling