Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
scenariosimmarket.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19/*! \file scenario/scenariosimmarket.cpp
20 \brief A Market class that can be updated by Scenarios
21 \ingroup
22*/
23
29
37
84
85#include <ql/instruments/makecapfloor.hpp>
86#include <ql/math/interpolations/loginterpolation.hpp>
87#include <ql/termstructures/credit/interpolatedsurvivalprobabilitycurve.hpp>
88#include <ql/termstructures/defaulttermstructure.hpp>
89#include <ql/termstructures/volatility/capfloor/capfloortermvolatilitystructure.hpp>
90#include <ql/termstructures/volatility/capfloor/capfloortermvolsurface.hpp>
91#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
92#include <ql/termstructures/volatility/equityfx/blackvoltermstructure.hpp>
93#include <ql/termstructures/volatility/optionlet/strippedoptionlet.hpp>
94#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
95#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
96#include <ql/termstructures/volatility/swaption/swaptionvolcube.hpp>
97#include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp>
98#include <ql/termstructures/volatility/swaption/swaptionvolstructure.hpp>
99#include <ql/termstructures/yield/discountcurve.hpp>
100#include <ql/time/calendars/target.hpp>
101#include <ql/time/calendars/weekendsonly.hpp>
102#include <ql/time/daycounters/actual365fixed.hpp>
103#include <ql/time/daycounters/actualactual.hpp>
104#include <ql/quotes/compositequote.hpp>
105#include <ql/quotes/derivedquote.hpp>
106
107#include <boost/algorithm/string.hpp>
108#include <boost/timer/timer.hpp>
109
110using namespace QuantLib;
111using namespace QuantExt;
112using namespace ore::data;
113using namespace std;
114
115namespace {
116
117// Utility function that is in catch blocks below
118void processException(bool continueOnError, const std::exception& e, const std::string& curveId = "",
120 const bool simDataWritten = false) {
121 string curve;
123 curve = to_string(keyType) + "/";
124 curve += curveId;
125
126 std::string message = "skipping this object in scenario sim market";
127 if (!curve.empty()) {
128 message += " (scenario data was ";
129 if (!simDataWritten)
130 message += "not ";
131 message += "written for this object.)";
132 }
133 if (continueOnError) {
134 std::string exceptionMessage = e.what();
135 /* We do not log a structured curve error message, if the exception message indicates that the problem
136 already occurred in the inti market. In this case we have already logged a structured error there. */
137 if (boost::starts_with(exceptionMessage, "did not find object ")) {
138 ALOG("CurveID: " << curve << ": " << message << ": " << exceptionMessage);
139 } else {
140 StructuredCurveErrorMessage(curve, message, exceptionMessage).log();
141 }
142 } else {
143 QL_FAIL("Object with CurveID '" << curve << "' failed to build in scenario sim market: " << e.what());
144 }
145}
146} // namespace
147
148namespace ore {
149namespace analytics {
150
152
153 if (y == ore::data::YieldCurveType::Discount) {
155 } else if (y == ore::data::YieldCurveType::Yield) {
157 } else if (y == ore::data::YieldCurveType::EquityDividend) {
159 } else {
160 QL_FAIL("yieldCurveType not supported");
161 }
162}
163
165
167 return ore::data::YieldCurveType::Discount;
168 } else if (rf == RiskFactorKey::KeyType::YieldCurve) {
169 return ore::data::YieldCurveType::Yield;
170 } else if (rf == RiskFactorKey::KeyType::DividendYield) {
171 return ore::data::YieldCurveType::EquityDividend;
172 } else {
173 QL_FAIL("RiskFactorKey::KeyType not supported");
174 }
175}
176
177namespace {
178ReactionToTimeDecay parseDecayMode(const string& s) {
179 static map<string, ReactionToTimeDecay> m = {{"ForwardVariance", ForwardForwardVariance},
180 {"ConstantVariance", ConstantVariance}};
181
182 auto it = m.find(s);
183 if (it != m.end()) {
184 return it->second;
185 } else {
186 QL_FAIL("Decay mode \"" << s << "\" not recognized");
187 }
188}
189
190void checkDayCounterConsistency(const std::string& curveId, const DayCounter& initCurveDayCounter,
191 const DayCounter& simCurveDayCounter) {
192 if (initCurveDayCounter != simCurveDayCounter) {
193 std::string initDcName = initCurveDayCounter.empty() ? "(empty)" : initCurveDayCounter.name();
194 std::string ssmDcName = simCurveDayCounter.empty() ? "(empty)" : simCurveDayCounter.name();
195 ALOG("inconsistent day counters: when using spreaded curves in scenario sim market, the init curve day counter"
196 "(" +
197 initDcName + ") should be equal to the ssm day counter (" + ssmDcName +
198 "), continuing anyway, please consider fixing this in either the initial market or ssm "
199 "configuration");
200 }
201}
202
203QuantLib::ext::shared_ptr<YieldTermStructure>
204makeYieldCurve(const std::string& curveId, const bool spreaded, const Handle<YieldTermStructure>& initMarketTs,
205 const std::vector<Real>& yieldCurveTimes, const std::vector<Handle<Quote>>& quotes, const DayCounter& dc,
206 const Calendar& cal, const std::string& interpolation, const std::string& extrapolation) {
207 if (ObservationMode::instance().mode() == ObservationMode::Mode::Unregister && !spreaded) {
208 return QuantLib::ext::shared_ptr<YieldTermStructure>(QuantLib::ext::make_shared<QuantExt::InterpolatedDiscountCurve>(
209 yieldCurveTimes, quotes, 0, cal, dc,
210 interpolation == "LogLinear" ? QuantExt::InterpolatedDiscountCurve::Interpolation::logLinear
211 : QuantExt::InterpolatedDiscountCurve::Interpolation::linearZero,
212 extrapolation == "FlatZero" ? QuantExt::InterpolatedDiscountCurve::Extrapolation::flatZero
213 : QuantExt::InterpolatedDiscountCurve::Extrapolation::flatFwd));
214 } else {
215 if (spreaded) {
216 checkDayCounterConsistency(curveId, initMarketTs->dayCounter(), dc);
217 return QuantLib::ext::make_shared<QuantExt::SpreadedDiscountCurve>(
218 initMarketTs, yieldCurveTimes, quotes,
219 interpolation == "LogLinear" ? QuantExt::SpreadedDiscountCurve::Interpolation::logLinear
220 : QuantExt::SpreadedDiscountCurve::Interpolation::linearZero,
221 extrapolation == "FlatZero" ? SpreadedDiscountCurve::Extrapolation::flatZero
222 : SpreadedDiscountCurve::Extrapolation::flatFwd);
223 } else {
224 auto idc = QuantLib::ext::make_shared<QuantExt::InterpolatedDiscountCurve2>(
225 yieldCurveTimes, quotes, dc,
226 interpolation == "LogLinear" ? QuantExt::InterpolatedDiscountCurve2::Interpolation::logLinear
227 : QuantExt::InterpolatedDiscountCurve2::Interpolation::linearZero,
228 extrapolation == "FlatZero" ? InterpolatedDiscountCurve2::Extrapolation::flatZero
229 : InterpolatedDiscountCurve2::Extrapolation::flatFwd);
230 idc->setAdjustReferenceDate(false);
231 return idc;
232 }
233 }
234}
235
236} // namespace
237
238void ScenarioSimMarket::writeSimData(std::map<RiskFactorKey, QuantLib::ext::shared_ptr<SimpleQuote>>& simDataTmp,
239 std::map<RiskFactorKey, Real>& absoluteSimDataTmp,
240 const RiskFactorKey::KeyType keyType, const std::string& name,
241 const std::vector<std::vector<Real>>& coordinates) {
242 simData_.insert(simDataTmp.begin(), simDataTmp.end());
243 absoluteSimData_.insert(absoluteSimDataTmp.begin(), absoluteSimDataTmp.end());
244 coordinatesData_.insert(std::make_tuple(keyType, name, coordinates));
245 simDataTmp.clear();
246 absoluteSimDataTmp.clear();
247}
248
249void ScenarioSimMarket::addYieldCurve(const QuantLib::ext::shared_ptr<Market>& initMarket, const std::string& configuration,
250 const RiskFactorKey::KeyType rf, const string& key, const vector<Period>& tenors,
251 bool& simDataWritten, bool simulate, bool spreaded) {
252 Handle<YieldTermStructure> wrapper = (riskFactorYieldCurve(rf) == ore::data::YieldCurveType::Discount)
253 ? initMarket->discountCurve(key, configuration)
254 : initMarket->yieldCurve(riskFactorYieldCurve(rf), key, configuration);
255 QL_REQUIRE(!wrapper.empty(), "yield curve not provided for " << key);
256 QL_REQUIRE(tenors.front() > 0 * Days, "yield curve tenors must not include t=0");
257 // include today
258
259 // constructing yield curves
260 DayCounter dc = wrapper->dayCounter();
261 vector<Time> yieldCurveTimes(1, 0.0); // include today
262 vector<Date> yieldCurveDates(1, asof_);
263 for (auto& tenor : tenors) {
264 yieldCurveTimes.push_back(dc.yearFraction(asof_, asof_ + tenor));
265 yieldCurveDates.push_back(asof_ + tenor);
266 }
267
268 vector<Handle<Quote>> quotes;
269 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(1.0));
270 quotes.push_back(Handle<Quote>(q));
271 vector<Real> discounts(yieldCurveTimes.size());
272 std::map<RiskFactorKey, QuantLib::ext::shared_ptr<SimpleQuote>> simDataTmp;
273 std::map<RiskFactorKey, Real> absoluteSimDataTmp;
274 for (Size i = 0; i < yieldCurveTimes.size() - 1; i++) {
275 Real val = wrapper->discount(yieldCurveDates[i + 1]);
276 DLOG("ScenarioSimMarket yield curve " << rf << " " << key << " discount[" << i << "]=" << val);
277 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(spreaded ? 1.0 : val));
278 Handle<Quote> qh(q);
279 quotes.push_back(qh);
280
281 // Check if the risk factor is simulated before adding it
282 if (simulate) {
283 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(rf, key, i), std::forward_as_tuple(q));
284 // if generating spreaded scenarios, add the absolute value as well
285 if (spreaded) {
286 absoluteSimDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(rf, key, i),
287 std::forward_as_tuple(val));
288 }
289 }
290 }
291
292 writeSimData(simDataTmp, absoluteSimDataTmp, rf, key,
293 {std::vector<Real>(std::next(yieldCurveTimes.begin(), 1), yieldCurveTimes.end())});
294 simDataWritten = true;
295
296 QuantLib::ext::shared_ptr<YieldTermStructure> yieldCurve =
297 makeYieldCurve(key, spreaded, wrapper, yieldCurveTimes, quotes, dc, TARGET(), parameters_->interpolation(),
298 parameters_->extrapolation());
299
300 Handle<YieldTermStructure> ych(yieldCurve);
301 if (wrapper->allowsExtrapolation())
302 ych->enableExtrapolation();
303 yieldCurves_.insert(make_pair(make_tuple(Market::defaultConfiguration, riskFactorYieldCurve(rf), key), ych));
304}
305
306ScenarioSimMarket::ScenarioSimMarket(const QuantLib::ext::shared_ptr<Market>& initMarket,
307 const QuantLib::ext::shared_ptr<ScenarioSimMarketParameters>& parameters,
308 const std::string& configuration, const CurveConfigurations& curveConfigs,
309 const TodaysMarketParameters& todaysMarketParams, const bool continueOnError,
310 const bool useSpreadedTermStructures, const bool cacheSimData,
311 const bool allowPartialScenarios, const IborFallbackConfig& iborFallbackConfig,
312 const bool handlePseudoCurrencies,
313 const QuantLib::ext::shared_ptr<Scenario>& offSetScenario)
314 : ScenarioSimMarket(initMarket, parameters, QuantLib::ext::make_shared<FixingManager>(initMarket->asofDate()),
315 configuration, curveConfigs, todaysMarketParams, continueOnError, useSpreadedTermStructures,
316 cacheSimData, allowPartialScenarios, iborFallbackConfig, handlePseudoCurrencies,
317 offSetScenario) {}
318
320 const QuantLib::ext::shared_ptr<Market>& initMarket, const QuantLib::ext::shared_ptr<ScenarioSimMarketParameters>& parameters,
321 const QuantLib::ext::shared_ptr<FixingManager>& fixingManager, const std::string& configuration,
322 const ore::data::CurveConfigurations& curveConfigs, const ore::data::TodaysMarketParameters& todaysMarketParams,
323 const bool continueOnError, const bool useSpreadedTermStructures, const bool cacheSimData,
324 const bool allowPartialScenarios, const IborFallbackConfig& iborFallbackConfig, const bool handlePseudoCurrencies, const QuantLib::ext::shared_ptr<Scenario>& offSetScenario)
325 : SimMarket(handlePseudoCurrencies), parameters_(parameters), fixingManager_(fixingManager),
326 filter_(QuantLib::ext::make_shared<ScenarioFilter>()), useSpreadedTermStructures_(useSpreadedTermStructures),
327 cacheSimData_(cacheSimData), allowPartialScenarios_(allowPartialScenarios),
328 iborFallbackConfig_(iborFallbackConfig), offsetScenario_(offSetScenario) {
329
330 LOG("building ScenarioSimMarket...");
331 asof_ = initMarket->asofDate();
332 DLOG("AsOf " << QuantLib::io::iso_date(asof_));
333
334 // check ssm parameters
335 QL_REQUIRE(parameters_->interpolation() == "LogLinear" || parameters_->interpolation() == "LinearZero",
336 "ScenarioSimMarket: Interpolation (" << parameters_->interpolation()
337 << ") must be set to 'LogLinear' or 'LinearZero'");
338 QL_REQUIRE(parameters_->extrapolation() == "FlatZero" || parameters_->extrapolation() == "FlatFwd",
339 "ScenarioSimMarket: YieldCurves / Extrapolation ('" << parameters_->extrapolation()
340 << "') must be set to 'FlatZero' or 'FlatFwd'");
341 QL_REQUIRE(parameters_->defaultCurveExtrapolation() == "FlatZero" ||
342 parameters_->defaultCurveExtrapolation() == "FlatFwd",
343 "ScenarioSimMarket: DefaultCurves / Extrapolation ('" << parameters_->extrapolation()
344 << "') must be set to 'FlatZero' or 'FlatFwd'");
345
346 for (const auto& param : parameters->parameters()) {
347 try {
348 // we populate the temp containers for each curve and write the result to the global
349 // containers only if the set of data points is complete for this curve
350 std::map<RiskFactorKey, QuantLib::ext::shared_ptr<SimpleQuote>> simDataTmp;
351 std::map<RiskFactorKey, Real> absoluteSimDataTmp;
352
353 boost::timer::cpu_timer timer;
354
355 switch (param.first) {
357 std::map<std::string, Handle<Quote>> fxQuotes;
358 for (const auto& name : param.second.second) {
359 bool simDataWritten = false;
360 try {
361 // constructing fxSpots_
362 DLOG("adding " << name << " FX rates");
363 Real v = initMarket->fxSpot(name, configuration)->value();
364 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 1.0 : v);
366 auto m = [v](Real x) { return x * v; };
367 fxQuotes[name] = Handle<Quote>(
368 QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(Handle<Quote>(q), m));
369 } else {
370 fxQuotes[name] = Handle<Quote>(q);
371 }
372 // Check if the risk factor is simulated before adding it
373 if (param.second.first) {
374 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name),
375 std::forward_as_tuple(q));
377 absoluteSimDataTmp.emplace(std::piecewise_construct,
378 std::forward_as_tuple(param.first, name),
379 std::forward_as_tuple(v));
380 }
381 }
382 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
383 simDataWritten = true;
384 } catch (const std::exception& e) {
385 processException(continueOnError, e, name, param.first, simDataWritten);
386 }
387 }
388 fx_ = QuantLib::ext::make_shared<FXTriangulation>(fxQuotes);
389 break;
390 }
391
394 for (const auto& name : param.second.second) {
395 bool simDataWritten = false;
396 try {
397 DLOG("building " << name << " yield curve..");
398 vector<Period> tenors = parameters->yieldCurveTenors(name);
399 addYieldCurve(initMarket, configuration, param.first, name, tenors, simDataWritten,
400 param.second.first, useSpreadedTermStructures_);
401 DLOG("building " << name << " yield curve done");
402 } catch (const std::exception& e) {
403 processException(continueOnError, e, name, param.first, simDataWritten);
404 }
405 }
406 break;
407
409 // make sure we built overnight indices first, so that we can build ibor fallback indices
410 // that depend on them
411 std::vector<std::string> indices;
412 for (auto const& i : param.second.second) {
413 bool isOn = false;
414 try {
415 isOn = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(*initMarket->iborIndex(i, configuration)) !=
416 nullptr;
417 } catch (...) {
418 }
419 if (isOn)
420 indices.insert(indices.begin(), i);
421 else
422 indices.push_back(i);
423 }
424 // loop over sorted indices and build them
425 for (const auto& name : indices) {
426 bool simDataWritten = false;
427 try {
428 DLOG("building " << name << " index curve");
429 std::vector<string> indexTokens;
430 split(indexTokens, name, boost::is_any_of("-"));
431 Handle<IborIndex> index;
432 if (indexTokens[1] == "GENERIC") {
433 // If we have a generic curve build the index using the index currency's discount curve
434 // no need to check for a convention based ibor index in this case
435 index = Handle<IborIndex>(
436 parseIborIndex(name, initMarket->discountCurve(indexTokens[0], configuration)));
437 } else {
438 index = initMarket->iborIndex(name, configuration);
439 }
440 QL_REQUIRE(!index.empty(), "index object for " << name << " not provided");
441 Handle<YieldTermStructure> wrapperIndex = index->forwardingTermStructure();
442 QL_REQUIRE(!wrapperIndex.empty(), "no termstructure for index " << name);
443 vector<string> keys(parameters->yieldCurveTenors(name).size());
444
445 DayCounter dc = wrapperIndex->dayCounter();
446 vector<Time> yieldCurveTimes(1, 0.0); // include today
447 vector<Date> yieldCurveDates(1, asof_);
448 QL_REQUIRE(parameters->yieldCurveTenors(name).front() > 0 * Days,
449 "yield curve tenors must not include t=0");
450 for (auto& tenor : parameters->yieldCurveTenors(name)) {
451 yieldCurveTimes.push_back(dc.yearFraction(asof_, asof_ + tenor));
452 yieldCurveDates.push_back(asof_ + tenor);
453 }
454
455 // include today
456 vector<Handle<Quote>> quotes;
457 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(1.0));
458 quotes.push_back(Handle<Quote>(q));
459
460 for (Size i = 0; i < yieldCurveTimes.size() - 1; i++) {
461 Real val = wrapperIndex->discount(yieldCurveDates[i + 1]);
462 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(useSpreadedTermStructures_ ? 1.0 : val));
463 Handle<Quote> qh(q);
464 quotes.push_back(qh);
465
466 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name, i),
467 std::forward_as_tuple(q));
469 absoluteSimDataTmp.emplace(std::piecewise_construct,
470 std::forward_as_tuple(param.first, name, i),
471 std::forward_as_tuple(val));
472 }
473 // FIXME where do we check whether the risk factor is simulated?
474 DLOG("ScenarioSimMarket index curve " << name << " discount[" << i << "]=" << val);
475 }
476
477 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name,
478 {std::vector<Real>(std::next(yieldCurveTimes.begin(), 1), yieldCurveTimes.end())});
479 simDataWritten = true;
480
481 QuantLib::ext::shared_ptr<YieldTermStructure> indexCurve = makeYieldCurve(
482 name, useSpreadedTermStructures_, wrapperIndex, yieldCurveTimes, quotes, dc,
483 index->fixingCalendar(), parameters_->interpolation(), parameters_->extrapolation());
484
485 Handle<YieldTermStructure> ich(indexCurve);
486 if (wrapperIndex->allowsExtrapolation())
487 ich->enableExtrapolation();
488
489 QuantLib::ext::shared_ptr<IborIndex> i = index->clone(ich);
491 // handle ibor fallback indices
492 auto fallbackData = iborFallbackConfig_.fallbackData(name);
493 auto f = iborIndices_.find(make_pair(Market::defaultConfiguration, fallbackData.rfrIndex));
494 QL_REQUIRE(f != iborIndices_.end(),
495 "Could not build ibor fallback index '"
496 << name << "', because rfr index '" << fallbackData.rfrIndex
497 << "' is not present in scenario sim market, is the rfr index in the "
498 "scenario sim market parameters?");
499 auto rfrInd = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(*f->second);
500 QL_REQUIRE(rfrInd != nullptr,
501 "Could not cast '"
502 << fallbackData.rfrIndex
503 << "' to overnight index when building the ibor fallback index '" << name
504 << "'");
505 if (auto original = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(i))
506 i = QuantLib::ext::make_shared<QuantExt::FallbackOvernightIndex>(
507 original, rfrInd, fallbackData.spread, fallbackData.switchDate,
509 else
510 i = QuantLib::ext::make_shared<QuantExt::FallbackIborIndex>(
511 i, rfrInd, fallbackData.spread, fallbackData.switchDate,
513 DLOG("built ibor fall back index '"
514 << name << "' with rfr index '" << fallbackData.rfrIndex << "', spread "
515 << fallbackData.spread << ", use rfr curve in scen sim market: " << std::boolalpha
517 }
518 iborIndices_.insert(
519 make_pair(make_pair(Market::defaultConfiguration, name), Handle<IborIndex>(i)));
520 DLOG("building " << name << " index curve done");
521 } catch (const std::exception& e) {
522 processException(continueOnError, e, name, param.first, simDataWritten);
523 }
524 }
525 break;
526 }
527
529 for (const auto& name : param.second.second) {
530 bool simDataWritten = false;
531 try {
532 // building equity spots
533 DLOG("adding " << name << " equity spot...");
534 Real spotVal = initMarket->equitySpot(name, configuration)->value();
535 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 1.0 : spotVal);
537 auto m = [spotVal](Real x) { return x * spotVal; };
538 equitySpots_.insert(
539 make_pair(make_pair(Market::defaultConfiguration, name),
540 Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(
541 Handle<Quote>(q), m))));
542 } else {
543 equitySpots_.insert(
544 make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
545 }
546 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name),
547 std::forward_as_tuple(q));
549 absoluteSimDataTmp.emplace(std::piecewise_construct,
550 std::forward_as_tuple(param.first, name),
551 std::forward_as_tuple(spotVal));
552 }
553 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
554 simDataWritten = true;
555 DLOG("adding " << name << " equity spot done");
556 } catch (const std::exception& e) {
557 processException(continueOnError, e, name, param.first, simDataWritten);
558 }
559 }
560 break;
561
563 for (const auto& name : param.second.second) {
564 bool simDataWritten = false;
565 try {
566 DLOG("building " << name << " equity dividend yield curve..");
567 vector<Period> tenors = parameters->equityDividendTenors(name);
568 addYieldCurve(initMarket, configuration, param.first, name, tenors, simDataWritten,
569 param.second.first, useSpreadedTermStructures_);
570 DLOG("building " << name << " equity dividend yield curve done");
571
572 // Equity spots and Yield/Index curves added first so we can now build equity index
573 // First get Forecast Curve
574 string forecastCurve;
575 if (curveConfigs.hasEquityCurveConfig(name)) {
576 // From the equity config, get the currency and forecast curve of the equity
577 auto eqVolConfig = curveConfigs.equityCurveConfig(name);
578 string forecastName = eqVolConfig->forecastingCurve();
579 string eqCcy = eqVolConfig->currency();
580 // Build a YieldCurveSpec and extract the yieldCurveSpec name
581 YieldCurveSpec ycspec(eqCcy, forecastName);
582 forecastCurve = ycspec.name();
583 TLOG("Got forecast curve '" << forecastCurve << "' from equity curve config for " << name);
584 }
585
586 // Get the nominal term structure from this scenario simulation market
587 Handle<YieldTermStructure> forecastTs =
588 getYieldCurve(forecastCurve, todaysMarketParams, Market::defaultConfiguration);
589 Handle<EquityIndex2> curve = initMarket->equityCurve(name, configuration);
590
591 // If forecast term structure is empty, fall back on this scenario simulation market's discount
592 // curve
593 if (forecastTs.empty()) {
594 string ccy = curve->currency().code();
595 TLOG("Falling back on the discount curve for currency '"
596 << ccy << "' for equity forecast curve '" << name << "'");
597 forecastTs = discountCurve(ccy);
598 }
599 QuantLib::ext::shared_ptr<EquityIndex2> ei(
600 curve->clone(equitySpot(name, configuration), forecastTs,
601 yieldCurve(YieldCurveType::EquityDividend, name, configuration)));
602 Handle<EquityIndex2> eh(ei);
603 equityCurves_.insert(make_pair(make_pair(Market::defaultConfiguration, name), eh));
604 } catch (const std::exception& e) {
605 processException(continueOnError, e, name, param.first, simDataWritten);
606 }
607 }
608 break;
609
611 for (const auto& name : param.second.second) {
612 // security spreads and recovery rates are optional
613 try {
614 DLOG("Adding security spread " << name << " from configuration " << configuration);
615 Real v = initMarket->securitySpread(name, configuration)->value();
616 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : v);
618 auto m = [v](Real x) { return x + v; };
619 securitySpreads_.insert(
620 make_pair(make_pair(Market::defaultConfiguration, name),
621 Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(
622 Handle<Quote>(q), m))));
623 } else {
624 securitySpreads_.insert(
625 make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
626 }
627 if (param.second.first) {
628 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name),
629 std::forward_as_tuple(q));
631 absoluteSimDataTmp.emplace(std::piecewise_construct,
632 std::forward_as_tuple(param.first, name),
633 std::forward_as_tuple(v));
634 }
635 }
636 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
637
638 } catch (const std::exception& e) {
639 DLOG("skipping this object: " << e.what());
640 }
641
642 try {
643 DLOG("Adding security recovery rate " << name << " from configuration " << configuration);
644 Real v = initMarket->recoveryRate(name, configuration)->value();
645 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 1.0 : v);
647 auto m = [v](Real x) { return x * v; };
648 recoveryRates_.insert(
649 make_pair(make_pair(Market::defaultConfiguration, name),
650 Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(
651 Handle<Quote>(q), m))));
652 } else {
653 recoveryRates_.insert(
654 make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
655 }
656
657 // TODO this comes from the default curves section in the parameters,
658 // do we want to specify the simulation of security recovery rates separately?
659 if (parameters->simulateRecoveryRates()) {
660 simDataTmp.emplace(std::piecewise_construct,
661 std::forward_as_tuple(RiskFactorKey::KeyType::RecoveryRate, name),
662 std::forward_as_tuple(q));
664 absoluteSimDataTmp.emplace(std::piecewise_construct,
665 std::forward_as_tuple(param.first, name),
666 std::forward_as_tuple(v));
667 }
668 }
669 writeSimData(simDataTmp, absoluteSimDataTmp, RiskFactorKey::KeyType::RecoveryRate, name, {});
670 } catch (const std::exception& e) {
671 DLOG("skipping this object: " << e.what());
672 }
673 }
674 break;
675
678 for (const auto& name : param.second.second) {
679 bool simDataWritten = false;
680 try {
681 // set parameters for swaption resp. yield vols
682 RelinkableHandle<SwaptionVolatilityStructure> wrapper;
683 vector<Period> optionTenors, underlyingTenors;
684 vector<Real> strikeSpreads;
685 string shortSwapIndexBase = "", swapIndexBase = "";
686 bool isCube, isAtm, simulateAtmOnly;
688 DLOG("building " << name << " swaption volatility curve...");
689 wrapper.linkTo(*initMarket->swaptionVol(name, configuration));
690 shortSwapIndexBase = initMarket->shortSwapIndexBase(name, configuration);
691 swapIndexBase = initMarket->swapIndexBase(name, configuration);
692 isCube = parameters->swapVolIsCube(name);
693 optionTenors = parameters->swapVolExpiries(name);
694 underlyingTenors = parameters->swapVolTerms(name);
695 strikeSpreads = parameters->swapVolStrikeSpreads(name);
696 simulateAtmOnly = parameters->simulateSwapVolATMOnly();
697 } else {
698 DLOG("building " << name << " yield volatility curve...");
699 wrapper.linkTo(*initMarket->yieldVol(name, configuration));
700 isCube = false;
701 optionTenors = parameters->yieldVolExpiries();
702 underlyingTenors = parameters->yieldVolTerms();
703 strikeSpreads = {0.0};
704 simulateAtmOnly = true;
705 }
706 DLOG("Initial market " << name << " yield volatility type = " << wrapper->volatilityType());
707
708 // Check if underlying market surface is atm or smile
709 isAtm = QuantLib::ext::dynamic_pointer_cast<SwaptionVolatilityMatrix>(*wrapper) != nullptr ||
710 QuantLib::ext::dynamic_pointer_cast<ConstantSwaptionVolatility>(*wrapper) != nullptr;
711
712 Handle<SwaptionVolatilityStructure> svp;
713 if (param.second.first) {
714 LOG("Simulating yield vols for ccy " << name);
715 DLOG("YieldVol T0 source is atm : " << (isAtm ? "True" : "False"));
716 DLOG("YieldVol ssm target is cube : " << (isCube ? "True" : "False"));
717 DLOG("YieldVol simulate atm only : " << (simulateAtmOnly ? "True" : "False"));
718 if (simulateAtmOnly) {
719 QL_REQUIRE(strikeSpreads.size() == 1 && close_enough(strikeSpreads[0], 0),
720 "for atmOnly strikeSpreads must be {0.0}");
721 }
722 QuantLib::ext::shared_ptr<QuantLib::SwaptionVolatilityCube> cube;
723 if (isCube && !isAtm) {
724 QuantLib::ext::shared_ptr<SwaptionVolCubeWithATM> tmp =
725 QuantLib::ext::dynamic_pointer_cast<SwaptionVolCubeWithATM>(*wrapper);
726 QL_REQUIRE(tmp, "swaption cube missing");
727 cube = tmp->cube();
728 }
729 vector<vector<Handle<Quote>>> quotes, atmQuotes;
730 quotes.resize(optionTenors.size() * underlyingTenors.size(),
731 vector<Handle<Quote>>(strikeSpreads.size(), Handle<Quote>()));
732 atmQuotes.resize(optionTenors.size(),
733 std::vector<Handle<Quote>>(underlyingTenors.size(), Handle<Quote>()));
734 vector<vector<Real>> shift(optionTenors.size(), vector<Real>(underlyingTenors.size(), 0.0));
735 Size atmSlice = std::find_if(strikeSpreads.begin(), strikeSpreads.end(),
736 [](const Real s) { return close_enough(s, 0.0); }) -
737 strikeSpreads.begin();
738 QL_REQUIRE(atmSlice < strikeSpreads.size(),
739 "could not find atm slice (strikeSpreads do not contain 0.0)");
740
741 // convert to normal if
742 // a) we have a swaption (i.e. not a yield) volatility and
743 // b) the T0 term structure is not normal
744 // c) we are not in the situation of simulating ATM only and having a non-normal cube in T0,
745 // since in this case the T0 structure is dynamically used to determine the sim market
746 // vols
747 // d) we do not use spreaded term structures, in which case we keep the original T0
748 // term structure in any case
749 bool convertToNormal = wrapper->volatilityType() != Normal &&
751 (!simulateAtmOnly || isAtm) && !useSpreadedTermStructures_;
752 DLOG("T0 ts is normal : " << (wrapper->volatilityType() == Normal ? "True"
753 : "False"));
754 DLOG("Have swaption vol : "
755 << (param.first == RiskFactorKey::KeyType::SwaptionVolatility ? "True" : "False"));
756 DLOG("Will convert to normal vol : " << (convertToNormal ? "True" : "False"));
757
758 // Set up a vol converter, and create if vol type is not normal
759 SwaptionVolatilityConverter* converter = nullptr;
760 if (convertToNormal) {
761 Handle<SwapIndex> swapIndex = initMarket->swapIndex(swapIndexBase, configuration);
762 Handle<SwapIndex> shortSwapIndex =
763 initMarket->swapIndex(shortSwapIndexBase, configuration);
764 converter = new SwaptionVolatilityConverter(asof_, *wrapper, *swapIndex,
765 *shortSwapIndex, Normal);
766 }
767
768 for (Size k = 0; k < strikeSpreads.size(); ++k) {
769 for (Size i = 0; i < optionTenors.size(); ++i) {
770 for (Size j = 0; j < underlyingTenors.size(); ++j) {
771 Real strike = Null<Real>();
772 if (!simulateAtmOnly && cube)
773 strike = cube->atmStrike(optionTenors[i], underlyingTenors[j]) +
774 strikeSpreads[k];
775 Real vol;
776 if (convertToNormal) {
777 // if not a normal volatility use the converted to convert to normal at
778 // given point
779 vol = converter->convert(wrapper->optionDateFromTenor(optionTenors[i]),
780 underlyingTenors[j], strikeSpreads[k],
781 wrapper->dayCounter(), Normal);
782 } else {
783 vol =
784 wrapper->volatility(optionTenors[i], underlyingTenors[j], strike, true);
785 }
786 QuantLib::ext::shared_ptr<SimpleQuote> q(
787 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
788
789 Size index = i * underlyingTenors.size() * strikeSpreads.size() +
790 j * strikeSpreads.size() + k;
791
792 simDataTmp.emplace(std::piecewise_construct,
793 std::forward_as_tuple(param.first, name, index),
794 std::forward_as_tuple(q));
796 absoluteSimDataTmp.emplace(std::piecewise_construct,
797 std::forward_as_tuple(param.first, name, index),
798 std::forward_as_tuple(vol));
799 }
800 auto tmp = Handle<Quote>(q);
801 quotes[i * underlyingTenors.size() + j][k] = tmp;
802 if (k == atmSlice) {
803 atmQuotes[i][j] = tmp;
804 shift[i][j] =
805 !convertToNormal && wrapper->volatilityType() == ShiftedLognormal
806 ? wrapper->shift(optionTenors[i], underlyingTenors[j])
807 : 0.0;
808 DLOG("AtmVol at " << optionTenors.at(i) << "/" << underlyingTenors.at(j)
809 << " is " << vol << ", shift is " << shift[i][j]
810 << ", (name,index) = (" << name << "," << index << ")");
811 } else {
812 DLOG("SmileVol at " << optionTenors.at(i) << "/" << underlyingTenors.at(j)
813 << "/" << strikeSpreads.at(k) << " is " << vol
814 << ", (name,index) = (" << name << "," << index << ")");
815 }
816 }
817 }
818 }
819
820 std::vector<std::vector<Real>> coordinates(3);
821 for (Size i = 0; i < optionTenors.size(); ++i) {
822 coordinates[0].push_back(
823 wrapper->timeFromReference(wrapper->optionDateFromTenor(optionTenors[i])));
824 }
825 for (Size j = 0; j < underlyingTenors.size(); ++j) {
826 coordinates[1].push_back(wrapper->swapLength(underlyingTenors[j]));
827 }
828 for (Size k = 0; k < strikeSpreads.size(); ++k) {
829 coordinates[2].push_back(strikeSpreads[k]);
830 }
831
832 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, coordinates);
833 simDataWritten = true;
834 bool flatExtrapolation = true; // FIXME: get this from curve configuration
835 VolatilityType volType = convertToNormal ? Normal : wrapper->volatilityType();
836 DayCounter dc = wrapper->dayCounter();
837
839 bool stickyStrike = parameters_->swapVolSmileDynamics(name) == "StickyStrike";
840 QuantLib::ext::shared_ptr<SwapIndex> swapIndex, shortSwapIndex;
841 QuantLib::ext::shared_ptr<SwapIndex> simSwapIndex, simShortSwapIndex;
842 if (!stickyStrike) {
843 if (addSwapIndexToSsm(swapIndexBase, continueOnError)) {
844 simSwapIndex = *this->swapIndex(swapIndexBase, configuration);
845 }
846 if (addSwapIndexToSsm(shortSwapIndexBase, continueOnError)) {
847 simShortSwapIndex = *this->swapIndex(shortSwapIndexBase, configuration);
848 }
849 if (simSwapIndex == nullptr || simShortSwapIndex == nullptr)
850 stickyStrike = true;
851 }
852 if(!swapIndexBase.empty())
853 swapIndex = *initMarket->swapIndex(swapIndexBase, configuration);
854 if(!shortSwapIndexBase.empty())
855 shortSwapIndex = *initMarket->swapIndex(shortSwapIndexBase, configuration);
856 svp =
857 Handle<SwaptionVolatilityStructure>(QuantLib::ext::make_shared<SpreadedSwaptionVolatility>(
858 wrapper, optionTenors, underlyingTenors, strikeSpreads, quotes, swapIndex,
859 shortSwapIndex, simSwapIndex, simShortSwapIndex, !stickyStrike));
860 } else {
861 Handle<SwaptionVolatilityStructure> atm;
862 atm = Handle<SwaptionVolatilityStructure>(QuantLib::ext::make_shared<SwaptionVolatilityMatrix>(
863 wrapper->calendar(), wrapper->businessDayConvention(), optionTenors,
864 underlyingTenors, atmQuotes, dc, flatExtrapolation, volType, shift));
865 atm->enableExtrapolation(); // see below for svp, take this from T0 config?
866 if (simulateAtmOnly) {
867 if (isAtm) {
868 svp = atm;
869 } else {
870 // floating reference date matrix in sim market
871 // if we have a cube, we keep the vol spreads constant under scenarios
872 // notice that cube is from todaysmarket, so it has a fixed reference date,
873 // which means that we keep the smiles constant in terms of vol spreads when
874 // moving forward in time; notice also that the volatility will be "sticky
875 // strike", i.e. it will not react to changes in the ATM level
876 svp = Handle<SwaptionVolatilityStructure>(
877 QuantLib::ext::make_shared<SwaptionVolatilityConstantSpread>(atm, wrapper));
878 }
879 } else {
880 if (isCube) {
881 QuantLib::ext::shared_ptr<SwaptionVolatilityCube> tmp(new QuantExt::SwaptionVolCube2(
882 atm, optionTenors, underlyingTenors, strikeSpreads, quotes,
883 *initMarket->swapIndex(swapIndexBase, configuration),
884 *initMarket->swapIndex(shortSwapIndexBase, configuration), false,
885 flatExtrapolation, false));
886 tmp->setAdjustReferenceDate(false);
887 svp = Handle<SwaptionVolatilityStructure>(
888 QuantLib::ext::make_shared<SwaptionVolCubeWithATM>(tmp));
889 } else {
890 svp = atm;
891 }
892 }
893 }
894 } else {
895 string decayModeString = parameters->swapVolDecayMode();
896 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
897 DLOG("Dynamic (" << wrapper->volatilityType() << ") yield vols (" << decayModeString
898 << ") for qualifier " << name);
899
900 QL_REQUIRE(!QuantLib::ext::dynamic_pointer_cast<ProxySwaptionVolatility>(*wrapper),
901 "DynamicSwaptionVolatilityMatrix does not support ProxySwaptionVolatility surface");
902
903 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atmSlice;
904 if (isAtm)
905 atmSlice = *wrapper;
906 else {
907 auto c = QuantLib::ext::dynamic_pointer_cast<SwaptionVolCubeWithATM>(*wrapper);
908 QL_REQUIRE(c, "internal error - expected swaption cube to be SwaptionVolCubeWithATM.");
909 atmSlice = *c->cube()->atmVol();
910 }
911
912 if (isCube)
913 WLOG("Only ATM slice is considered from init market's cube");
914 QuantLib::ext::shared_ptr<QuantLib::SwaptionVolatilityStructure> svolp =
915 QuantLib::ext::make_shared<QuantExt::DynamicSwaptionVolatilityMatrix>(
916 atmSlice, 0, NullCalendar(), decayMode);
917 svp = Handle<SwaptionVolatilityStructure>(svolp);
918 }
919 svp->setAdjustReferenceDate(false);
920 svp->enableExtrapolation(); // FIXME
921
922 DLOG("Simulation market " << name << " yield volatility type = " << svp->volatilityType());
923
925 swaptionCurves_.insert(make_pair(make_pair(Market::defaultConfiguration, name), svp));
926 swaptionIndexBases_.insert(make_pair(make_pair(Market::defaultConfiguration, name),
927 make_pair(shortSwapIndexBase, swapIndexBase)));
928 swaptionIndexBases_.insert(make_pair(make_pair(Market::defaultConfiguration, name),
929 make_pair(swapIndexBase, swapIndexBase)));
930 } else {
931 yieldVolCurves_.insert(make_pair(make_pair(Market::defaultConfiguration, name), svp));
932 }
933 } catch (const std::exception& e) {
934 processException(continueOnError, e, name, param.first, simDataWritten);
935 }
936 }
937 break;
938
940 for (const auto& name : param.second.second) {
941 bool simDataWritten = false;
942 try {
943 LOG("building " << name << " cap/floor volatility curve...");
944 Handle<OptionletVolatilityStructure> wrapper = initMarket->capFloorVol(name, configuration);
945 auto [iborIndexName, rateComputationPeriod] =
946 initMarket->capFloorVolIndexBase(name, configuration);
947 QuantLib::ext::shared_ptr<IborIndex> iborIndex =
948 iborIndexName.empty() ? nullptr : parseIborIndex(iborIndexName);
949
950 LOG("Initial market cap/floor volatility type = " << wrapper->volatilityType());
951
952 Handle<OptionletVolatilityStructure> hCapletVol;
953
954 // Check if the risk factor is simulated before adding it
955 if (param.second.first) {
956 LOG("Simulating Cap/Floor Optionlet vols for key " << name);
957
958 // Try to get the ibor index that the cap floor structure relates to
959 // We use this to convert Period to Date below to sample from `wrapper`
960 Natural settleDays = 0;
961 bool isOis = false;
962 Calendar iborCalendar;
963 Size onSettlementDays = 0;
964
965 // get the curve config for the index, or if not available for its ccy
966 QuantLib::ext::shared_ptr<CapFloorVolatilityCurveConfig> config;
967 if (curveConfigs.hasCapFloorVolCurveConfig(name)) {
968 config = curveConfigs.capFloorVolCurveConfig(name);
969 } else {
970 if (iborIndex && curveConfigs.hasCapFloorVolCurveConfig(iborIndex->currency().code())) {
971 config = curveConfigs.capFloorVolCurveConfig(iborIndex->currency().code());
972 }
973 }
974
975 // get info from the config if we have one
976 if (config) {
977 settleDays = config->settleDays();
978 onSettlementDays = config->onCapSettlementDays();
979 }
980
981 // derive info from the ibor index
982 if (iborIndex) {
983 iborCalendar = iborIndex->fixingCalendar();
984 isOis = QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(iborIndex) != nullptr;
985 }
986
987 vector<Period> optionTenors = parameters->capFloorVolExpiries(name);
988 vector<Date> optionDates(optionTenors.size());
989
990 vector<Real> strikes = parameters->capFloorVolStrikes(name);
991 bool isAtm = false;
992 // Strikes may be empty here which means that an ATM curve has been configured
993 if (strikes.empty()) {
994 QL_REQUIRE(
995 parameters->capFloorVolIsAtm(name),
996 "Strikes for "
997 << name
998 << " is empty in simulation parameters so expected its ATM flag to be true");
999 strikes = {0.0};
1000 isAtm = true;
1001 }
1002
1003 vector<vector<Handle<Quote>>> quotes(
1004 optionTenors.size(), vector<Handle<Quote>>(strikes.size(), Handle<Quote>()));
1005
1006 DLOG("cap floor use adjusted option pillars = " << std::boolalpha << parameters_->capFloorVolAdjustOptionletPillars());
1007 DLOG("have ibor index = " << std::boolalpha << (iborIndex != nullptr));
1008
1009 Rate atmStrike = Null<Rate>();
1010 for (Size i = 0; i < optionTenors.size(); ++i) {
1011
1012 if (parameters_->capFloorVolAdjustOptionletPillars() && iborIndex) {
1013 // If we ask for cap pillars at tenors t_i for i = 1,...,N, we should attempt to
1014 // place the optionlet pillars at the fixing date of the last optionlet in the cap
1015 // with tenor t_i, if capFloorVolAdjustOptionletPillars is true.
1016 if(isOis) {
1017 Leg capFloor =
1019 CapFloor::Cap, optionTenors[i],
1020 QuantLib::ext::dynamic_pointer_cast<QuantLib::OvernightIndex>(iborIndex),
1021 rateComputationPeriod, 0.0)
1023 .withSettlementDays(onSettlementDays);
1024 if (capFloor.empty()) {
1025 optionDates[i] = asof_ + 1;
1026 } else {
1027 auto lastCoupon = QuantLib::ext::dynamic_pointer_cast<
1029 QL_REQUIRE(lastCoupon, "SSM internal error, could not cast to "
1030 "CappedFlooredOvernightIndexedCoupon "
1031 "when building optionlet vol for '"
1032 << name << "' (index=" << iborIndex->name()
1033 << ")");
1034 optionDates[i] =
1035 std::max(asof_ + 1, lastCoupon->underlying()->fixingDates().front());
1036 }
1037 } else {
1038 QuantLib::ext::shared_ptr<CapFloor> capFloor =
1039 MakeCapFloor(CapFloor::Cap, optionTenors[i], iborIndex, 0.0, 0 * Days);
1040 if (capFloor->floatingLeg().empty()) {
1041 optionDates[i] = asof_ + 1;
1042 } else {
1043 optionDates[i] =
1044 std::max(asof_ + 1, capFloor->lastFloatingRateCoupon()->fixingDate());
1045 }
1046 }
1047 QL_REQUIRE(i == 0 || optionDates[i] > optionDates[i - 1],
1048 "SSM: got non-increasing option dates "
1049 << optionDates[i - 1] << ", " << optionDates[i] << " for tenors "
1050 << optionTenors[i - 1] << ", " << optionTenors[i] << " for index "
1051 << iborIndex->name());
1052 } else {
1053 // Otherwise, just place the optionlet pillars at the configured tenors.
1054 optionDates[i] = wrapper->optionDateFromTenor(optionTenors[i]);
1055 if (iborCalendar != Calendar()) {
1056 // In case the original cap floor surface has the incorrect calendar configured.
1057 optionDates[i] = iborCalendar.adjust(optionDates[i]);
1058 }
1059 }
1060
1061 DLOG("Option [tenor, date] pair is [" << optionTenors[i] << ", "
1062 << io::iso_date(optionDates[i]) << "]");
1063
1064 // If ATM, use initial market's discount curve and ibor index to calculate ATM rate
1065 if (isAtm) {
1066 QL_REQUIRE(iborIndex != nullptr,
1067 "SSM: Expected ibor index for key "
1068 << name << " from the key or a curve config for a ccy");
1069 auto t0_iborIndex = *initMarket->iborIndex(
1070 IndexNameTranslator::instance().oreName(iborIndex->name()), configuration);
1071 if (parameters_->capFloorVolUseCapAtm()) {
1072 QL_REQUIRE(!isOis, "SSM: capFloorVolUseCapATM not supported for OIS indices ("
1073 << t0_iborIndex->name() << ")");
1074 QuantLib::ext::shared_ptr<CapFloor> cap =
1075 MakeCapFloor(CapFloor::Cap, optionTenors[i], t0_iborIndex, 0.0, 0 * Days);
1076 atmStrike = cap->atmRate(**initMarket->discountCurve(name, configuration));
1077 } else {
1078 if (isOis) {
1079 Leg capFloor =
1080 MakeOISCapFloor(CapFloor::Cap, optionTenors[i],
1081 QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(t0_iborIndex),
1082 rateComputationPeriod, 0.0)
1084 .withSettlementDays(onSettlementDays);
1085 if (capFloor.empty()) {
1086 atmStrike = t0_iborIndex->fixing(optionDates[i]);
1087 } else {
1088 auto lastCoupon =
1089 QuantLib::ext::dynamic_pointer_cast<CappedFlooredOvernightIndexedCoupon>(
1090 capFloor.back());
1091 QL_REQUIRE(lastCoupon, "SSM internal error, could not cast to "
1092 "CappedFlooredOvernightIndexedCoupon "
1093 "when building optionlet vol for '"
1094 << name << "', index=" << t0_iborIndex->name());
1095 atmStrike = lastCoupon->underlying()->rate();
1096 }
1097 } else {
1098 atmStrike = t0_iborIndex->fixing(optionDates[i]);
1099 }
1100 }
1101 }
1102
1103 for (Size j = 0; j < strikes.size(); ++j) {
1104 Real strike = isAtm ? atmStrike : strikes[j];
1105 Real vol =
1106 wrapper->volatility(optionDates[i], strike, true);
1107 DLOG("Vol at [date, strike] pair [" << optionDates[i] << ", " << std::fixed
1108 << std::setprecision(4) << strike << "] is "
1109 << std::setprecision(12) << vol);
1110 QuantLib::ext::shared_ptr<SimpleQuote> q =
1111 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : vol);
1112 Size index = i * strikes.size() + j;
1113 simDataTmp.emplace(std::piecewise_construct,
1114 std::forward_as_tuple(param.first, name, index),
1115 std::forward_as_tuple(q));
1117 absoluteSimDataTmp.emplace(std::piecewise_construct,
1118 std::forward_as_tuple(param.first, name, index),
1119 std::forward_as_tuple(vol));
1120 }
1121 quotes[i][j] = Handle<Quote>(q);
1122 }
1123 }
1124
1125 std::vector<std::vector<Real>> coordinates(2);
1126 for(Size i=0;i<optionTenors.size();++i) {
1127 coordinates[0].push_back(
1128 wrapper->timeFromReference(wrapper->optionDateFromTenor(optionTenors[i])));
1129 }
1130 for(Size j=0;j<strikes.size();++j) {
1131 coordinates[1].push_back(isAtm ? atmStrike : strikes[j]);
1132 }
1133
1134 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, coordinates);
1135 simDataWritten = true;
1136
1137 DayCounter dc = wrapper->dayCounter();
1138
1140 hCapletVol = Handle<OptionletVolatilityStructure>(
1141 QuantLib::ext::make_shared<QuantExt::SpreadedOptionletVolatility2>(wrapper, optionDates,
1142 strikes, quotes));
1143 } else {
1144 // FIXME: Works as of today only, i.e. for sensitivity/scenario analysis.
1145 // TODO: Build floating reference date StrippedOptionlet class for MC path generators
1146 QuantLib::ext::shared_ptr<StrippedOptionlet> optionlet = QuantLib::ext::make_shared<StrippedOptionlet>(
1147 settleDays, wrapper->calendar(), wrapper->businessDayConvention(), iborIndex,
1148 optionDates, strikes, quotes, dc, wrapper->volatilityType(),
1149 wrapper->displacement());
1150
1151 hCapletVol = Handle<OptionletVolatilityStructure>(
1153 optionlet));
1154 }
1155 } else {
1156 string decayModeString = parameters->capFloorVolDecayMode();
1157 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
1158
1159 QL_REQUIRE(!QuantLib::ext::dynamic_pointer_cast<ProxyOptionletVolatility>(*wrapper),
1160 "DynamicOptionletVolatilityStructure does not support ProxyOptionletVolatility surface.");
1161
1162 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> capletVol =
1163 QuantLib::ext::make_shared<DynamicOptionletVolatilityStructure>(*wrapper, 0, NullCalendar(),
1164 decayMode);
1165 hCapletVol = Handle<OptionletVolatilityStructure>(capletVol);
1166 }
1167 hCapletVol->setAdjustReferenceDate(false);
1168 hCapletVol->enableExtrapolation();
1169 capFloorCurves_.emplace(std::piecewise_construct,
1170 std::forward_as_tuple(Market::defaultConfiguration, name),
1171 std::forward_as_tuple(hCapletVol));
1172 capFloorIndexBase_.emplace(
1173 std::piecewise_construct, std::forward_as_tuple(Market::defaultConfiguration, name),
1174 std::forward_as_tuple(std::make_pair(iborIndexName, rateComputationPeriod)));
1175
1176 LOG("Simulation market cap/floor volatility type = " << hCapletVol->volatilityType());
1177 } catch (const std::exception& e) {
1178 processException(continueOnError, e, name, param.first, simDataWritten);
1179 }
1180 }
1181 break;
1182
1184 for (const auto& name : param.second.second) {
1185 bool simDataWritten = false;
1186 try {
1187 LOG("building " << name << " default curve..");
1188 auto wrapper = initMarket->defaultCurve(name, configuration);
1189 vector<Handle<Quote>> quotes;
1190
1191 QL_REQUIRE(parameters->defaultTenors(name).front() > 0 * Days,
1192 "default curve tenors must not include t=0");
1193
1194 vector<Date> dates(1, asof_);
1195 vector<Real> times(1, 0.0);
1196
1197 DayCounter dc = wrapper->curve()->dayCounter();
1198
1199 for (Size i = 0; i < parameters->defaultTenors(name).size(); i++) {
1200 dates.push_back(asof_ + parameters->defaultTenors(name)[i]);
1201 times.push_back(dc.yearFraction(asof_, dates.back()));
1202 }
1203
1204 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(1.0));
1205 quotes.push_back(Handle<Quote>(q));
1206 for (Size i = 0; i < dates.size() - 1; i++) {
1207 Probability prob = wrapper->curve()->survivalProbability(dates[i + 1], true);
1208 QuantLib::ext::shared_ptr<SimpleQuote> q =
1209 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 1.0 : prob);
1210 // Check if the risk factor is simulated before adding it
1211 if (param.second.first) {
1212 simDataTmp.emplace(std::piecewise_construct,
1213 std::forward_as_tuple(param.first, name, i),
1214 std::forward_as_tuple(q));
1215 DLOG("ScenarioSimMarket default curve " << name << " survival[" << i << "]=" << prob);
1217 absoluteSimDataTmp.emplace(std::piecewise_construct,
1218 std::forward_as_tuple(param.first, name, i),
1219 std::forward_as_tuple(prob));
1220 }
1221 }
1222 Handle<Quote> qh(q);
1223 quotes.push_back(qh);
1224 }
1225 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name,
1226 {std::vector<Real>(std::next(times.begin(), 1), times.end())});
1227 simDataWritten = true;
1228 Calendar cal = ore::data::parseCalendar(parameters->defaultCurveCalendar(name));
1229 Handle<DefaultProbabilityTermStructure> defaultCurve;
1231 defaultCurve = Handle<DefaultProbabilityTermStructure>(
1232 QuantLib::ext::make_shared<QuantExt::SpreadedSurvivalProbabilityTermStructure>(
1233 wrapper->curve(), times, quotes,
1234 parameters->defaultCurveExtrapolation() == "FlatZero"
1235 ? QuantExt::SpreadedSurvivalProbabilityTermStructure::Extrapolation::flatZero
1236 : QuantExt::SpreadedSurvivalProbabilityTermStructure::Extrapolation::flatFwd));
1237 } else {
1238 defaultCurve = Handle<DefaultProbabilityTermStructure>(
1239 QuantLib::ext::make_shared<QuantExt::SurvivalProbabilityCurve<LogLinear>>(
1240 dates, quotes, dc, cal, std::vector<Handle<Quote>>(), std::vector<Date>(),
1241 LogLinear(),
1242 parameters->defaultCurveExtrapolation() == "FlatZero"
1245 }
1246 defaultCurve->setAdjustReferenceDate(false);
1247 defaultCurve->enableExtrapolation();
1248 defaultCurves_.insert(make_pair(
1250 Handle<CreditCurve>(QuantLib::ext::make_shared<CreditCurve>(
1251 defaultCurve, wrapper->rateCurve(), wrapper->recovery(), wrapper->refData()))));
1252 } catch (const std::exception& e) {
1253 processException(continueOnError, e, name, param.first, simDataWritten);
1254 }
1255 }
1256 break;
1257
1259 for (const auto& name : param.second.second) {
1260 bool simDataWritten = false;
1261 try {
1262 DLOG("Adding security recovery rate " << name << " from configuration " << configuration);
1263 Real v = initMarket->recoveryRate(name, configuration)->value();
1264 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 1.0 : v);
1266 auto m = [v](Real x) { return x * v; };
1267 recoveryRates_.insert(
1268 make_pair(make_pair(Market::defaultConfiguration, name),
1269 Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(
1270 Handle<Quote>(q), m))));
1271 } else {
1272 recoveryRates_.insert(
1273 make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
1274 }
1275 // Check if the risk factor is simulated before adding it
1276 if (param.second.first) {
1277 simDataTmp.emplace(std::piecewise_construct,
1278 std::forward_as_tuple(RiskFactorKey::KeyType::RecoveryRate, name),
1279 std::forward_as_tuple(q));
1281 absoluteSimDataTmp.emplace(
1282 std::piecewise_construct,
1283 std::forward_as_tuple(RiskFactorKey::KeyType::RecoveryRate, name),
1284 std::forward_as_tuple(v));
1285 }
1286 }
1287 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
1288 simDataWritten = true;
1289 recoveryRates_.insert(
1290 make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
1291 } catch (const std::exception& e) {
1292 processException(continueOnError, e, name, param.first, simDataWritten);
1293 }
1294 }
1295 break;
1296
1298 for (const auto& name : param.second.second) {
1299 bool simDataWritten = false;
1300 try {
1301 LOG("building " << name << " cds vols..");
1302 Handle<QuantExt::CreditVolCurve> wrapper = initMarket->cdsVol(name, configuration);
1303 Handle<QuantExt::CreditVolCurve> cvh;
1304 bool stickyStrike = parameters_->cdsVolSmileDynamics(name) == "StickyStrike";
1305 if (param.second.first) {
1306 LOG("Simulating CDS Vols for " << name);
1307 vector<Handle<Quote>> quotes;
1308 vector<Volatility> vols;
1309 vector<Time> times;
1310 vector<Date> expiryDates;
1311 DayCounter dc = wrapper->dayCounter();
1312 for (Size i = 0; i < parameters->cdsVolExpiries().size(); i++) {
1313 Date date = asof_ + parameters->cdsVolExpiries()[i];
1314 expiryDates.push_back(date);
1315 // hardcoded, single term 5y
1316 Volatility vol = wrapper->volatility(date, 5.0, Null<Real>(), wrapper->type());
1317 vols.push_back(vol);
1318 times.push_back(dc.yearFraction(asof_, date));
1319 QuantLib::ext::shared_ptr<SimpleQuote> q =
1320 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : vol);
1321 if (parameters->simulateCdsVols()) {
1322 simDataTmp.emplace(std::piecewise_construct,
1323 std::forward_as_tuple(param.first, name, i),
1324 std::forward_as_tuple(q));
1326 absoluteSimDataTmp.emplace(std::piecewise_construct,
1327 std::forward_as_tuple(param.first, name, i),
1328 std::forward_as_tuple(vol));
1329 }
1330 }
1331 quotes.emplace_back(q);
1332 }
1333 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {times});
1334 simDataWritten = true;
1336 (!useSpreadedTermStructures_ && parameters->simulateCdsVolATMOnly())) {
1337 std::vector<Handle<Quote>> spreads;
1338 if (parameters_->simulateCdsVolATMOnly()) {
1339 for (size_t i = 0; i < quotes.size(); i++) {
1340 Handle<Quote> atmVol(QuantLib::ext::make_shared<SimpleQuote>(quotes[i]->value()));
1341 Handle<Quote> quote(QuantLib::ext::make_shared <
1342 CompositeQuote<std::minus<double>>>(quotes[i], atmVol,
1343 std::minus<double>()));
1344 spreads.push_back(quote);
1345 }
1346 } else {
1347 spreads = quotes;
1348 }
1349 std::vector<QuantLib::Period> simTerms;
1350 std::vector<Handle<CreditCurve>> simTermCurves;
1351 if (curveConfigs.hasCdsVolCurveConfig(name)) {
1352 // get the term curves from the curve config if possible
1353 auto cc = curveConfigs.cdsVolCurveConfig(name);
1354 simTerms = cc->terms();
1355 for (auto const& c : cc->termCurves())
1356 simTermCurves.push_back(defaultCurve(parseCurveSpec(c)->curveConfigID()));
1357 } else {
1358 // assume the default curve names follow the naming convention volName_5Y
1359 simTerms = wrapper->terms();
1360 for (auto const& t : simTerms) {
1361 simTermCurves.push_back(defaultCurve(name + "_" + ore::data::to_string(t)));
1362 }
1363 }
1364 cvh = Handle<CreditVolCurve>(QuantLib::ext::make_shared<SpreadedCreditVolCurve>(
1365 wrapper, expiryDates, spreads, !stickyStrike, simTerms, simTermCurves));
1366 } else {
1367 // TODO support strike and term dependence
1368 cvh = Handle<CreditVolCurve>(QuantLib::ext::make_shared<CreditVolCurveWrapper>(
1369 Handle<BlackVolTermStructure>(QuantLib::ext::make_shared<BlackVarianceCurve3>(
1370 0, NullCalendar(), wrapper->businessDayConvention(), dc, times, quotes,
1371 false))));
1372 }
1373 } else {
1374 string decayModeString = parameters->cdsVolDecayMode();
1375 LOG("Deterministic CDS Vols with decay mode " << decayModeString << " for " << name);
1376 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
1377
1378 // TODO support strike and term dependence, hardcoded term 5y
1379 cvh = Handle<CreditVolCurve>(
1380 QuantLib::ext::make_shared<CreditVolCurveWrapper>(Handle<BlackVolTermStructure>(
1381 QuantLib::ext::make_shared<QuantExt::DynamicBlackVolTermStructure<tag::curve>>(
1382 Handle<BlackVolTermStructure>(
1383 QuantLib::ext::make_shared<BlackVolFromCreditVolWrapper>(wrapper, 5.0)),
1384 0, NullCalendar(), decayMode,
1385 stickyStrike ? StickyStrike : StickyLogMoneyness))));
1386 }
1387 cvh->setAdjustReferenceDate(false);
1388 if (wrapper->allowsExtrapolation())
1389 cvh->enableExtrapolation();
1390 cdsVols_.insert(make_pair(make_pair(Market::defaultConfiguration, name), cvh));
1391 } catch (const std::exception& e) {
1392 processException(continueOnError, e, name, param.first, simDataWritten);
1393 }
1394 }
1395 break;
1396
1398 for (const auto& name : param.second.second) {
1399 bool simDataWritten = false;
1400 try {
1401 Handle<BlackVolTermStructure> wrapper = initMarket->fxVol(name, configuration);
1402 Handle<Quote> spot = fxSpot(name);
1403 QL_REQUIRE(name.length() == 6, "invalid ccy pair length");
1404 string forCcy = name.substr(0, 3);
1405 string domCcy = name.substr(3, 3);
1406
1407 // Get the yield curve IDs from the FX volatility configuration
1408 // They may still be empty
1409 string foreignTsId;
1410 string domesticTsId;
1411 if (curveConfigs.hasFxVolCurveConfig(name)) {
1412 auto fxVolConfig = curveConfigs.fxVolCurveConfig(name);
1413 foreignTsId = fxVolConfig->fxForeignYieldCurveID();
1414 TLOG("Got foreign term structure '" << foreignTsId
1415 << "' from FX volatility curve config for " << name);
1416 domesticTsId = fxVolConfig->fxDomesticYieldCurveID();
1417 TLOG("Got domestic term structure '" << domesticTsId
1418 << "' from FX volatility curve config for " << name);
1419 }
1420 Handle<BlackVolTermStructure> fvh;
1421
1422 bool stickyStrike = parameters_->fxVolSmileDynamics(name) == "StickyStrike";
1423
1424 if (param.second.first) {
1425 LOG("Simulating FX Vols for " << name);
1426 auto& expiries = parameters->fxVolExpiries(name);
1427 Size m = expiries.size();
1428 Calendar cal = wrapper->calendar();
1429 if (cal.empty()) {
1430 cal = NullCalendar();
1431 }
1432 DayCounter dc = wrapper->dayCounter();
1433 vector<vector<Handle<Quote>>> quotes;
1434 vector<Time> times(m);
1435 vector<Date> dates(m);
1436
1437 // Attempt to get the relevant yield curves from the initial market
1438 Handle<YieldTermStructure> initForTS =
1439 getYieldCurve(foreignTsId, todaysMarketParams, configuration, initMarket);
1440 TLOG("Foreign term structure '" << foreignTsId << "' from t_0 market is "
1441 << (initForTS.empty() ? "empty" : "not empty"));
1442 Handle<YieldTermStructure> initDomTS =
1443 getYieldCurve(domesticTsId, todaysMarketParams, configuration, initMarket);
1444 TLOG("Domestic term structure '" << domesticTsId << "' from t_0 market is "
1445 << (initDomTS.empty() ? "empty" : "not empty"));
1446
1447 // fall back on discount curves
1448 if (initForTS.empty() || initDomTS.empty()) {
1449 TLOG("Falling back on the discount curves for " << forCcy << " and " << domCcy
1450 << " from t_0 market");
1451 initForTS = initMarket->discountCurve(forCcy, configuration);
1452 initDomTS = initMarket->discountCurve(domCcy, configuration);
1453 }
1454
1455 // Attempt to get the relevant yield curves from this scenario simulation market
1456 Handle<YieldTermStructure> forTS =
1457 getYieldCurve(foreignTsId, todaysMarketParams, Market::defaultConfiguration);
1458 TLOG("Foreign term structure '" << foreignTsId << "' from sim market is "
1459 << (forTS.empty() ? "empty" : "not empty"));
1460 Handle<YieldTermStructure> domTS =
1461 getYieldCurve(domesticTsId, todaysMarketParams, Market::defaultConfiguration);
1462 TLOG("Domestic term structure '" << domesticTsId << "' from sim market is "
1463 << (domTS.empty() ? "empty" : "not empty"));
1464
1465 // fall back on discount curves
1466 if (forTS.empty() || domTS.empty()) {
1467 TLOG("Falling back on the discount curves for " << forCcy << " and " << domCcy
1468 << " from sim market");
1469 forTS = discountCurve(forCcy);
1470 domTS = discountCurve(domCcy);
1471 }
1472
1473 for (Size k = 0; k < m; k++) {
1474 dates[k] = asof_ + expiries[k];
1475 times[k] = wrapper->timeFromReference(dates[k]);
1476 }
1477
1478 QuantLib::ext::shared_ptr<BlackVolTermStructure> fxVolCurve;
1479 if (parameters->fxVolIsSurface(name)) {
1480 vector<Real> strikes;
1481 strikes = parameters->fxUseMoneyness(name) ? parameters->fxVolMoneyness(name)
1482 : parameters->fxVolStdDevs(name);
1483 Size n = strikes.size();
1484 quotes.resize(n, vector<Handle<Quote>>(m, Handle<Quote>()));
1485
1486 // hardcode this for now
1487 bool flatExtrapolation = true;
1488
1489 // get vol matrix to feed to surface
1490 if (parameters->fxUseMoneyness(name)) { // if moneyness
1491 for (Size j = 0; j < m; j++) {
1492 for (Size i = 0; i < n; i++) {
1493 Real mon = strikes[i];
1494 // strike (assuming forward prices)
1495 Real k = spot->value() * mon * initForTS->discount(dates[j]) /
1496 initDomTS->discount(dates[j]);
1497 Size idx = i * m + j;
1498
1499 Volatility vol = wrapper->blackVol(dates[j], k, true);
1500 QuantLib::ext::shared_ptr<SimpleQuote> q(
1501 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
1502 simDataTmp.emplace(std::piecewise_construct,
1503 std::forward_as_tuple(param.first, name, idx),
1504 std::forward_as_tuple(q));
1506 absoluteSimDataTmp.emplace(
1507 std::piecewise_construct,
1508 std::forward_as_tuple(param.first, name, idx),
1509 std::forward_as_tuple(q->value()));
1510 }
1511 quotes[i][j] = Handle<Quote>(q);
1512 }
1513 }
1514 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {strikes, times});
1515 simDataWritten = true;
1516 // build the surface
1518 fxVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilitySurfaceMoneynessForward>(
1519 Handle<BlackVolTermStructure>(wrapper), spot, times,
1520 parameters->fxVolMoneyness(name), quotes,
1521 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot->value())), initForTS,
1522 initDomTS, forTS, domTS, stickyStrike);
1523 } else {
1524 fxVolCurve = QuantLib::ext::make_shared<BlackVarianceSurfaceMoneynessForward>(
1525 cal, spot, times, parameters->fxVolMoneyness(name), quotes, dc, forTS,
1526 domTS, stickyStrike, flatExtrapolation);
1527 }
1528 } else { // if stdDevPoints
1529 // forwards
1530 vector<Real> fwds;
1531 vector<Real> atmVols;
1532 for (Size i = 0; i < m; i++) {
1533 Real k = spot->value() * initForTS->discount(dates[i]) / initDomTS->discount(dates[i]);
1534 fwds.push_back(k);
1535 atmVols.push_back(wrapper->blackVol(dates[i], k));
1536 DLOG("on date " << dates[i] << ": fwd = " << fwds.back()
1537 << ", atmVol = " << atmVols.back());
1538 }
1539
1540 // interpolations
1541 Interpolation forwardCurve =
1542 Linear().interpolate(times.begin(), times.end(), fwds.begin());
1543 Interpolation atmVolCurve =
1544 Linear().interpolate(times.begin(), times.end(), atmVols.begin());
1545
1546 // populate quotes
1547 vector<vector<Handle<Quote>>> absQuotes(n,
1548 vector<Handle<Quote>>(m, Handle<Quote>()));
1549 BlackVarianceSurfaceStdDevs::populateVolMatrix(wrapper, absQuotes, times,
1550 parameters->fxVolStdDevs(name),
1551 forwardCurve, atmVolCurve);
1553 for (Size i = 0; i < n; ++i)
1554 for (Size j = 0; j < m; ++j)
1555 quotes[i][j] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0));
1556 } else {
1557 quotes = absQuotes;
1558 }
1559
1560 // sort out simDataTemp
1561 for (Size i = 0; i < m; i++) {
1562 for (Size j = 0; j < n; j++) {
1563 Size idx = j * m + i;
1564 QuantLib::ext::shared_ptr<Quote> q = quotes[j][i].currentLink();
1565 QuantLib::ext::shared_ptr<SimpleQuote> sq =
1566 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(q);
1567 simDataTmp.emplace(std::piecewise_construct,
1568 std::forward_as_tuple(param.first, name, idx),
1569 std::forward_as_tuple(sq));
1571 absoluteSimDataTmp.emplace(
1572 std::piecewise_construct,
1573 std::forward_as_tuple(param.first, name, idx),
1574 std::forward_as_tuple(absQuotes[j][i]->value()));
1575 }
1576 }
1577 }
1578 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {strikes, times});
1579 simDataWritten = true;
1580
1581 // set up a FX Index
1582 Handle<FxIndex> fxInd = fxIndex(name);
1583
1584 if (parameters->fxUseMoneyness(name)) { // moneyness
1585 } else { // standard deviations
1587 fxVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilitySurfaceStdDevs>(
1588 Handle<BlackVolTermStructure>(wrapper), spot, times,
1589 parameters->fxVolStdDevs(name), quotes,
1590 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot->value())),
1591 initForTS, initDomTS, forTS, domTS, stickyStrike);
1592 } else {
1593 fxVolCurve = QuantLib::ext::make_shared<BlackVarianceSurfaceStdDevs>(
1594 cal, spot, times, parameters->fxVolStdDevs(name), quotes, dc,
1595 fxInd.currentLink(), stickyStrike, flatExtrapolation);
1596 }
1597 }
1598 }
1599 } else { // not a surface - case for ATM or simulateATMOnly
1600 quotes.resize(1, vector<Handle<Quote>>(m, Handle<Quote>()));
1601 // Only need ATM quotes in this case
1602 for (Size j = 0; j < m; j++) {
1603 // Index is expires then moneyness.
1604 Size idx = j;
1605 Real f =
1606 spot->value() * initForTS->discount(dates[j]) / initDomTS->discount(dates[j]);
1607 Volatility vol = wrapper->blackVol(dates[j], f);
1608 QuantLib::ext::shared_ptr<SimpleQuote> q(
1609 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
1610 simDataTmp.emplace(std::piecewise_construct,
1611 std::forward_as_tuple(param.first, name, idx),
1612 std::forward_as_tuple(q));
1614 absoluteSimDataTmp.emplace(std::piecewise_construct,
1615 std::forward_as_tuple(param.first, name, idx),
1616 std::forward_as_tuple(vol));
1617 }
1618 quotes[0][j] = Handle<Quote>(q);
1619 }
1620
1621 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {times});
1622 simDataWritten = true;
1623
1625 // if simulate atm only is false, we use the ATM slice from the wrapper only
1626 // the smile dynamics is sticky strike here always (if t0 is a surface)
1627 fxVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilityCurve>(
1628 Handle<BlackVolTermStructure>(wrapper), times, quotes[0],
1629 !parameters->simulateFxVolATMOnly());
1630 } else {
1631 LOG("ATM FX Vols (BlackVarianceCurve3) for " << name);
1632 QuantLib::ext::shared_ptr<BlackVolTermStructure> atmCurve;
1633 atmCurve = QuantLib::ext::make_shared<BlackVarianceCurve3>(
1634 0, NullCalendar(), wrapper->businessDayConvention(), dc, times, quotes[0], false);
1635 // if we have a surface but are only simulating atm vols we wrap the atm curve and
1636 // the full t0 surface
1637 if (parameters->simulateFxVolATMOnly()) {
1638 LOG("Simulating FX Vols (FXVolatilityConstantSpread) for " << name);
1639 fxVolCurve = QuantLib::ext::make_shared<BlackVolatilityConstantSpread>(
1640 Handle<BlackVolTermStructure>(atmCurve), wrapper);
1641 } else {
1642 fxVolCurve = atmCurve;
1643 }
1644 }
1645 }
1646 fvh = Handle<BlackVolTermStructure>(fxVolCurve);
1647
1648 } else {
1649 string decayModeString = parameters->fxVolDecayMode();
1650 LOG("Deterministic FX Vols with decay mode " << decayModeString << " for " << name);
1651 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
1652
1653 // currently only curves (i.e. strike independent) FX volatility structures are
1654 // supported, so we use a) the more efficient curve tag and b) a hard coded sticky
1655 // strike stickiness, since then no yield term structures and no fx spot are required
1656 // that define the ATM level - to be revisited when FX surfaces are supported
1657 fvh = Handle<BlackVolTermStructure>(
1658 QuantLib::ext::make_shared<QuantExt::DynamicBlackVolTermStructure<tag::curve>>(
1659 wrapper, 0, NullCalendar(), decayMode,
1660 stickyStrike ? StickyStrike : StickyLogMoneyness));
1661 }
1662
1663 fvh->setAdjustReferenceDate(false);
1664 fvh->enableExtrapolation();
1665 fxVols_.insert(make_pair(make_pair(Market::defaultConfiguration, name), fvh));
1666
1667 // build inverted surface
1668 QL_REQUIRE(name.size() == 6, "Invalid Ccy pair " << name);
1669 string reverse = name.substr(3) + name.substr(0, 3);
1670 Handle<QuantLib::BlackVolTermStructure> ifvh(
1671 QuantLib::ext::make_shared<BlackInvertedVolTermStructure>(fvh));
1672 ifvh->enableExtrapolation();
1673 fxVols_.insert(make_pair(make_pair(Market::defaultConfiguration, reverse), ifvh));
1674 } catch (const std::exception& e) {
1675 processException(continueOnError, e, name, param.first, simDataWritten);
1676 }
1677 }
1678 break;
1679
1681 for (const auto& name : param.second.second) {
1682 bool simDataWritten = false;
1683 try {
1684 Handle<BlackVolTermStructure> wrapper = initMarket->equityVol(name, configuration);
1685 Handle<BlackVolTermStructure> evh;
1686
1687 bool stickyStrike = parameters_->equityVolSmileDynamics(name) == "StickyStrike";
1688 if (param.second.first) {
1690 Handle<Quote> spot = eqCurve->equitySpot();
1691 auto expiries = parameters->equityVolExpiries(name);
1692
1693 Size m = expiries.size();
1694 vector<vector<Handle<Quote>>> quotes;
1695 vector<Time> times(m);
1696 vector<Date> dates(m);
1697 Calendar cal;
1698 if (curveConfigs.hasEquityVolCurveConfig(name)) {
1699 auto cfg = curveConfigs.equityVolCurveConfig(name);
1700 if (cfg->calendar().empty())
1701 cal = parseCalendar(cfg->ccy());
1702 else
1703 cal = parseCalendar(cfg->calendar());
1704 }
1705 if (cal.empty() || cal == NullCalendar()) {
1706 // take the equity curves calendar - this at least ensures fixings align
1707 cal = eqCurve->fixingCalendar();
1708 }
1709 DayCounter dc = wrapper->dayCounter();
1710
1711 for (Size k = 0; k < m; k++) {
1712 dates[k] = cal.advance(asof_, expiries[k]);
1713 times[k] = dc.yearFraction(asof_, dates[k]);
1714 }
1715
1716 QuantLib::ext::shared_ptr<BlackVolTermStructure> eqVolCurve;
1717
1718 if (parameters->equityVolIsSurface(name)) {
1719 vector<Real> strikes;
1720 strikes = parameters->equityUseMoneyness(name)
1721 ? parameters->equityVolMoneyness(name)
1722 : parameters->equityVolStandardDevs(name);
1723 Size n = strikes.size();
1724 quotes.resize(n, vector<Handle<Quote>>(m, Handle<Quote>()));
1725
1726 if (parameters->equityUseMoneyness(name)) { // moneyness surface
1727 for (Size j = 0; j < m; j++) {
1728 for (Size i = 0; i < n; i++) {
1729 Real mon = strikes[i];
1730 // strike (assuming forward prices)
1731 Real k = eqCurve->forecastFixing(dates[j]) * mon;
1732 Size idx = i * m + j;
1733 Volatility vol = wrapper->blackVol(dates[j], k);
1734 QuantLib::ext::shared_ptr<SimpleQuote> q(
1735 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
1736 simDataTmp.emplace(std::piecewise_construct,
1737 std::forward_as_tuple(param.first, name, idx),
1738 std::forward_as_tuple(q));
1740 absoluteSimDataTmp.emplace(
1741 std::piecewise_construct,
1742 std::forward_as_tuple(param.first, name, idx),
1743 std::forward_as_tuple(vol));
1744 }
1745 quotes[i][j] = Handle<Quote>(q);
1746 }
1747 }
1748 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {strikes, times});
1749 simDataWritten = true;
1750 LOG("Simulating EQ Vols (BlackVarianceSurfaceMoneyness) for " << name);
1751
1753 eqVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilitySurfaceMoneynessForward>(
1754 Handle<BlackVolTermStructure>(wrapper), spot, times,
1755 parameters->equityVolMoneyness(name), quotes,
1756 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot->value())),
1757 initMarket->equityCurve(name, configuration)->equityDividendCurve(),
1758 initMarket->equityCurve(name, configuration)->equityForecastCurve(),
1759 eqCurve->equityDividendCurve(), eqCurve->equityForecastCurve(),
1760 stickyStrike);
1761 } else {
1762 // FIXME should that be Forward, since we read the vols at fwd moneyness above?
1763 eqVolCurve = QuantLib::ext::make_shared<BlackVarianceSurfaceMoneynessSpot>(
1764 cal, spot, times, parameters->equityVolMoneyness(name), quotes, dc,
1765 stickyStrike);
1766 }
1767 eqVolCurve->enableExtrapolation();
1768
1769 } else { // standard deviations surface
1770 // forwards
1771 vector<Real> fwds;
1772 vector<Real> atmVols;
1773 for (Size i = 0; i < expiries.size(); i++) {
1774 auto eqForward = eqCurve->forecastFixing(dates[i]);
1775 fwds.push_back(eqForward);
1776 atmVols.push_back(wrapper->blackVol(dates[i], eqForward));
1777 DLOG("on date " << dates[i] << ": fwd = " << fwds.back()
1778 << ", atmVol = " << atmVols.back());
1779 }
1780
1781 // interpolations
1782 Interpolation forwardCurve =
1783 Linear().interpolate(times.begin(), times.end(), fwds.begin());
1784 Interpolation atmVolCurve =
1785 Linear().interpolate(times.begin(), times.end(), atmVols.begin());
1786
1787 // populate quotes
1788 vector<vector<Handle<Quote>>> absQuotes(n,
1789 vector<Handle<Quote>>(m, Handle<Quote>()));
1791 forwardCurve, atmVolCurve);
1793 for (Size i = 0; i < n; ++i)
1794 for (Size j = 0; j < m; ++j)
1795 quotes[i][j] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0));
1796 } else {
1797 quotes = absQuotes;
1798 }
1799
1800 // add to simDataTemp
1801 for (Size i = 0; i < m; i++) {
1802 for (Size j = 0; j < n; j++) {
1803 Size idx = j * m + i;
1804 QuantLib::ext::shared_ptr<Quote> q = quotes[j][i].currentLink();
1805 QuantLib::ext::shared_ptr<SimpleQuote> sq =
1806 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(q);
1807 QL_REQUIRE(sq, "Quote is not a SimpleQuote"); // why do we need this?
1808 simDataTmp.emplace(std::piecewise_construct,
1809 std::forward_as_tuple(param.first, name, idx),
1810 std::forward_as_tuple(sq));
1812 absoluteSimDataTmp.emplace(
1813 std::piecewise_construct,
1814 std::forward_as_tuple(param.first, name, idx),
1815 std::forward_as_tuple(absQuotes[j][i]->value()));
1816 }
1817 }
1818 }
1819 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {strikes, times});
1820 simDataWritten = true;
1821 bool flatExtrapolation = true; // flat extrapolation of strikes at far ends.
1823 eqVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilitySurfaceStdDevs>(
1824 Handle<BlackVolTermStructure>(wrapper), spot, times,
1825 parameters->equityVolStandardDevs(name), quotes,
1826 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot->value())),
1827 initMarket->equityCurve(name, configuration)->equityDividendCurve(),
1828 initMarket->equityCurve(name, configuration)->equityForecastCurve(),
1829 eqCurve->equityDividendCurve(), eqCurve->equityForecastCurve(),
1830 stickyStrike);
1831 } else {
1832 eqVolCurve = QuantLib::ext::make_shared<BlackVarianceSurfaceStdDevs>(
1833 cal, spot, times, parameters->equityVolStandardDevs(name), quotes, dc,
1834 eqCurve.currentLink(), stickyStrike, flatExtrapolation);
1835 }
1836 }
1837 } else { // not a surface - case for ATM or simulateATMOnly
1838 quotes.resize(1, vector<Handle<Quote>>(m, Handle<Quote>()));
1839 // Only need ATM quotes in this case
1840 for (Size j = 0; j < m; j++) {
1841 // Index is expires then moneyness. TODO: is this the best?
1842 Size idx = j;
1843 auto eqForward = eqCurve->fixing(dates[j]);
1844 Volatility vol = wrapper->blackVol(dates[j], eqForward);
1845 QuantLib::ext::shared_ptr<SimpleQuote> q(
1846 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
1847 simDataTmp.emplace(std::piecewise_construct,
1848 std::forward_as_tuple(param.first, name, idx),
1849 std::forward_as_tuple(q));
1851 absoluteSimDataTmp.emplace(std::piecewise_construct,
1852 std::forward_as_tuple(param.first, name, idx),
1853 std::forward_as_tuple(vol));
1854 }
1855 quotes[0][j] = Handle<Quote>(q);
1856 }
1857
1858 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {times});
1859 simDataWritten = true;
1860
1862 // if simulate atm only is false, we use the ATM slice from the wrapper only
1863 // the smile dynamics is sticky strike here always (if t0 is a surface)
1864 eqVolCurve = QuantLib::ext::make_shared<SpreadedBlackVolatilityCurve>(
1865 Handle<BlackVolTermStructure>(wrapper), times, quotes[0],
1866 !parameters->simulateEquityVolATMOnly());
1867 } else {
1868 LOG("ATM EQ Vols (BlackVarianceCurve3) for " << name);
1869 QuantLib::ext::shared_ptr<BlackVolTermStructure> atmCurve;
1870 atmCurve = QuantLib::ext::make_shared<BlackVarianceCurve3>(0, NullCalendar(),
1871 wrapper->businessDayConvention(),
1872 dc, times, quotes[0], false);
1873 // if we have a surface but are only simulating atm vols we wrap the atm curve and
1874 // the full t0 surface
1875 if (parameters->simulateEquityVolATMOnly()) {
1876 LOG("Simulating EQ Vols (EquityVolatilityConstantSpread) for " << name);
1877 eqVolCurve = QuantLib::ext::make_shared<BlackVolatilityConstantSpread>(
1878 Handle<BlackVolTermStructure>(atmCurve), wrapper);
1879 } else {
1880 eqVolCurve = atmCurve;
1881 }
1882 }
1883 }
1884 evh = Handle<BlackVolTermStructure>(eqVolCurve);
1885
1886 } else {
1887 string decayModeString = parameters->equityVolDecayMode();
1888 DLOG("Deterministic EQ Vols with decay mode " << decayModeString << " for " << name);
1889 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
1890
1891 // currently only curves (i.e. strike independent) EQ volatility structures are
1892 // supported, so we use a) the more efficient curve tag and b) a hard coded sticky
1893 // strike stickiness, since then no yield term structures and no EQ spot are required
1894 // that define the ATM level - to be revisited when EQ surfaces are supported
1895 evh = Handle<BlackVolTermStructure>(
1896 QuantLib::ext::make_shared<QuantExt::DynamicBlackVolTermStructure<tag::curve>>(
1897 wrapper, 0, NullCalendar(), decayMode,
1898 stickyStrike ? StickyStrike : StickyLogMoneyness));
1899 }
1900
1901 evh->setAdjustReferenceDate(false);
1902 if (wrapper->allowsExtrapolation())
1903 evh->enableExtrapolation();
1904 equityVols_.insert(make_pair(make_pair(Market::defaultConfiguration, name), evh));
1905 DLOG("EQ volatility curve built for " << name);
1906 } catch (const std::exception& e) {
1907 processException(continueOnError, e, name, param.first, simDataWritten);
1908 }
1909 }
1910 break;
1911
1913 for (const auto& name : param.second.second) {
1914 bool simDataWritten = false;
1915 try {
1916 Handle<QuantExt::BaseCorrelationTermStructure> wrapper =
1917 initMarket->baseCorrelation(name, configuration);
1918 if (!param.second.first)
1919 baseCorrelations_.insert(make_pair(make_pair(Market::defaultConfiguration, name), wrapper));
1920 else {
1921 std::vector<Real> times;
1922 Size nd = parameters->baseCorrelationDetachmentPoints().size();
1923 Size nt = parameters->baseCorrelationTerms().size();
1924 vector<vector<Handle<Quote>>> quotes(nd, vector<Handle<Quote>>(nt));
1925 vector<Period> terms(nt);
1926 vector<double> detachmentPoints(nd);
1927 for (Size i = 0; i < nd; ++i) {
1928 Real lossLevel = parameters->baseCorrelationDetachmentPoints()[i];
1929 detachmentPoints[i] = lossLevel;
1930 for (Size j = 0; j < nt; ++j) {
1931 Period term = parameters->baseCorrelationTerms()[j];
1932 if (i == 0)
1933 terms[j] = term;
1934 times.push_back(wrapper->timeFromReference(asof_ + term));
1935 Real bc = wrapper->correlation(asof_ + term, lossLevel, true); // extrapolate
1936 QuantLib::ext::shared_ptr<SimpleQuote> q =
1937 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : bc);
1938 simDataTmp.emplace(std::piecewise_construct,
1939 std::forward_as_tuple(param.first, name, i * nt + j),
1940 std::forward_as_tuple(q));
1942 absoluteSimDataTmp.emplace(std::piecewise_construct,
1943 std::forward_as_tuple(param.first, name, i * nt + j),
1944 std::forward_as_tuple(bc));
1945 }
1946 quotes[i][j] = Handle<Quote>(q);
1947 }
1948 }
1949
1951 simDataTmp, absoluteSimDataTmp, param.first, name,
1952 {parameters->baseCorrelationDetachmentPoints(), times});
1953 simDataWritten = true;
1954
1955 //
1956 if (nt == 1) {
1957 terms.push_back(terms[0] + 1 * terms[0].units()); // arbitrary, but larger than the first term
1958 for (Size i = 0; i < nd; ++i)
1959 quotes[i].push_back(quotes[i][0]);
1960 }
1961
1962 if (nd == 1) {
1963 quotes.push_back(vector<Handle<Quote>>(terms.size()));
1964 for (Size j = 0; j < terms.size(); ++j)
1965 quotes[1][j] = quotes[0][j];
1966
1967 if (detachmentPoints[0] < 1.0 && !QuantLib::close_enough(detachmentPoints[0], 1.0)) {
1968 detachmentPoints.push_back(1.0);
1969 } else {
1970 detachmentPoints.insert(detachmentPoints.begin(), 0.01); // arbitrary, but larger than then 0 and less than 1.0
1971 }
1972 }
1973
1974 QuantLib::ext::shared_ptr<QuantExt::BaseCorrelationTermStructure> bcp;
1976 bcp = QuantLib::ext::make_shared<QuantExt::SpreadedBaseCorrelationCurve>(
1977 wrapper, terms, detachmentPoints, quotes);
1978 bcp->enableExtrapolation(wrapper->allowsExtrapolation());
1979 } else {
1980 DayCounter dc = wrapper->dayCounter();
1981 bcp = QuantLib::ext::make_shared<InterpolatedBaseCorrelationTermStructure<Bilinear>>(
1982 wrapper->settlementDays(), wrapper->calendar(), wrapper->businessDayConvention(),
1983 terms, detachmentPoints, quotes, dc);
1984
1985 bcp->enableExtrapolation(wrapper->allowsExtrapolation());
1986 }
1987 bcp->setAdjustReferenceDate(false);
1988 Handle<QuantExt::BaseCorrelationTermStructure> bch(bcp);
1989 baseCorrelations_.insert(make_pair(make_pair(Market::defaultConfiguration, name), bch));
1990 }
1991 DLOG("Base correlations built for " << name);
1992 } catch (const std::exception& e) {
1993 processException(continueOnError, e, name, param.first, simDataWritten);
1994 }
1995 }
1996 break;
1997
1999 for (const auto& name : param.second.second) {
2000 bool simDataWritten = false;
2001 try {
2002 DLOG("adding " << name << " base CPI price");
2003 Handle<ZeroInflationIndex> zeroInflationIndex =
2004 initMarket->zeroInflationIndex(name, configuration);
2005 Period obsLag = zeroInflationIndex->zeroInflationTermStructure()->observationLag();
2006 Date fixingDate = zeroInflationIndex->zeroInflationTermStructure()->baseDate();
2007 Real baseCPI = zeroInflationIndex->fixing(fixingDate);
2008
2009 QuantLib::ext::shared_ptr<InflationIndex> inflationIndex =
2010 QuantLib::ext::dynamic_pointer_cast<InflationIndex>(*zeroInflationIndex);
2011
2012 auto q = QuantLib::ext::make_shared<SimpleQuote>(baseCPI);
2014 auto m = [baseCPI](Real x) { return x * baseCPI; };
2015 Handle<InflationIndexObserver> inflObserver(
2016 QuantLib::ext::make_shared<InflationIndexObserver>(
2017 inflationIndex,
2018 Handle<Quote>(
2019 QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(Handle<Quote>(q), m)),
2020 obsLag));
2021 baseCpis_.insert(make_pair(make_pair(Market::defaultConfiguration, name), inflObserver));
2022 } else {
2023 Handle<InflationIndexObserver> inflObserver(
2024 QuantLib::ext::make_shared<InflationIndexObserver>(inflationIndex, Handle<Quote>(q),
2025 obsLag));
2026 baseCpis_.insert(make_pair(make_pair(Market::defaultConfiguration, name), inflObserver));
2027 }
2028 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name),
2029 std::forward_as_tuple(q));
2031 absoluteSimDataTmp.emplace(std::piecewise_construct,
2032 std::forward_as_tuple(param.first, name),
2033 std::forward_as_tuple(baseCPI));
2034 }
2035 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
2036 simDataWritten = true;
2037 } catch (const std::exception& e) {
2038 processException(continueOnError, e, name, param.first, simDataWritten);
2039 }
2040 }
2041 break;
2042
2044 for (const auto& name : param.second.second) {
2045 bool simDataWritten = false;
2046 try {
2047 LOG("building " << name << " zero inflation curve");
2048
2049 Handle<ZeroInflationIndex> inflationIndex = initMarket->zeroInflationIndex(name, configuration);
2050 Handle<ZeroInflationTermStructure> inflationTs = inflationIndex->zeroInflationTermStructure();
2051 vector<string> keys(parameters->zeroInflationTenors(name).size());
2052
2053 Date date0 = asof_ - inflationTs->observationLag();
2054 DayCounter dc = inflationTs->dayCounter();
2055 vector<Date> quoteDates;
2056 vector<Time> zeroCurveTimes(
2057 1, -dc.yearFraction(inflationPeriod(date0, inflationTs->frequency()).first, asof_));
2058 vector<Handle<Quote>> quotes;
2059 QL_REQUIRE(parameters->zeroInflationTenors(name).front() > 0 * Days,
2060 "zero inflation tenors must not include t=0");
2061
2062 for (auto& tenor : parameters->zeroInflationTenors(name)) {
2063 Date inflDate = inflationPeriod(date0 + tenor, inflationTs->frequency()).first;
2064 zeroCurveTimes.push_back(dc.yearFraction(asof_, inflDate));
2065 quoteDates.push_back(asof_ + tenor);
2066 }
2067
2068 for (Size i = 1; i < zeroCurveTimes.size(); i++) {
2069 Real rate = inflationTs->zeroRate(quoteDates[i - 1]);
2070 if (inflationTs->hasSeasonality()) {
2071 Date fixingDate = quoteDates[i - 1] - inflationTs->observationLag();
2072 rate = inflationTs->seasonality()->deseasonalisedZeroRate(fixingDate,
2073 rate, *inflationTs.currentLink());
2074 }
2075 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : rate);
2076 if (i == 1) {
2077 // add the zero rate at first tenor to the T0 time, to ensure flat interpolation of T1
2078 // rate for time t T0 < t < T1
2079 quotes.push_back(Handle<Quote>(q));
2080 }
2081 quotes.push_back(Handle<Quote>(q));
2082 simDataTmp.emplace(std::piecewise_construct,
2083 std::forward_as_tuple(param.first, name, i - 1),
2084 std::forward_as_tuple(q));
2086 absoluteSimDataTmp.emplace(std::piecewise_construct,
2087 std::forward_as_tuple(param.first, name, i - 1),
2088 std::forward_as_tuple(rate));
2089 DLOG("ScenarioSimMarket zero inflation curve " << name << " zeroRate[" << i
2090 << "]=" << rate);
2091 }
2092
2093 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name,
2094 {std::vector<Real>(std::next(zeroCurveTimes.begin(), 1), zeroCurveTimes.end())});
2095 simDataWritten = true;
2096
2097 // FIXME: Settlement days set to zero - needed for floating term structure implementation
2098 QuantLib::ext::shared_ptr<ZeroInflationTermStructure> zeroCurve;
2100 zeroCurve =
2101 QuantLib::ext::make_shared<SpreadedZeroInflationCurve>(inflationTs, zeroCurveTimes, quotes);
2102 } else {
2103 zeroCurve = QuantLib::ext::make_shared<ZeroInflationCurveObserverMoving<Linear>>(
2104 0, inflationIndex->fixingCalendar(), dc, inflationTs->observationLag(),
2105 inflationTs->frequency(), false, zeroCurveTimes, quotes,
2106 inflationTs->seasonality());
2107 }
2108
2109 Handle<ZeroInflationTermStructure> its(zeroCurve);
2110 its->setAdjustReferenceDate(false);
2111 its->enableExtrapolation();
2112 QuantLib::ext::shared_ptr<ZeroInflationIndex> i =
2113 parseZeroInflationIndex(name, Handle<ZeroInflationTermStructure>(its));
2114 Handle<ZeroInflationIndex> zh(i);
2115 zeroInflationIndices_.insert(make_pair(make_pair(Market::defaultConfiguration, name), zh));
2116
2117 LOG("building " << name << " zero inflation curve done");
2118 } catch (const std::exception& e) {
2119 processException(continueOnError, e, name, param.first, simDataWritten);
2120 }
2121 }
2122 break;
2123
2125 for (const auto& name : param.second.second) {
2126 bool simDataWritten = false;
2127 try {
2128 LOG("building " << name << " zero inflation cap/floor volatility curve...");
2129 Handle<QuantLib::CPIVolatilitySurface> wrapper =
2130 initMarket->cpiInflationCapFloorVolatilitySurface(name, configuration);
2131 Handle<ZeroInflationIndex> zeroInflationIndex =
2132 initMarket->zeroInflationIndex(name, configuration);
2133 // LOG("Initial market zero inflation cap/floor volatility type = " <<
2134 // wrapper->volatilityType());
2135
2136 Handle<QuantLib::CPIVolatilitySurface> hCpiVol;
2137
2138 // Check if the risk factor is simulated before adding it
2139 if (param.second.first) {
2140 LOG("Simulating zero inflation cap/floor vols for index name " << name);
2141
2142 DayCounter dc = wrapper->dayCounter();
2143 vector<Period> optionTenors = parameters->zeroInflationCapFloorVolExpiries(name);
2144 vector<Date> optionDates(optionTenors.size());
2145 vector<Real> strikes = parameters->zeroInflationCapFloorVolStrikes(name);
2146 vector<vector<Handle<Quote>>> quotes(
2147 optionTenors.size(), vector<Handle<Quote>>(strikes.size(), Handle<Quote>()));
2148 for (Size i = 0; i < optionTenors.size(); ++i) {
2149 optionDates[i] = wrapper->optionDateFromTenor(optionTenors[i]);
2150 for (Size j = 0; j < strikes.size(); ++j) {
2151 Real vol =
2152 wrapper->volatility(optionTenors[i], strikes[j], wrapper->observationLag(),
2153 wrapper->allowsExtrapolation());
2154 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : vol);
2155 Size index = i * strikes.size() + j;
2156 simDataTmp.emplace(std::piecewise_construct,
2157 std::forward_as_tuple(param.first, name, index),
2158 std::forward_as_tuple(q));
2160 absoluteSimDataTmp.emplace(std::piecewise_construct,
2161 std::forward_as_tuple(param.first, name, index),
2162 std::forward_as_tuple(vol));
2163 }
2164 quotes[i][j] = Handle<Quote>(q);
2165 }
2166 }
2167
2168 std::vector<std::vector<Real>> coordinates(2);
2169 for (Size i = 0; i < optionTenors.size(); ++i) {
2170 coordinates[0].push_back(
2171 wrapper->timeFromReference(wrapper->optionDateFromTenor(optionTenors[i])));
2172 }
2173 for (Size j = 0; j < strikes.size(); ++j) {
2174 coordinates[1].push_back(strikes[j]);
2175 }
2176
2177 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, coordinates);
2178 simDataWritten = true;
2179
2180
2181
2183 auto surface = QuantLib::ext::dynamic_pointer_cast<QuantExt::CPIVolatilitySurface>(wrapper.currentLink());
2184 QL_REQUIRE(surface,
2185 "Internal error, todays market should build QuantExt::CPIVolatiltiySurface "
2186 "instead of QuantLib::CPIVolatilitySurface");
2187 hCpiVol = Handle<QuantLib::CPIVolatilitySurface>(
2188 QuantLib::ext::make_shared<SpreadedCPIVolatilitySurface>(
2189 Handle<QuantExt::CPIVolatilitySurface>(surface), optionDates, strikes, quotes));
2190 } else {
2191 auto surface =
2192 QuantLib::ext::dynamic_pointer_cast<QuantExt::CPIVolatilitySurface>(wrapper.currentLink());
2193 QL_REQUIRE(surface,
2194 "Internal error, todays market should build QuantExt::CPIVolatiltiySurface "
2195 "instead of QuantLib::CPIVolatilitySurface");
2196 hCpiVol = Handle<QuantLib::CPIVolatilitySurface>(
2197 QuantLib::ext::make_shared<InterpolatedCPIVolatilitySurface<Bilinear>>(
2198 optionTenors, strikes, quotes, zeroInflationIndex.currentLink(),
2199 wrapper->settlementDays(), wrapper->calendar(),
2200 wrapper->businessDayConvention(), wrapper->dayCounter(),
2201 wrapper->observationLag(), surface->capFloorStartDate(), Bilinear(),
2202 surface->volatilityType(), surface->displacement()));
2203 }
2204 } else {
2205 // string decayModeString = parameters->zeroInflationCapFloorVolDecayMode();
2206 // ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
2207 // QuantLib::ext::shared_ptr<CPIVolatilitySurface> cpiVol =
2208 // QuantLib::ext::make_shared<QuantExt::DynamicCPIVolatilitySurface>(*wrapper, decayMode);
2209 // hCpiVol = Handle<CPIVolatilitySurface>(cpiVol);#
2210 // FIXME
2211 hCpiVol = wrapper;
2212 }
2213
2214 hCpiVol->setAdjustReferenceDate(false);
2215 if (wrapper->allowsExtrapolation())
2216 hCpiVol->enableExtrapolation();
2218 std::piecewise_construct, std::forward_as_tuple(Market::defaultConfiguration, name),
2219 std::forward_as_tuple(hCpiVol));
2220
2221 } catch (const std::exception& e) {
2222 processException(continueOnError, e, name, param.first, simDataWritten);
2223 }
2224 }
2225 break;
2226
2228 for (const auto& name : param.second.second) {
2229 bool simDataWritten = false;
2230 try {
2231 Handle<YoYInflationIndex> yoyInflationIndex =
2232 initMarket->yoyInflationIndex(name, configuration);
2233 Handle<YoYInflationTermStructure> yoyInflationTs =
2234 yoyInflationIndex->yoyInflationTermStructure();
2235 vector<string> keys(parameters->yoyInflationTenors(name).size());
2236
2237 Date date0 = asof_ - yoyInflationTs->observationLag();
2238 DayCounter dc = yoyInflationTs->dayCounter();
2239 vector<Date> quoteDates;
2240 vector<Time> yoyCurveTimes(
2241 1, -dc.yearFraction(inflationPeriod(date0, yoyInflationTs->frequency()).first, asof_));
2242 vector<Handle<Quote>> quotes;
2243 QL_REQUIRE(parameters->yoyInflationTenors(name).front() > 0 * Days,
2244 "zero inflation tenors must not include t=0");
2245
2246 for (auto& tenor : parameters->yoyInflationTenors(name)) {
2247 Date inflDate = inflationPeriod(date0 + tenor, yoyInflationTs->frequency()).first;
2248 yoyCurveTimes.push_back(dc.yearFraction(asof_, inflDate));
2249 quoteDates.push_back(asof_ + tenor);
2250 }
2251
2252 for (Size i = 1; i < yoyCurveTimes.size(); i++) {
2253 Real rate = yoyInflationTs->yoyRate(quoteDates[i - 1]);
2254 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : rate);
2255 if (i == 1) {
2256 // add the zero rate at first tenor to the T0 time, to ensure flat interpolation of T1
2257 // rate for time t T0 < t < T1
2258 quotes.push_back(Handle<Quote>(q));
2259 }
2260 quotes.push_back(Handle<Quote>(q));
2261 simDataTmp.emplace(std::piecewise_construct,
2262 std::forward_as_tuple(param.first, name, i - 1),
2263 std::forward_as_tuple(q));
2265 absoluteSimDataTmp.emplace(std::piecewise_construct,
2266 std::forward_as_tuple(param.first, name, i - 1),
2267 std::forward_as_tuple(rate));
2268 DLOG("ScenarioSimMarket yoy inflation curve " << name << " yoyRate[" << i << "]=" << rate);
2269 }
2270
2271 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name,
2272 {std::vector<Real>(std::next(yoyCurveTimes.begin(), 1), yoyCurveTimes.end())});
2273 simDataWritten = true;
2274
2275 QuantLib::ext::shared_ptr<YoYInflationTermStructure> yoyCurve;
2276 // Note this is *not* a floating term structure, it is only suitable for sensi runs
2277 // TODO: floating
2279 yoyCurve =
2280 QuantLib::ext::make_shared<SpreadedYoYInflationCurve>(yoyInflationTs, yoyCurveTimes, quotes);
2281 } else {
2282 yoyCurve = QuantLib::ext::make_shared<YoYInflationCurveObserverMoving<Linear>>(
2283 0, yoyInflationIndex->fixingCalendar(), dc, yoyInflationTs->observationLag(),
2284 yoyInflationTs->frequency(), yoyInflationIndex->interpolated(), yoyCurveTimes,
2285 quotes, yoyInflationTs->seasonality());
2286 }
2287 yoyCurve->setAdjustReferenceDate(false);
2288 Handle<YoYInflationTermStructure> its(yoyCurve);
2289 its->enableExtrapolation();
2290 QuantLib::ext::shared_ptr<YoYInflationIndex> i(yoyInflationIndex->clone(its));
2291 Handle<YoYInflationIndex> zh(i);
2292 yoyInflationIndices_.insert(make_pair(make_pair(Market::defaultConfiguration, name), zh));
2293 } catch (const std::exception& e) {
2294 processException(continueOnError, e, name, param.first, simDataWritten);
2295 }
2296 }
2297 break;
2298
2300 for (const auto& name : param.second.second) {
2301 bool simDataWritten = false;
2302 try {
2303 LOG("building " << name << " yoy inflation cap/floor volatility curve...");
2304 Handle<QuantExt::YoYOptionletVolatilitySurface> wrapper =
2305 initMarket->yoyCapFloorVol(name, configuration);
2306 LOG("Initial market "
2307 << name << " yoy inflation cap/floor volatility type = " << wrapper->volatilityType());
2308 Handle<QuantExt::YoYOptionletVolatilitySurface> hYoYCapletVol;
2309
2310 // Check if the risk factor is simulated before adding it
2311 if (param.second.first) {
2312 LOG("Simulating yoy inflation optionlet vols for index name " << name);
2313 vector<Period> optionTenors = parameters->yoyInflationCapFloorVolExpiries(name);
2314 vector<Date> optionDates(optionTenors.size());
2315 vector<Real> strikes = parameters->yoyInflationCapFloorVolStrikes(name);
2316 vector<vector<Handle<Quote>>> quotes(
2317 optionTenors.size(), vector<Handle<Quote>>(strikes.size(), Handle<Quote>()));
2318 for (Size i = 0; i < optionTenors.size(); ++i) {
2319 optionDates[i] = wrapper->optionDateFromTenor(optionTenors[i]);
2320 for (Size j = 0; j < strikes.size(); ++j) {
2321 Real vol =
2322 wrapper->volatility(optionTenors[i], strikes[j], wrapper->observationLag(),
2323 wrapper->allowsExtrapolation());
2324 QuantLib::ext::shared_ptr<SimpleQuote> q(
2325 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : vol));
2326 Size index = i * strikes.size() + j;
2327 simDataTmp.emplace(std::piecewise_construct,
2328 std::forward_as_tuple(param.first, name, index),
2329 std::forward_as_tuple(q));
2331 absoluteSimDataTmp.emplace(std::piecewise_construct,
2332 std::forward_as_tuple(param.first, name, index),
2333 std::forward_as_tuple(vol));
2334 }
2335 quotes[i][j] = Handle<Quote>(q);
2336 TLOG("ScenarioSimMarket yoy cf vol " << name << " tenor #" << i << " strike #" << j
2337 << " " << vol);
2338 }
2339 }
2340
2341 std::vector<std::vector<Real>> coordinates(2);
2342 for (Size i = 0; i < optionTenors.size(); ++i) {
2343 coordinates[0].push_back(
2344 wrapper->timeFromReference(wrapper->optionDateFromTenor(optionTenors[i])));
2345 }
2346 for (Size j = 0; j < strikes.size(); ++j) {
2347 coordinates[1].push_back(strikes[j]);
2348 }
2349
2350 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, coordinates);
2351 simDataWritten = true;
2352
2353 DayCounter dc = wrapper->dayCounter();
2354
2355 QuantLib::ext::shared_ptr<QuantExt::YoYOptionletVolatilitySurface> yoyoptionletvolsurface;
2357 yoyoptionletvolsurface = QuantLib::ext::make_shared<QuantExt::SpreadedYoYVolatilitySurface>(
2358 wrapper, optionDates, strikes, quotes);
2359 } else {
2360 yoyoptionletvolsurface = QuantLib::ext::make_shared<StrippedYoYInflationOptionletVol>(
2361 0, wrapper->calendar(), wrapper->businessDayConvention(), dc,
2362 wrapper->observationLag(), wrapper->frequency(), wrapper->indexIsInterpolated(),
2363 optionDates, strikes, quotes, wrapper->volatilityType(), wrapper->displacement());
2364 }
2365 hYoYCapletVol = Handle<QuantExt::YoYOptionletVolatilitySurface>(yoyoptionletvolsurface);
2366 } else {
2367 string decayModeString = parameters->yoyInflationCapFloorVolDecayMode();
2368 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
2369 QuantLib::ext::shared_ptr<QuantExt::DynamicYoYOptionletVolatilitySurface> yoyCapletVol =
2370 QuantLib::ext::make_shared<QuantExt::DynamicYoYOptionletVolatilitySurface>(*wrapper, decayMode);
2371 hYoYCapletVol = Handle<QuantExt::YoYOptionletVolatilitySurface>(yoyCapletVol);
2372 }
2373 hYoYCapletVol->setAdjustReferenceDate(false);
2374 if (wrapper->allowsExtrapolation())
2375 hYoYCapletVol->enableExtrapolation();
2376 yoyCapFloorVolSurfaces_.emplace(std::piecewise_construct,
2377 std::forward_as_tuple(Market::defaultConfiguration, name),
2378 std::forward_as_tuple(hYoYCapletVol));
2379 LOG("Simulation market yoy inflation cap/floor volatility type = "
2380 << hYoYCapletVol->volatilityType());
2381 } catch (const std::exception& e) {
2382 processException(continueOnError, e, name, param.first, simDataWritten);
2383 }
2384 }
2385 break;
2386
2388
2389 std::vector<std::string> curveNames;
2390 std::vector<std::string> basisCurves;
2391 for (const auto& name : param.second.second) {
2392 try {
2393 Handle<PriceTermStructure> initialCommodityCurve =
2394 initMarket->commodityPriceCurve(name, configuration);
2395 QuantLib::ext::shared_ptr<CommodityBasisPriceTermStructure> basisCurve =
2396 QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityBasisPriceTermStructure>(
2397 initialCommodityCurve.currentLink());
2398 if (basisCurve != nullptr) {
2399 basisCurves.push_back(name);
2400 } else {
2401 curveNames.push_back(name);
2402 }
2403 } catch (...) {
2404 curveNames.push_back(name);
2405 }
2406 }
2407 curveNames.insert(curveNames.end(), basisCurves.begin(), basisCurves.end());
2408
2409 for (const auto& name : curveNames) {
2410
2411 bool simDataWritten = false;
2412 try {
2413 LOG("building commodity curve for " << name);
2414
2415 // Time zero initial market commodity curve
2416 Handle<PriceTermStructure> initialCommodityCurve =
2417 initMarket->commodityPriceCurve(name, configuration);
2418
2419 bool allowsExtrapolation = initialCommodityCurve->allowsExtrapolation();
2420
2421 // Get the configured simulation tenors. Simulation tenors being empty at this point means
2422 // that we wish to use the pillar date points from the t_0 market PriceTermStructure.
2423 vector<Period> simulationTenors = parameters->commodityCurveTenors(name);
2424 DayCounter commodityCurveDayCounter = initialCommodityCurve->dayCounter();
2425 if (simulationTenors.empty()) {
2426 DLOG("simulation tenors are empty, use "
2427 << initialCommodityCurve->pillarDates().size()
2428 << " pillar dates from T0 curve to build ssm curve.");
2429 simulationTenors.reserve(initialCommodityCurve->pillarDates().size());
2430 for (const Date& d : initialCommodityCurve->pillarDates()) {
2431 QL_REQUIRE(d >= asof_, "Commodity curve pillar date (" << io::iso_date(d)
2432 << ") must be after as of ("
2433 << io::iso_date(asof_) << ").");
2434 simulationTenors.push_back(Period(d - asof_, Days));
2435 }
2436
2437 // It isn't great to be updating parameters here. However, actual tenors are requested
2438 // downstream from parameters and they need to be populated.
2439 parameters->setCommodityCurveTenors(name, simulationTenors);
2440 } else {
2441 DLOG("using " << simulationTenors.size() << " simulation tenors.");
2442 }
2443
2444 // Get prices at specified simulation times from time 0 market curve and place in quotes
2445 vector<Handle<Quote>> quotes(simulationTenors.size());
2446 vector<Real> times;
2447 for (Size i = 0; i < simulationTenors.size(); i++) {
2448 Date d = asof_ + simulationTenors[i];
2449 Real price = initialCommodityCurve->price(d, allowsExtrapolation);
2450 times.push_back(initialCommodityCurve->timeFromReference(d));
2451 TLOG("Commodity curve: price at " << io::iso_date(d) << " is " << price);
2452 // if we simulate the factors and use spreaded ts, the quote should be zero
2453 QuantLib::ext::shared_ptr<SimpleQuote> quote = QuantLib::ext::make_shared<SimpleQuote>(
2454 param.second.first && useSpreadedTermStructures_ ? 0.0 : price);
2455 quotes[i] = Handle<Quote>(quote);
2456
2457 // If we are simulating commodities, add the quote to simData_
2458 if (param.second.first) {
2459 simDataTmp.emplace(piecewise_construct, forward_as_tuple(param.first, name, i),
2460 forward_as_tuple(quote));
2462 absoluteSimDataTmp.emplace(piecewise_construct,
2463 forward_as_tuple(param.first, name, i),
2464 forward_as_tuple(price));
2465 }
2466 }
2467
2468 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {times});
2469 simDataWritten = true;
2470 QuantLib::ext::shared_ptr<PriceTermStructure> priceCurve;
2471
2472 if (param.second.first && useSpreadedTermStructures_) {
2473 vector<Real> simulationTimes;
2474 for (auto const& t : simulationTenors) {
2475 simulationTimes.push_back(commodityCurveDayCounter.yearFraction(asof_, asof_ + t));
2476 }
2477 if (simulationTimes.front() != 0.0) {
2478 simulationTimes.insert(simulationTimes.begin(), 0.0);
2479 quotes.insert(quotes.begin(), quotes.front());
2480 }
2481 // Created spreaded commodity price curve if we simulate commodities and spreads should be
2482 // used
2483 priceCurve = QuantLib::ext::make_shared<SpreadedPriceTermStructure>(initialCommodityCurve,
2484 simulationTimes, quotes);
2485 } else {
2486 priceCurve= QuantLib::ext::make_shared<InterpolatedPriceCurve<LinearFlat>>(
2487 simulationTenors, quotes, commodityCurveDayCounter, initialCommodityCurve->currency());
2488 }
2489
2490 auto orgBasisCurve =
2491 QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityBasisPriceTermStructure>(
2492 initialCommodityCurve.currentLink());
2493
2494 Handle<PriceTermStructure> pts;
2495 if (orgBasisCurve == nullptr) {
2496 pts = Handle<PriceTermStructure>(priceCurve);
2497 } else {
2498 auto baseIndex = commodityIndices_.find(
2499 {Market::defaultConfiguration, orgBasisCurve->baseIndex()->underlyingName()});
2500 QL_REQUIRE(baseIndex != commodityIndices_.end(),
2501 "Internal error in scenariosimmarket: couldn't find underlying base curve '"
2502 << orgBasisCurve->baseIndex()->underlyingName()
2503 << "' while building commodity basis curve '" << name << "'");
2504 pts = Handle<PriceTermStructure>(QuantLib::ext::make_shared<CommodityBasisPriceCurveWrapper>(
2505 orgBasisCurve, baseIndex->second.currentLink(), priceCurve));
2506 }
2507
2508 pts->setAdjustReferenceDate(false);
2509 pts->enableExtrapolation(allowsExtrapolation);
2510
2511 Handle<CommodityIndex> commIdx(parseCommodityIndex(name, false, pts));
2512 commodityIndices_.emplace(piecewise_construct,
2513 forward_as_tuple(Market::defaultConfiguration, name),
2514 forward_as_tuple(commIdx));
2515 } catch (const std::exception& e) {
2516 processException(continueOnError, e, name, param.first, simDataWritten);
2517 }
2518 }
2519 break;
2520 }
2522 for (const auto& name : param.second.second) {
2523 bool simDataWritten = false;
2524 try {
2525 LOG("building commodity volatility for " << name);
2526
2527 // Get initial base volatility structure
2528 Handle<BlackVolTermStructure> baseVol = initMarket->commodityVolatility(name, configuration);
2529
2530 Handle<BlackVolTermStructure> newVol;
2531 bool stickyStrike = parameters_->commodityVolSmileDynamics(name) == "StickyStrike";
2532 if (param.second.first) {
2533
2534 // Check and reorg moneyness and/or expiries to simplify subsequent code.
2535 vector<Real> moneyness = parameters->commodityVolMoneyness(name);
2536 QL_REQUIRE(!moneyness.empty(), "Commodity volatility moneyness for "
2537 << name << " should have at least one element.");
2538 sort(moneyness.begin(), moneyness.end());
2539 auto mIt = unique(moneyness.begin(), moneyness.end(),
2540 [](const Real& x, const Real& y) { return close(x, y); });
2541 QL_REQUIRE(mIt == moneyness.end(),
2542 "Commodity volatility moneyness values for " << name << " should be unique.");
2543
2544 vector<Period> expiries = parameters->commodityVolExpiries(name);
2545 QL_REQUIRE(!expiries.empty(), "Commodity volatility expiries for "
2546 << name << " should have at least one element.");
2547 sort(expiries.begin(), expiries.end());
2548 auto eIt = unique(expiries.begin(), expiries.end());
2549 QL_REQUIRE(eIt == expiries.end(),
2550 "Commodity volatility expiries for " << name << " should be unique.");
2551
2552 // Get this scenario simulation market's commodity price curve. An exception is expected
2553 // if there is no commodity curve but there is a commodity volatility.
2554 const auto& priceCurve = *commodityPriceCurve(name, configuration);
2555
2556 // More than one moneyness implies a surface. If we have a surface, we will build a
2557 // forward surface below which requires two yield term structures, one for the commodity
2558 // price currency and another that recovers the commodity forward prices. We don't want
2559 // the commodity prices changing with changes in the commodity price currency yield curve
2560 // so we take a copy here - it will work for sticky strike false also.
2561 bool isSurface = moneyness.size() > 1;
2562 Handle<YieldTermStructure> yts;
2563 Handle<YieldTermStructure> priceYts;
2564
2565 if (isSurface) {
2566
2567 vector<Date> dates{asof_};
2568 vector<Real> dfs{1.0};
2569
2570 auto discCurve = discountCurve(priceCurve->currency().code(), configuration);
2571 for (const auto& expiry : expiries) {
2572 auto d = asof_ + expiry;
2573 if (d == asof_)
2574 continue;
2575 dates.push_back(d);
2576 dfs.push_back(discCurve->discount(d, true));
2577 }
2578
2579 auto ytsPtr = QuantLib::ext::make_shared<DiscountCurve>(dates, dfs, discCurve->dayCounter());
2580 ytsPtr->enableExtrapolation();
2581 yts = Handle<YieldTermStructure>(ytsPtr);
2582 priceYts = Handle<YieldTermStructure>(
2583 QuantLib::ext::make_shared<PriceTermStructureAdapter>(priceCurve, ytsPtr));
2584 priceYts->enableExtrapolation();
2585 }
2586
2587 // Create surface of quotes, rows are moneyness, columns are expiries.
2588 using QuoteRow = vector<Handle<Quote>>;
2589 using QuoteMatrix = vector<QuoteRow>;
2590 QuoteMatrix quotes(moneyness.size(), QuoteRow(expiries.size()));
2591
2592 // Calculate up front the expiry times, dates and forward prices.
2593 vector<Date> expiryDates(expiries.size());
2594 vector<Time> expiryTimes(expiries.size());
2595 vector<Real> forwards(expiries.size());
2596 // TODO: do we want to use the base vol dc or - as elsewhere - a dc specified in the ssm
2597 // parameters?
2598 DayCounter dayCounter = baseVol->dayCounter();
2599 for (Size j = 0; j < expiries.size(); ++j) {
2600 Date d = asof_ + expiries[j];
2601 expiryDates[j] = d;
2602 expiryTimes[j] = dayCounter.yearFraction(asof_, d);
2603 forwards[j] = priceCurve->price(d);
2604 }
2605
2606 // Store the quotes.
2607 Size index = 0;
2608 for (Size i = 0; i < moneyness.size(); ++i) {
2609 for (Size j = 0; j < expiries.size(); ++j) {
2610 Real strike = moneyness[i] * forwards[j];
2611 auto vol = baseVol->blackVol(expiryDates[j], strike);
2612 auto quote =
2613 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : vol);
2614 simDataTmp.emplace(piecewise_construct, forward_as_tuple(param.first, name, index),
2615 forward_as_tuple(quote));
2617 absoluteSimDataTmp.emplace(piecewise_construct,
2618 forward_as_tuple(param.first, name, index),
2619 forward_as_tuple(vol));
2620 }
2621 quotes[i][j] = Handle<Quote>(quote);
2622 ++index;
2623 }
2624 }
2625
2626 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {moneyness, expiryTimes});
2627 simDataWritten = true;
2628
2629 // Create volatility structure
2630 if (!isSurface) {
2631 DLOG("Ssm comm vol for " << name << " uses BlackVarianceCurve3.");
2633 newVol =
2634 Handle<BlackVolTermStructure>(QuantLib::ext::make_shared<SpreadedBlackVolatilityCurve>(
2635 Handle<BlackVolTermStructure>(baseVol), expiryTimes, quotes[0], true));
2636 } else {
2637 newVol = Handle<BlackVolTermStructure>(QuantLib::ext::make_shared<BlackVarianceCurve3>(
2638 0, NullCalendar(), baseVol->businessDayConvention(), dayCounter, expiryTimes,
2639 quotes[0], false));
2640 }
2641 } else {
2642 DLOG("Ssm comm vol for " << name << " uses BlackVarianceSurfaceMoneynessSpot.");
2643
2644 bool flatExtrapMoneyness = true;
2645 Handle<Quote> spot(QuantLib::ext::make_shared<SimpleQuote>(priceCurve->price(0)));
2647 // get init market curves to populate sticky ts in vol surface ctor
2648 Handle<YieldTermStructure> initMarketYts =
2649 initMarket->discountCurve(priceCurve->currency().code(), configuration);
2650 Handle<QuantExt::PriceTermStructure> priceCurve =
2651 initMarket->commodityPriceCurve(name, configuration);
2652 Handle<YieldTermStructure> initMarketPriceYts(
2653 QuantLib::ext::make_shared<PriceTermStructureAdapter>(*priceCurve, *initMarketYts));
2654 // create vol surface
2655 newVol = Handle<BlackVolTermStructure>(
2656 QuantLib::ext::make_shared<SpreadedBlackVolatilitySurfaceMoneynessForward>(
2657 Handle<BlackVolTermStructure>(baseVol), spot, expiryTimes, moneyness,
2658 quotes, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot->value())),
2659 initMarketPriceYts, initMarketYts, priceYts, yts, stickyStrike));
2660 } else {
2661 newVol = Handle<BlackVolTermStructure>(
2662 QuantLib::ext::make_shared<BlackVarianceSurfaceMoneynessForward>(
2663 baseVol->calendar(), spot, expiryTimes, moneyness, quotes, dayCounter,
2664 priceYts, yts, stickyStrike, flatExtrapMoneyness));
2665 }
2666 }
2667
2668 } else {
2669 string decayModeString = parameters->commodityVolDecayMode();
2670 DLOG("Deterministic commodity volatilities with decay mode " << decayModeString << " for "
2671 << name);
2672 ReactionToTimeDecay decayMode = parseDecayMode(decayModeString);
2673 // Copy what was done for equity here
2674 // May need to revisit when looking at commodity RFE
2675 newVol = Handle<BlackVolTermStructure>(
2676 QuantLib::ext::make_shared<QuantExt::DynamicBlackVolTermStructure<tag::curve>>(
2677 baseVol, 0, NullCalendar(), decayMode,
2678 stickyStrike ? StickyStrike : StickyLogMoneyness));
2679 }
2680
2681 newVol->setAdjustReferenceDate(false);
2682 newVol->enableExtrapolation(baseVol->allowsExtrapolation());
2683 commodityVols_.emplace(piecewise_construct,
2684 forward_as_tuple(Market::defaultConfiguration, name),
2685 forward_as_tuple(newVol));
2686
2687 DLOG("Commodity volatility curve built for " << name);
2688 } catch (const std::exception& e) {
2689 processException(continueOnError, e, name, param.first, simDataWritten);
2690 }
2691 }
2692 break;
2693
2695 for (const auto& name : param.second.second) {
2696 bool simDataWritten = false;
2697 try {
2698 LOG("Adding correlations for " << name << " from configuration " << configuration);
2699
2700 vector<string> tokens = getCorrelationTokens(name);
2701 QL_REQUIRE(tokens.size() == 2, "not a valid correlation pair: " << name);
2702 pair<string, string> pair = std::make_pair(tokens[0], tokens[1]);
2703
2704 QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure> corr;
2705 Handle<QuantExt::CorrelationTermStructure> baseCorr =
2706 initMarket->correlationCurve(pair.first, pair.second, configuration);
2707
2708 Handle<QuantExt::CorrelationTermStructure> ch;
2709 if (param.second.first) {
2710 Size n = parameters->correlationStrikes().size();
2711 Size m = parameters->correlationExpiries().size();
2712 vector<vector<Handle<Quote>>> quotes(n, vector<Handle<Quote>>(m, Handle<Quote>()));
2713 vector<Time> times(m);
2714 Calendar cal = baseCorr->calendar();
2715 DayCounter dc = baseCorr->dayCounter();
2716
2717 for (Size i = 0; i < n; i++) {
2718 Real strike = parameters->correlationStrikes()[i];
2719
2720 for (Size j = 0; j < m; j++) {
2721 // Index is expiries then strike TODO: is this the best?
2722 Size idx = i * m + j;
2723 times[j] = dc.yearFraction(asof_, asof_ + parameters->correlationExpiries()[j]);
2724 Real correlation =
2725 baseCorr->correlation(asof_ + parameters->correlationExpiries()[j], strike);
2726 QuantLib::ext::shared_ptr<SimpleQuote> q(
2727 new SimpleQuote(useSpreadedTermStructures_ ? 0.0 : correlation));
2728 simDataTmp.emplace(
2729 std::piecewise_construct,
2730 std::forward_as_tuple(RiskFactorKey::KeyType::Correlation, name, idx),
2731 std::forward_as_tuple(q));
2733 absoluteSimDataTmp.emplace(
2734 std::piecewise_construct,
2735 std::forward_as_tuple(RiskFactorKey::KeyType::Correlation, name, idx),
2736 std::forward_as_tuple(correlation));
2737 }
2738 quotes[i][j] = Handle<Quote>(q);
2739 }
2740 }
2741
2742 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name,
2743 {parameters->correlationStrikes(), times});
2744 simDataWritten = true;
2745
2746 if (n == 1 && m == 1) {
2748 ch = Handle<QuantExt::CorrelationTermStructure>(
2749 QuantLib::ext::make_shared<QuantExt::SpreadedCorrelationCurve>(baseCorr, times,
2750 quotes[0]));
2751 } else {
2752 ch = Handle<QuantExt::CorrelationTermStructure>(QuantLib::ext::make_shared<FlatCorrelation>(
2753 baseCorr->settlementDays(), cal, quotes[0][0], dc));
2754 }
2755 } else if (n == 1) {
2757 ch = Handle<QuantExt::CorrelationTermStructure>(
2758 QuantLib::ext::make_shared<QuantExt::SpreadedCorrelationCurve>(baseCorr, times,
2759 quotes[0]));
2760 } else {
2761 ch = Handle<QuantExt::CorrelationTermStructure>(
2762 QuantLib::ext::make_shared<InterpolatedCorrelationCurve<Linear>>(times, quotes[0], dc,
2763 cal));
2764 }
2765 } else {
2766 QL_FAIL("only atm or flat correlation termstructures currently supported");
2767 }
2768
2769 ch->enableExtrapolation(baseCorr->allowsExtrapolation());
2770 } else {
2771 ch = Handle<QuantExt::CorrelationTermStructure>(*baseCorr);
2772 }
2773
2774 ch->setAdjustReferenceDate(false);
2775 correlationCurves_[make_tuple(Market::defaultConfiguration, pair.first, pair.second)] = ch;
2776 } catch (const std::exception& e) {
2777 processException(continueOnError, e, name, param.first, simDataWritten);
2778 }
2779 }
2780 break;
2781
2783 for (const auto& name : param.second.second) {
2784 bool simDataWritten = false;
2785 try {
2786 DLOG("Adding cpr " << name << " from configuration " << configuration);
2787 Real v = initMarket->cpr(name, configuration)->value();
2788 auto q = QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : v);
2790 auto m = [v](Real x) { return x + v; };
2791 cprs_.insert(make_pair(make_pair(Market::defaultConfiguration, name),
2792 Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(m)>>(
2793 Handle<Quote>(q), m))));
2794 } else {
2795 cprs_.insert(make_pair(make_pair(Market::defaultConfiguration, name), Handle<Quote>(q)));
2796 }
2797
2798 if (param.second.first) {
2799 simDataTmp.emplace(std::piecewise_construct, std::forward_as_tuple(param.first, name),
2800 std::forward_as_tuple(q));
2802 absoluteSimDataTmp.emplace(std::piecewise_construct,
2803 std::forward_as_tuple(param.first, name),
2804 std::forward_as_tuple(v));
2805 }
2806 }
2807 writeSimData(simDataTmp, absoluteSimDataTmp, param.first, name, {});
2808 simDataWritten = true;
2809 } catch (const std::exception& e) {
2810 processException(continueOnError, e, name, param.first, simDataWritten);
2811 }
2812 }
2813 break;
2814
2816 // nothing to do, these are written to asd
2817 break;
2818
2820 // nothing to do, these are written to asd
2821 break;
2822
2824 WLOG("RiskFactorKey None not yet implemented");
2825 break;
2826 }
2827
2828 if (!param.second.second.empty()) {
2829 LOG("built " << std::left << std::setw(25) << param.first << std::right << std::setw(10)
2830 << param.second.second.size() << std::setprecision(3) << std::setw(15)
2831 << static_cast<double>(timer.elapsed().wall) / 1E6 << " ms");
2832 }
2833
2834 } catch (const std::exception& e) {
2835 StructuredMessage(ore::data::StructuredMessage::Category::Error,
2836 ore::data::StructuredMessage::Group::Curve, e.what(),
2837 {{"exceptionType", "ScenarioSimMarket top level catch - this should never happen, "
2838 "contact dev. Results are likely wrong or incomplete."}})
2839 .log();
2840 processException(continueOnError, e);
2841 }
2842 }
2843
2844 // swap indices
2845 DLOG("building swap indices...");
2846 for (const auto& it : parameters->swapIndices()) {
2847 addSwapIndexToSsm(it.first, continueOnError);
2848 }
2849
2850 if (offsetScenario_ != nullptr && offsetScenario_->isAbsolute() && !useSpreadedTermStructures_) {
2851 auto recastedScenario = recastScenario(offsetScenario_, offsetScenario_->coordinates(), coordinatesData_);
2852 QL_REQUIRE(recastedScenario != nullptr, "ScenarioSimMarke: Offset Scenario couldn't applied");
2853 for (auto& [key, quote] : simData_) {
2854 if (recastedScenario->has(key)) {
2855 quote->setValue(recastedScenario->get(key));
2856 } else {
2857 QL_FAIL("ScenarioSimMarket: Offset Scenario doesnt contain key "
2858 << key
2859 << ". Internal error, possibly an internal error in the recastScenario method, contact dev.");
2860 }
2861 }
2862 } else if (offsetScenario_ != nullptr && offsetScenario_->isAbsolute() && useSpreadedTermStructures_) {
2863 auto recastedScenario = recastScenario(offsetScenario_, offsetScenario_->coordinates(), coordinatesData_);
2864 QL_REQUIRE(recastedScenario != nullptr, "ScenarioSimMarke: Offset Scenario couldn't applied");
2865 for (auto& [key, data] : simData_) {
2866 if (recastedScenario->has(key)) {
2867 auto shift = getDifferenceScenario(key.keytype, absoluteSimData_[key], recastedScenario->get(key));
2868 data->setValue(shift);
2869 absoluteSimData_[key] = recastedScenario->get(key);
2870 } else {
2871 QL_FAIL("ScenarioSimMarket: Offset Scenario doesnt contain key "
2872 << key
2873 << ". Internal error, possibly an internal error in the recastScenario method, contact dev.");
2874 }
2875 }
2876 } else if (offsetScenario_ != nullptr && !offsetScenario_->isAbsolute() && !useSpreadedTermStructures_) {
2877 auto recastedScenario = recastScenario(offsetScenario_, offsetScenario_->coordinates(), coordinatesData_);
2878 QL_REQUIRE(recastedScenario != nullptr, "ScenarioSimMarke: Offset Scenario couldn't applied");
2879 for (auto& [key, quote] : simData_) {
2880 if (recastedScenario->has(key)) {
2881 quote->setValue(addDifferenceToScenario(key.keytype, quote->value(), recastedScenario->get(key)));
2882 } else {
2883 QL_FAIL("ScenarioSimMarket: Offset Scenario doesnt contain key "
2884 << key
2885 << ". Internal error, possibly an internal error in the recastScenario method, contact dev.");
2886 }
2887 }
2888 } else if (offsetScenario_ != nullptr && !offsetScenario_->isAbsolute() && useSpreadedTermStructures_) {
2889 auto recastedScenario = recastScenario(offsetScenario_, offsetScenario_->coordinates(), coordinatesData_);
2890 QL_REQUIRE(recastedScenario != nullptr, "ScenarioSimMarke: Offset Scenario couldn't applied");
2891 for (auto& [key, quote] : simData_) {
2892 if (recastedScenario->has(key)) {
2893 quote->setValue(recastedScenario->get(key));
2894 absoluteSimData_[key] =
2895 addDifferenceToScenario(key.keytype, absoluteSimData_[key], recastedScenario->get(key));
2896 } else {
2897 QL_FAIL("ScenarioSimMarket: Offset Scenario doesnt contain key "
2898 << key
2899 << ". Internal error, possibly an internal error in the recastScenario method, contact dev.");
2900 }
2901 }
2902 }
2903
2904 LOG("building base scenario");
2905 auto tmp = QuantLib::ext::make_shared<SimpleScenario>(initMarket->asofDate(), "BASE", 1.0);
2906 if (!useSpreadedTermStructures_) {
2907 for (auto const& data : simData_) {
2908 tmp->add(data.first, data.second->value());
2909 }
2910 tmp->setAbsolute(true);
2911 for (auto const& [type, name, coordinates] : coordinatesData_) {
2912 tmp->setCoordinates(type, name, coordinates);
2913 }
2914 baseScenarioAbsolute_ = baseScenario_ = tmp;
2915 } else {
2916 auto tmpAbs = QuantLib::ext::make_shared<SimpleScenario>(initMarket->asofDate(), "BASE", 1.0);
2917 for (auto const& data : simData_) {
2918 tmp->add(data.first, data.second->value());
2919 }
2920 for (auto const& data : absoluteSimData_) {
2921 tmpAbs->add(data.first, data.second);
2922 }
2923 tmp->setAbsolute(false);
2924 tmpAbs->setAbsolute(true);
2925 for (auto const& [type, name, coordinates] : coordinatesData_) {
2926 tmp->setCoordinates(type, name, coordinates);
2927 tmpAbs->setCoordinates(type, name, coordinates);
2928 }
2929 baseScenario_ = tmp;
2930 baseScenarioAbsolute_ = tmpAbs;
2931 }
2932 LOG("building base scenario done");
2933}
2934
2935bool ScenarioSimMarket::addSwapIndexToSsm(const std::string& indexName, const bool continueOnError) {
2936 auto dsc = parameters_->swapIndices().find(indexName);
2937 if (dsc == parameters_->swapIndices().end()) {
2938 return false;
2939 }
2940 DLOG("Adding swap index " << indexName << " with discounting index " << dsc->second);
2941 try {
2942 addSwapIndex(indexName, dsc->second, Market::defaultConfiguration);
2943 DLOG("Adding swap index " << indexName << " done.");
2944 return true;
2945 } catch (const std::exception& e) {
2946 processException(continueOnError, e, indexName);
2947 return false;
2948 }
2949}
2950
2951void ScenarioSimMarket::reset() {
2952 auto filterBackup = filter_;
2953 // no filter
2954 filter_ = QuantLib::ext::make_shared<ScenarioFilter>();
2955 // reset eval date
2956 Settings::instance().evaluationDate() = baseScenario_->asof();
2957 // reset numeraire and label
2958 numeraire_ = baseScenario_->getNumeraire();
2959 label_ = baseScenario_->label();
2960 // delete the sim data cache
2961 cachedSimData_.clear();
2962 cachedSimDataActive_.clear();
2963 // reset term structures
2964 applyScenario(baseScenario_);
2965 // clear delta scenario keys
2966 diffToBaseKeys_.clear();
2967 // see the comment in update() for why this is necessary...
2968 if (ObservationMode::instance().mode() == ObservationMode::Mode::Unregister) {
2969 QuantLib::ext::shared_ptr<QuantLib::Observable> obs = QuantLib::Settings::instance().evaluationDate();
2970 obs->notifyObservers();
2971 }
2972 // reset fixing manager
2973 fixingManager_->reset();
2974 // restore the filter
2975 filter_ = filterBackup;
2976}
2977
2978void ScenarioSimMarket::applyScenario(const QuantLib::ext::shared_ptr<Scenario>& scenario) {
2979
2980 currentScenario_ = scenario;
2981
2982 // 1 handle delta scenario
2983
2984 auto deltaScenario = QuantLib::ext::dynamic_pointer_cast<DeltaScenario>(scenario);
2985
2986 /*! our assumption is that either all or none of the scenarios we apply are
2987 delta scenarios or the base scenario */
2988
2989 if (deltaScenario != nullptr) {
2990 for (auto const& key : diffToBaseKeys_) {
2991 auto it = simData_.find(key);
2992 if (it != simData_.end()) {
2993 it->second->setValue(baseScenario_->get(key));
2994 }
2995 }
2996 diffToBaseKeys_.clear();
2997 auto delta = deltaScenario->delta();
2998 bool missingPoint = false;
2999 for (auto const& key : delta->keys()) {
3000 auto it = simData_.find(key);
3001 if (it == simData_.end()) {
3002 ALOG("simulation data point missing for key " << key);
3003 missingPoint = true;
3004 } else {
3005 if (filter_->allow(key)) {
3006 it->second->setValue(delta->get(key));
3007 diffToBaseKeys_.insert(key);
3008 }
3009 }
3010 }
3011 QL_REQUIRE(!missingPoint, "simulation data points missing from scenario, exit.");
3012
3013 return;
3014 }
3015
3016 // 2 apply scenario based on cached indices for simData_ for a SimpleScenario
3017 // the scenario's keysHash() is used to make sure consistent keys are used
3018 // if keysHash() is zero, this check is not effective (for backwards compatibility)
3019 if (cacheSimData_) {
3020 if (auto s = QuantLib::ext::dynamic_pointer_cast<SimpleScenario>(scenario)) {
3021
3022 // fill cache
3023
3024 if (cachedSimData_.empty() || s->keysHash() != cachedSimDataKeysHash_) {
3025 cachedSimData_.clear();
3026 cachedSimDataKeysHash_ = s->keysHash();
3027 Size count = 0;
3028 for (auto const& key : s->keys()) {
3029 auto it = simData_.find(key);
3030 if (it == simData_.end()) {
3031 WLOG("simulation data point missing for key " << key);
3032 cachedSimData_.push_back(QuantLib::ext::shared_ptr<SimpleQuote>());
3033 cachedSimDataActive_.push_back(false);
3034 } else {
3035 ++count;
3036 cachedSimData_.push_back(it->second);
3037 cachedSimDataActive_.push_back(filter_->allow(key));
3038 }
3039 }
3040 if (count != simData_.size() && !allowPartialScenarios_) {
3041 ALOG("mismatch between scenario and sim data size, " << count << " vs " << simData_.size());
3042 for (auto it : simData_) {
3043 if (!scenario->has(it.first))
3044 WLOG("Key " << it.first << " missing in scenario");
3045 }
3046 QL_FAIL("mismatch between scenario and sim data size, exit.");
3047 }
3048 }
3049
3050 // apply scenario data according to cached indices
3051
3052 Size i = 0;
3053 for (auto const& q : s->data()) {
3054 if (cachedSimDataActive_[i])
3055 cachedSimData_[i]->setValue(q);
3056 ++i;
3057 }
3058
3059 return;
3060 }
3061 }
3062
3063 // 3 all other cases
3064
3065 const vector<RiskFactorKey>& keys = scenario->keys();
3066
3067 Size count = 0;
3068 for (const auto& key : keys) {
3069 // Loop through the scenario keys and check which keys are present in simData_,
3070 // adding to the count when a match is identified
3071 // Then check that the count=simData_.size - this ensures that simData_ is a valid
3072 // subset of the scenario - fails is a member of simData is not present in the
3073 // scenario
3074 auto it = simData_.find(key);
3075 if (it == simData_.end()) {
3076 WLOG("simulation data point missing for key " << key);
3077 } else {
3078 if (filter_->allow(key)) {
3079 it->second->setValue(scenario->get(key));
3080 }
3081 count++;
3082 }
3083 }
3084
3085 if (count != simData_.size() && !allowPartialScenarios_) {
3086 ALOG("mismatch between scenario and sim data size, " << count << " vs " << simData_.size());
3087 for (auto it : simData_) {
3088 if (!scenario->has(it.first))
3089 ALOG("Key " << it.first << " missing in scenario");
3090 }
3091 QL_FAIL("mismatch between scenario and sim data size, exit.");
3092 }
3093}
3094
3095void ScenarioSimMarket::preUpdate() {
3096 ObservationMode::Mode om = ObservationMode::instance().mode();
3097 if (om == ObservationMode::Mode::Disable)
3098 ObservableSettings::instance().disableUpdates(false);
3099 else if (om == ObservationMode::Mode::Defer)
3100 ObservableSettings::instance().disableUpdates(true);
3101}
3102
3103void ScenarioSimMarket::updateDate(const Date& d) {
3104 ObservationMode::Mode om = ObservationMode::instance().mode();
3105 if (d != Settings::instance().evaluationDate())
3106 Settings::instance().evaluationDate() = d;
3107 else if (om == ObservationMode::Mode::Unregister) {
3108 // Due to some of the notification chains having been unregistered,
3109 // it is possible that some lazy objects might be missed in the case
3110 // that the evaluation date has not been updated. Therefore, we
3111 // manually kick off an observer notification from this level.
3112 // We have unit regression tests in OREAnalyticsTestSuite to ensure
3113 // the various ObservationMode settings return the anticipated results.
3114 QuantLib::ext::shared_ptr<QuantLib::Observable> obs = QuantLib::Settings::instance().evaluationDate();
3115 obs->notifyObservers();
3116 }
3117}
3118
3119void ScenarioSimMarket::updateScenario(const Date& d) {
3120 QL_REQUIRE(scenarioGenerator_ != nullptr, "ScenarioSimMarket::update: no scenario generator set");
3121 auto scenario = scenarioGenerator_->next(d);
3122 QL_REQUIRE(scenario->asof() == d,
3123 "Invalid Scenario date " << scenario->asof() << ", expected " << d);
3124 numeraire_ = scenario->getNumeraire();
3125 label_ = scenario->label();
3126 applyScenario(scenario);
3127}
3128
3129void ScenarioSimMarket::postUpdate(const Date& d, bool withFixings) {
3130 ObservationMode::Mode om = ObservationMode::instance().mode();
3131
3132 // Observation Mode - key to update these before fixings are set
3133 if (om == ObservationMode::Mode::Disable) {
3134 refresh();
3135 ObservableSettings::instance().enableUpdates();
3136 } else if (om == ObservationMode::Mode::Defer) {
3137 ObservableSettings::instance().enableUpdates();
3138 }
3139
3140 // Apply fixings as historical fixings. Must do this before we populate ASD
3141 if (withFixings)
3142 fixingManager_->update(d);
3143}
3144
3145void ScenarioSimMarket::updateAsd(const Date& d) {
3146 if (asd_) {
3147 // add additional scenario data to the given container, if required
3148 for (auto i : parameters_->additionalScenarioDataIndices()) {
3149 QuantLib::ext::shared_ptr<QuantLib::Index> index;
3150 try {
3151 index = *iborIndex(i);
3152 } catch (...) {
3153 }
3154 try {
3155 index = *swapIndex(i);
3156 } catch (...) {
3157 }
3158 QL_REQUIRE(index != nullptr, "ScenarioSimMarket::update() index " << i << " not found in sim market");
3159 if (auto fb = QuantLib::ext::dynamic_pointer_cast<FallbackIborIndex>(index)) {
3160 // proxy fallback ibor index by its rfr index's fixing
3161 index = fb->rfrIndex();
3162 }
3163 asd_->set(index->fixing(index->fixingCalendar().adjust(d)), AggregationScenarioDataType::IndexFixing, i);
3164 }
3165
3166 for (auto c : parameters_->additionalScenarioDataCcys()) {
3167 if (c != parameters_->baseCcy())
3168 asd_->set(fxSpot(c + parameters_->baseCcy())->value(), AggregationScenarioDataType::FXSpot, c);
3169 }
3170
3171 for (Size i = 0; i < parameters_->additionalScenarioDataNumberOfCreditStates(); ++i) {
3172 RiskFactorKey key(RiskFactorKey::KeyType::CreditState, std::to_string(i));
3173 QL_REQUIRE(currentScenario_->has(key), "scenario does not have key " << key);
3174 asd_->set(currentScenario_->get(key), AggregationScenarioDataType::CreditState, std::to_string(i));
3175 }
3176
3177 for (const auto& n : parameters_->additionalScenarioDataSurvivalWeights()) {
3178 RiskFactorKey key(RiskFactorKey::KeyType::SurvivalWeight, n);
3179 QL_REQUIRE(currentScenario_->has(key), "scenario does not have key " << key);
3180 asd_->set(currentScenario_->get(key), AggregationScenarioDataType::SurvivalWeight, n);
3181 RiskFactorKey rrKey(RiskFactorKey::KeyType::RecoveryRate, n);
3182 QL_REQUIRE(currentScenario_->has(rrKey), "scenario does not have key " << key);
3183 asd_->set(currentScenario_->get(rrKey), AggregationScenarioDataType::RecoveryRate, n);
3184 }
3185
3186 asd_->set(numeraire_, AggregationScenarioDataType::Numeraire);
3187
3188 asd_->next();
3189 }
3190}
3191
3192bool ScenarioSimMarket::isSimulated(const RiskFactorKey::KeyType& factor) const {
3193 return std::find(nonSimulatedFactors_.begin(), nonSimulatedFactors_.end(), factor) == nonSimulatedFactors_.end();
3194}
3195
3196Handle<YieldTermStructure> ScenarioSimMarket::getYieldCurve(const string& yieldSpecId,
3197 const TodaysMarketParameters& todaysMarketParams,
3198 const string& configuration,
3199 const QuantLib::ext::shared_ptr<Market>& market) const {
3200
3201 // If yield spec ID is "", return empty Handle
3202 if (yieldSpecId.empty())
3203 return Handle<YieldTermStructure>();
3204
3205 if (todaysMarketParams.hasConfiguration(configuration)) {
3206 // Look for yield spec ID in index curves of todays market
3207 if (todaysMarketParams.hasMarketObject(MarketObject::IndexCurve)) {
3208 for (const auto& indexMapping : todaysMarketParams.mapping(MarketObject::IndexCurve, configuration)) {
3209 if (indexMapping.second == yieldSpecId) {
3210 if (market) {
3211 return market->iborIndex(indexMapping.first, configuration)->forwardingTermStructure();
3212 } else {
3213 return iborIndex(indexMapping.first, configuration)->forwardingTermStructure();
3214 }
3215 }
3216 }
3217 }
3218
3219 // Look for yield spec ID in yield curves of todays market
3220 if (todaysMarketParams.hasMarketObject(MarketObject::YieldCurve)) {
3221 for (const auto& yieldMapping : todaysMarketParams.mapping(MarketObject::YieldCurve, configuration)) {
3222 if (yieldMapping.second == yieldSpecId) {
3223 if (market) {
3224 return market->yieldCurve(yieldMapping.first, configuration);
3225 } else {
3226 return yieldCurve(yieldMapping.first, configuration);
3227 }
3228 }
3229 }
3230 }
3231
3232 // Look for yield spec ID in discount curves of todays market
3233 if (todaysMarketParams.hasMarketObject(MarketObject::DiscountCurve)) {
3234 for (const auto& discountMapping : todaysMarketParams.mapping(MarketObject::DiscountCurve, configuration)) {
3235 if (discountMapping.second == yieldSpecId) {
3236 if (market) {
3237 return market->discountCurve(discountMapping.first, configuration);
3238 } else {
3239 return discountCurve(discountMapping.first, configuration);
3240 }
3241 }
3242 }
3243 }
3244 } else if (configuration != Market::defaultConfiguration) {
3245 // try to fall back on default configuration
3246 return getYieldCurve(yieldSpecId, todaysMarketParams, Market::defaultConfiguration);
3247 }
3248
3249 // If yield spec ID still has not been found, return empty Handle
3250 return Handle<YieldTermStructure>();
3251}
3252
3253} // namespace analytics
3254} // namespace ore
3255
static void populateVolMatrix(const QuantLib::Handle< QuantLib::BlackVolTermStructure > &termStructre, std::vector< std::vector< Handle< QuantLib::Quote > > > &quotesToPopulate, const std::vector< Real > &times, const std::vector< Real > &stdDevPoints, const QuantLib::Interpolation &forwardCurve, const QuantLib::Interpolation atmVolCurve)
MakeOISCapFloor & withSettlementDays(Natural settlementDays)
MakeOISCapFloor & withTelescopicValueDates(bool telescopicValueDates)
boost::shared_ptr< SwaptionVolatilityStructure > convert() const
Pseudo Fixings Manager.
Data types stored in the scenario class.
Definition: scenario.hpp:48
KeyType
Risk Factor types.
Definition: scenario.hpp:51
A scenario filter can exclude certain key from updating the scenario.
Simulation Market updated with discrete scenarios.
std::map< RiskFactorKey, QuantLib::ext::shared_ptr< SimpleQuote > > simData_
bool addSwapIndexToSsm(const std::string &indexName, const bool continueOnError)
QuantLib::Handle< QuantLib::YieldTermStructure > getYieldCurve(const std::string &yieldSpecId, const ore::data::TodaysMarketParameters &todaysMarketParams, const std::string &configuration, const QuantLib::ext::shared_ptr< ore::data::Market > &market=nullptr) const
const QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > parameters_
void addYieldCurve(const QuantLib::ext::shared_ptr< Market > &initMarket, const std::string &configuration, const RiskFactorKey::KeyType rf, const string &key, const vector< Period > &tenors, bool &simDataWritten, bool simulate=true, bool spreaded=false)
ScenarioSimMarket(const bool handlePseudoCurrencies)
Constructor.
std::set< std::tuple< RiskFactorKey::KeyType, std::string, std::vector< std::vector< Real > > > > coordinatesData_
std::map< RiskFactorKey, Real > absoluteSimData_
void writeSimData(std::map< RiskFactorKey, QuantLib::ext::shared_ptr< SimpleQuote > > &simDataTmp, std::map< RiskFactorKey, Real > &absoluteSimDataTmp, const RiskFactorKey::KeyType keyType, const std::string &name, const std::vector< std::vector< Real > > &coordinates)
Simulation Market.
Definition: simmarket.hpp:44
string name() const
const FallbackData & fallbackData(const string &iborIndex) const
bool useRfrCurveInSimulationMarket() const
bool isIndexReplaced(const string &iborIndex, const QuantLib::Date &asof=QuantLib::Date::maxDate()) const
Handle< Quote > fxSpot(const string &ccypair, const string &configuration=Market::defaultConfiguration) const
QuantLib::Handle< QuantExt::FxIndex > fxIndex(const string &fxIndex, const string &configuration=Market::defaultConfiguration) const
static const string defaultConfiguration
Handle< YieldTermStructure > discountCurve(const string &ccy, const string &configuration=Market::defaultConfiguration) const
map< pair< string, string >, Handle< OptionletVolatilityStructure > > capFloorCurves_
string shortSwapIndexBase(const string &key, const string &configuration=Market::defaultConfiguration) const override
string swapIndexBase(const string &key, const string &configuration=Market::defaultConfiguration) const override
virtual Handle< ZeroInflationIndex > zeroInflationIndex(const string &indexName, const string &configuration=Market::defaultConfiguration) const override
Handle< QuantExt::EquityIndex2 > equityCurve(const string &eqName, const string &configuration=Market::defaultConfiguration) const override
map< tuple< string, string, string >, Handle< QuantExt::CorrelationTermStructure > > correlationCurves_
map< pair< string, string >, QuantLib::Handle< QuantExt::EquityIndex2 > > equityCurves_
map< pair< string, string >, Handle< CPIVolatilitySurface > > cpiInflationCapFloorVolatilitySurfaces_
map< pair< string, string >, Handle< QuantExt::InflationIndexObserver > > baseCpis_
map< pair< string, string >, Handle< BlackVolTermStructure > > fxVols_
QuantLib::ext::shared_ptr< FXTriangulation > fx_
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
map< pair< string, string >, QuantLib::Handle< QuantExt::CommodityIndex > > commodityIndices_
map< pair< string, string >, Handle< QuantExt::CreditCurve > > defaultCurves_
map< pair< string, string >, Handle< IborIndex > > iborIndices_
map< pair< string, string >, Handle< YoYOptionletVolatilitySurface > > yoyCapFloorVolSurfaces_
Handle< QuantExt::CreditCurve > defaultCurve(const string &, const string &configuration=Market::defaultConfiguration) const override
map< pair< string, string >, Handle< YoYInflationIndex > > yoyInflationIndices_
map< pair< string, string >, Handle< QuantExt::CreditVolCurve > > cdsVols_
map< pair< string, string >, Handle< Quote > > equitySpots_
map< pair< string, string >, Handle< QuantLib::SwaptionVolatilityStructure > > swaptionCurves_
map< pair< string, string >, Handle< QuantExt::BaseCorrelationTermStructure > > baseCorrelations_
map< pair< string, string >, Handle< Quote > > cprs_
map< pair< string, string >, Handle< Quote > > recoveryRates_
QuantLib::Handle< QuantExt::PriceTermStructure > commodityPriceCurve(const string &commodityName, const string &configuration=Market::defaultConfiguration) const override
Handle< Quote > equitySpot(const string &eqName, const string &configuration=Market::defaultConfiguration) const override
map< pair< string, string >, Handle< ZeroInflationIndex > > zeroInflationIndices_
Handle< SwapIndex > swapIndex(const string &indexName, const string &configuration=Market::defaultConfiguration) const override
map< pair< string, string >, pair< string, string > > swaptionIndexBases_
map< pair< string, string >, Handle< BlackVolTermStructure > > equityVols_
virtual Handle< YoYInflationIndex > yoyInflationIndex(const string &indexName, const string &configuration=Market::defaultConfiguration) const override
map< pair< string, string >, Handle< QuantLib::SwaptionVolatilityStructure > > yieldVolCurves_
map< pair< string, string >, Handle< Quote > > securitySpreads_
Handle< YieldTermStructure > yieldCurve(const YieldCurveType &type, const string &ccy, const string &configuration=Market::defaultConfiguration) const override
Handle< IborIndex > iborIndex(const string &indexName, const string &configuration=Market::defaultConfiguration) const override
map< pair< string, string >, QuantLib::Handle< QuantLib::BlackVolTermStructure > > commodityVols_
map< pair< string, string >, std::pair< string, QuantLib::Period > > capFloorIndexBase_
bool hasConfiguration(const string &configuration) const
const map< string, string > & mapping(const MarketObject o, const string &configuration) const
bool hasMarketObject(const MarketObject &o) const
SafeStack< ValueType > value
Delta scenario class.
QuantLib::ext::shared_ptr< CurveSpec > parseCurveSpec(const string &s)
ReactionToTimeDecay
ForwardForwardVariance
QuantLib::ext::shared_ptr< ZeroInflationIndex > parseZeroInflationIndex(const string &s, const Handle< ZeroInflationTermStructure > &h=Handle< ZeroInflationTermStructure >())
Calendar parseCalendar(const string &s)
QuantLib::ext::shared_ptr< IborIndex > parseIborIndex(const string &s, const Handle< YieldTermStructure > &h=Handle< YieldTermStructure >())
data
#define LOG(text)
#define DLOG(text)
#define ALOG(text)
#define WLOG(text)
#define TLOG(text)
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable log(RandomVariable x)
Real getDifferenceScenario(const RiskFactorKey::KeyType keyType, const Real v1, const Real v2)
RiskFactorKey::KeyType yieldCurveRiskFactor(const ore::data::YieldCurveType y)
Map a yield curve type to a risk factor key type.
QuantLib::ext::shared_ptr< Scenario > recastScenario(const QuantLib::ext::shared_ptr< Scenario > &scenario, const std::map< std::pair< RiskFactorKey::KeyType, std::string >, std::vector< std::vector< Real > > > &oldCoordinates, const std::map< std::pair< RiskFactorKey::KeyType, std::string >, std::vector< std::vector< Real > > > &newCoordinates)
Real addDifferenceToScenario(const RiskFactorKey::KeyType keyType, const Real v, const Real d)
ore::data::YieldCurveType riskFactorYieldCurve(const RiskFactorKey::KeyType rf)
std::vector< std::string > getCorrelationTokens(const std::string &name)
Size size(const ValueType &v)
std::string to_string(const LocationInfo &l)
QuantLib::ext::shared_ptr< QuantExt::CommodityIndex > parseCommodityIndex(const string &name, bool hasPrefix, const Handle< PriceTermStructure > &ts, const Calendar &cal, const bool enforceFutureIndex)
Singleton class to hold global Observation Mode.
std::size_t count
A Market class that can be updated by Scenarios.
Scenario utility functions.
Simple scenario class.
vector< Real > strikes
vector< string > curveConfigs
string name