Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
sensitivityscenariogenerator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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
21
25
26#include <ql/math/comparison.hpp>
27#include <ql/time/calendars/target.hpp>
28#include <ql/time/daycounters/actualactual.hpp>
30
31#include <algorithm>
32#include <ostream>
33
34using namespace QuantLib;
35using namespace QuantExt;
36using namespace std;
37
38namespace ore {
39namespace analytics {
40
42
44 const QuantLib::ext::shared_ptr<SensitivityScenarioData>& sensitivityData, const QuantLib::ext::shared_ptr<Scenario>& baseScenario,
45 const QuantLib::ext::shared_ptr<ScenarioSimMarketParameters>& simMarketData,
46 const QuantLib::ext::shared_ptr<ScenarioSimMarket>& simMarket,
47 const QuantLib::ext::shared_ptr<ScenarioFactory>& sensiScenarioFactory, const bool overrideTenors,
48 const std::string& sensitivityTemplate, const bool continueOnError,
49 const QuantLib::ext::shared_ptr<Scenario>& baseScenarioAbsolute)
50 : ShiftScenarioGenerator(baseScenario, simMarketData, simMarket), sensitivityData_(sensitivityData),
51 sensiScenarioFactory_(sensiScenarioFactory), sensitivityTemplate_(sensitivityTemplate),
52 overrideTenors_(overrideTenors), continueOnError_(continueOnError),
53 baseScenarioAbsolute_(baseScenarioAbsolute == nullptr ? baseScenario : baseScenarioAbsolute) {
54
55 QL_REQUIRE(sensitivityData_, "SensitivityScenarioGenerator: sensitivityData is null");
56
58}
59
60struct findFactor {
61 findFactor(const string& factor) : factor_(factor) {}
62
63 string factor_;
64 const bool operator()(const std::pair<string, string>& p) const {
65 return (p.first == factor_) || (p.second == factor_);
66 }
67};
68
69struct findPair {
70 findPair(const string& first, const string& second) : first_(first), second_(second) {}
71
72 string first_;
73 string second_;
74 const bool operator()(const std::pair<string, string>& p) const {
75 return (p.first == first_ && p.second == second_) || (p.second == first_ && p.first == second_);
76 }
77};
78
79bool close(const Real& t_1, const Real& t_2) { return QuantLib::close(t_1, t_2); }
80
81bool vectorEqual(const vector<Real>& v_1, const vector<Real>& v_2) {
82 return (v_1.size() == v_2.size() && std::equal(v_1.begin(), v_1.end(), v_2.begin(), close));
83}
84
86 Date asof = baseScenario_->asof();
87
88 QL_REQUIRE(sensitivityData_->crossGammaFilter().empty() || sensitivityData_->computeGamma(),
89 "SensitivityScenarioGenerator::generateScenarios(): if gamma computation is disabled, the cross gamma "
90 "filter must be empty");
91
94
97
100
101 if (simMarketData_->simulateFxSpots()) {
103 generateFxScenarios(false);
104 }
105
108
109 if (simMarketData_->simulateDividendYield()) {
112 }
113
116
119
120 if (simMarketData_->simulateYoYInflationCapFloorVols()) {
123 }
124
125 if (simMarketData_->simulateZeroInflationCapFloorVols()) {
128 }
129
130 if (simMarketData_->simulateFXVols()) {
133 }
134
135 if (simMarketData_->simulateEquityVols()) {
138 }
139
140 if (simMarketData_->simulateSwapVols()) {
143 }
144
145 if (simMarketData_->simulateYieldVols()) {
148 }
149
150 if (simMarketData_->simulateCapFloorVols()) {
153 }
154
155 if (simMarketData_->simulateSurvivalProbabilities()) {
158 }
159
160 if (simMarketData_->simulateCdsVols()) {
163 }
164
165 if (simMarketData_->simulateBaseCorrelations()) {
168 }
169
170 if (simMarketData_->commodityCurveSimulate()) {
173 }
174
175 if (simMarketData_->commodityVolSimulate()) {
178 }
179
180 if (simMarketData_->securitySpreadsSimulate()) {
183 }
184
185 if (simMarketData_->simulateCorrelations()) {
188 }
189
190 // fill keyToFactor and factorToKey maps from scenario descriptions
191
192 DLOG("Fill maps linking factors with RiskFactorKeys");
193 keyToFactor_.clear();
194 factorToKey_.clear();
195 for (Size i = 0; i < scenarioDescriptions_.size(); ++i) {
196 RiskFactorKey key = scenarioDescriptions_[i].key1();
197 string factor = scenarioDescriptions_[i].factor1();
198 keyToFactor_[key] = factor;
199 factorToKey_[factor] = key;
200 DLOG("KeyToFactor map: " << key << " to " << factor);
201 }
202
203 // add simultaneous up-moves in two risk factors for cross gamma calculation
204
205 for (Size i = 0; i < scenarios_.size(); ++i) {
207 if (iDesc.type() != ScenarioDescription::Type::Up)
208 continue;
209 string iKeyName = iDesc.keyName1();
210
211 // check if iKey matches filter
212 if (find_if(sensitivityData_->crossGammaFilter().begin(), sensitivityData_->crossGammaFilter().end(),
213 findFactor(iKeyName)) == sensitivityData_->crossGammaFilter().end())
214 continue;
215
216 for (Size j = i + 1; j < scenarios_.size(); ++j) {
218 if (jDesc.type() != ScenarioDescription::Type::Up)
219 continue;
220 string jKeyName = jDesc.keyName1();
221
222 // check if jKey matches filter
223 if (find_if(sensitivityData_->crossGammaFilter().begin(), sensitivityData_->crossGammaFilter().end(),
224 findPair(iKeyName, jKeyName)) == sensitivityData_->crossGammaFilter().end())
225 continue;
226
227 // build cross scenario
228 QuantLib::ext::shared_ptr<Scenario> crossScenario =
229 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
230
231 for (auto const& k : baseScenario_->keys()) {
232 Real v1 = scenarios_[i]->get(k);
233 Real v2 = scenarios_[j]->get(k);
234 Real b = baseScenario_->get(k);
235 if (!close_enough(v1, b) || !close_enough(v2, b))
236 // this is correct for both absolute and relative shifts
237 crossScenario->add(k, v1 + v2 - b);
238 }
239
240 scenarioDescriptions_.push_back(ScenarioDescription(iDesc, jDesc));
241 crossScenario->label(to_string(scenarioDescriptions_.back()));
242 scenarios_.push_back(crossScenario);
243 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << crossScenario->label() << " created");
244 }
245 }
246
247 LOG("sensitivity scenario generator finished generating scenarios.");
248}
249
250namespace {
251bool tryGetBaseScenarioValue(const QuantLib::ext::shared_ptr<Scenario> baseScenario, const RiskFactorKey& key, Real& value,
252 const bool continueOnError) {
253 try {
254 value = baseScenario->get(key);
255 return true;
256 } catch (const std::exception& e) {
257 if (continueOnError) {
258 ALOG("skip scenario generation for key " << key << ": " << e.what());
259 } else {
260 QL_FAIL(e.what());
261 }
262 }
263 return false;
264}
265} // namespace
266
268 if (auto it = data.keyedShiftType.find(sensitivityTemplate_); it != data.keyedShiftType.end())
269 return it->second;
270 return data.shiftType;
271}
272
274 if (auto it = data.keyedShiftSize.find(sensitivityTemplate_); it != data.keyedShiftSize.end())
275 return it->second;
276 return data.shiftSize;
277}
278
280 if (auto it = data.keyedShiftScheme.find(sensitivityTemplate_); it != data.keyedShiftScheme.end())
281 return it->second;
282 return data.shiftScheme;
283}
284
287 return sensitivityData_->computeGamma() || (up && (scheme == ShiftScheme::Forward)) ||
288 (!up && scheme == ShiftScheme::Backward) || scheme == ShiftScheme::Central;
289}
290
291void SensitivityScenarioGenerator::storeShiftData(const RiskFactorKey& key, const Real rate, const Real newRate) {
292 if (shiftSizes_.find(key) == shiftSizes_.end()) {
293 shiftSizes_[key] = std::abs(newRate - rate);
294 baseValues_[key] = rate;
295 }
296}
297
299 Date asof = baseScenario_->asof();
300 // We can choose to shift fewer FX risk factors than listed in the market
301 // Is this too strict?
302 // - implemented to avoid cases where input cross FX rates are not consistent
303 // - Consider an example (baseCcy = EUR) of a GBPUSD FX trade - two separate routes to pricing
304 // - (a) call GBPUSD FX rate from sim market
305 // - (b) call GBPEUR and EURUSD FX rates, manually join them to obtain GBPUSD
306 // - now, if GBPUSD is an explicit risk factor in sim market, consider what happens
307 // - if we bump GBPUSD value and leave other FX rates unchanged (for e.g. a sensitivity analysis)
308 // - (a) the value of the trade changes
309 // - (b) the value of the GBPUSD trade stays the same
310 // - in light of the above we restrict the universe of FX pairs that we support here for the time being
311 string baseCcy = simMarketData_->baseCcy();
312 for (auto sensi_fx : sensitivityData_->fxShiftData()) {
313 string foreign = sensi_fx.first.substr(0, 3);
314 string domestic = sensi_fx.first.substr(3);
315 QL_REQUIRE((domestic == baseCcy) || (foreign == baseCcy),
316 "SensitivityScenarioGenerator does not support cross FX pairs("
317 << sensi_fx.first << ", but base currency is " << baseCcy << ")");
318 }
319 // Log an ALERT if some currencies in simmarket are excluded from the list
320 for (auto sim_fx : simMarketData_->fxCcyPairs()) {
321 if (sensitivityData_->fxShiftData().find(sim_fx) == sensitivityData_->fxShiftData().end()) {
322 WLOG("FX pair " << sim_fx << " in simmarket is not included in sensitivities analysis");
323 }
324 }
325 for (auto sensi_fx : sensitivityData_->fxShiftData()) {
326 string ccypair = sensi_fx.first; // foreign + domestic;
328 if (!isScenarioRelevant(up, data))
329 continue;
331 Real size = (up ? 1.0 : -1.0) * getShiftSize(data);
332 bool relShift = (type == ShiftType::Relative);
333
334 Real rate;
336 if (!tryGetBaseScenarioValue(baseScenarioAbsolute_, key, rate, continueOnError_))
337 continue;
338
339 QuantLib::ext::shared_ptr<Scenario> scenario =
340 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
341
342 Real newRate = relShift ? rate * (1.0 + size) : (rate + size);
343 scenario->add(key, sensitivityData_->useSpreadedTermStructures() ? newRate / rate : newRate);
344
345 storeShiftData(key, rate, newRate);
346
347
348 scenarios_.push_back(scenario);
350 scenario->label(to_string(scenarioDescriptions_.back()));
351 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
352 << " created: " << newRate);
353 }
354 DLOG("FX scenarios done");
355}
356
358 Date asof = baseScenario_->asof();
359 // We can choose to shift fewer discount curves than listed in the market
360 // Log an ALERT if some equities in simmarket are excluded from the sensitivities list
361 for (auto sim_equity : simMarketData_->equityNames()) {
362 if (sensitivityData_->equityShiftData().find(sim_equity) == sensitivityData_->equityShiftData().end()) {
363 WLOG("Equity " << sim_equity << " in simmarket is not included in sensitivities analysis");
364 }
365 }
366 for (auto e : sensitivityData_->equityShiftData()) {
367 string equity = e.first;
369 if (!isScenarioRelevant(up, data))
370 continue;
372 Real size = up ? getShiftSize(data) : -1.0 * getShiftSize(data);
373 bool relShift = (type == ShiftType::Relative);
374
375 Real rate;
377 if (!tryGetBaseScenarioValue(baseScenarioAbsolute_, key, rate, continueOnError_))
378 continue;
379
380 QuantLib::ext::shared_ptr<Scenario> scenario =
381 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
382
383 Real newRate = relShift ? rate * (1.0 + size) : (rate + size);
384 // Real newRate = up ? rate * (1.0 + getShiftSize(data)) : rate * (1.0 - getShiftSize(data));
385 scenario->add(key, sensitivityData_->useSpreadedTermStructures() ? newRate / rate : newRate);
386
387 storeShiftData(key, rate, newRate);
388
389 scenarios_.push_back(scenario);
391 scenario->label(to_string(scenarioDescriptions_.back()));
392 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
393 << " created: " << newRate);
394 }
395 DLOG("Equity scenarios done");
396}
397
398namespace {
399void checkShiftTenors(const std::vector<Period>& effective, const std::vector<Period>& config,
400 const std::string& curveLabel, bool continueOnError = false) {
401 if (effective.size() != config.size()) {
402 string message = "mismatch between effective shift tenors (" + std::to_string(effective.size()) +
403 ") and configured shift tenors (" + std::to_string(config.size()) + ") for " + curveLabel;
404 ALOG(message);
405 for (auto const& p : effective)
406 ALOG("effective tenor: " << p);
407 for (auto const& p : config)
408 ALOG("config tenor: " << p);
409 if (!continueOnError)
410 QL_FAIL(message);
411 }
412}
413} // namespace
414
416 Date asof = baseScenario_->asof();
417 // Log an ALERT if some currencies in simmarket are excluded from the list
418 for (auto sim_ccy : simMarketData_->ccys()) {
419 if (sensitivityData_->discountCurveShiftData().find(sim_ccy) ==
420 sensitivityData_->discountCurveShiftData().end()) {
421 WLOG("Currency " << sim_ccy << " in simmarket is not included in sensitivities analysis");
422 }
423 }
424
425 for (auto c : sensitivityData_->discountCurveShiftData()) {
426 string ccy = c.first;
427 Size n_ten;
428 try {
429 n_ten = simMarketData_->yieldCurveTenors(ccy).size();
430 } catch (const std::exception& e) {
431 ALOG("skip scenario generation for discount curve " << ccy << ": " << e.what());
432 continue;
433 }
434 // original curves' buffer
435 std::vector<Real> zeros(n_ten);
436 std::vector<Real> times(n_ten);
437 // buffer for shifted zero curves
438 std::vector<Real> shiftedZeros(n_ten);
440 if (!isScenarioRelevant(up, data))
441 continue;
442 ShiftType shiftType = getShiftType(data);
443 DayCounter dc = Actual365Fixed();
444 try {
445 if (auto s = simMarket_.lock()) {
446 dc = s->discountCurve(ccy)->dayCounter();
447 } else {
448 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
449 }
450 } catch (const std::exception&) {
451 WLOG("Day counter lookup in simulation market failed for discount curve " << ccy << ", using default A365");
452 }
453
454 Real quote = 0.0;
455 bool valid = true;
456 for (Size j = 0; j < n_ten; ++j) {
457 Date d = asof + simMarketData_->yieldCurveTenors(ccy)[j];
458 times[j] = dc.yearFraction(asof, d);
459
461 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, quote, continueOnError_);
462 zeros[j] = -std::log(quote) / times[j];
463 }
464 if (!valid)
465 continue;
466
467 std::vector<Period> shiftTenors = overrideTenors_ && simMarketData_->hasYieldCurveTenors(ccy)
468 ? simMarketData_->yieldCurveTenors(ccy)
469 : data.shiftTenors;
470 checkShiftTenors(shiftTenors, data.shiftTenors, "Discount Curve " + ccy, continueOnError_);
471 std::vector<Time> shiftTimes(shiftTenors.size());
472 for (Size j = 0; j < shiftTenors.size(); ++j)
473 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
474 Real shiftSize = getShiftSize(data);
475 QL_REQUIRE(shiftTenors.size() > 0, "Discount shift tenors not specified");
476
477 // Can we store a valid shift size?
478 bool validShiftSize = vectorEqual(times, shiftTimes);
479
480 for (Size j = 0; j < shiftTenors.size(); ++j) {
481
482 QuantLib::ext::shared_ptr<Scenario> scenario =
483 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
484 // apply zero rate shift at tenor point j
485 applyShift(j, shiftSize, up, shiftType, shiftTimes, zeros, times, shiftedZeros, true);
486
487 // store shifted discount curve in the scenario
488 for (Size k = 0; k < n_ten; ++k) {
489 RiskFactorKey key(RFType::DiscountCurve, ccy, k);
490 // FIXME why do we have that here, but not in generateIndexCurveScenarios?
491 if (!close_enough(shiftedZeros[k], zeros[k])) {
492 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
493 if (sensitivityData_->useSpreadedTermStructures()) {
494 Real discount = exp(-zeros[k] * times[k]);
495 scenario->add(key, shiftedDiscount / discount);
496 } else {
497 scenario->add(key, shiftedDiscount);
498 }
499 }
500
501 // Possibly store valid shift size
502 if (validShiftSize && j == k) {
503 storeShiftData(key, zeros[k], shiftedZeros[k]);
504 }
505 }
506
507 // add this scenario to the scenario vector
508 scenarios_.push_back(scenario);
510 scenario->label(to_string(scenarioDescriptions_.back()));
511 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
512
513 } // end of shift curve tenors
514 }
515 DLOG("Discount curve scenarios done");
516}
517
519 Date asof = baseScenario_->asof();
520
521 for (auto sim_idx : simMarketData_->indices()) {
522 if (sensitivityData_->indexCurveShiftData().find(sim_idx) == sensitivityData_->indexCurveShiftData().end()) {
523 WLOG("Index " << sim_idx << " in simmarket is not included in sensitivities analysis");
524 }
525 }
526
527 for (auto idx : sensitivityData_->indexCurveShiftData()) {
528 string indexName = idx.first;
529 Size n_ten;
530 try {
531 n_ten = simMarketData_->yieldCurveTenors(indexName).size();
532 } catch (const std::exception& e) {
533 ALOG("skip scenario generation for index curve " << indexName << ": " << e.what());
534 continue;
535 }
536 // original curves' buffer
537 std::vector<Real> zeros(n_ten);
538 std::vector<Real> times(n_ten);
539 // buffer for shifted zero curves
540 std::vector<Real> shiftedZeros(n_ten);
541
543 if (!isScenarioRelevant(up, data))
544 continue;
545 ShiftType shiftType = getShiftType(data);
546
547 DayCounter dc = Actual365Fixed();
548 try {
549 if (auto s = simMarket_.lock()) {
550 dc = s->iborIndex(indexName)->forwardingTermStructure()->dayCounter();
551 } else {
552 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
553 }
554 } catch (const std::exception&) {
555 WLOG("Day counter lookup in simulation market failed for index " << indexName << ", using default A365");
556 }
557
558 Real quote = 0.0;
559 bool valid = true;
560 for (Size j = 0; j < n_ten; ++j) {
561 Date d = asof + simMarketData_->yieldCurveTenors(indexName)[j];
562 times[j] = dc.yearFraction(asof, d);
564 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, quote, continueOnError_);
565 zeros[j] = -std::log(quote) / times[j];
566 }
567 if (!valid)
568 continue;
569
570 std::vector<Period> shiftTenors = overrideTenors_ && simMarketData_->hasYieldCurveTenors(indexName)
571 ? simMarketData_->yieldCurveTenors(indexName)
572 : data.shiftTenors;
573 checkShiftTenors(shiftTenors, data.shiftTenors, "Index Curve " + indexName, continueOnError_);
574 std::vector<Time> shiftTimes(shiftTenors.size());
575 for (Size j = 0; j < shiftTenors.size(); ++j)
576 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
577 Real shiftSize = getShiftSize(data);
578 QL_REQUIRE(shiftTenors.size() > 0, "Index shift tenors not specified");
579
580 // Can we store a valid shift size?
581 bool validShiftSize = vectorEqual(times, shiftTimes);
582
583 for (Size j = 0; j < shiftTenors.size(); ++j) {
584
585 QuantLib::ext::shared_ptr<Scenario> scenario =
586 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
587
588 // apply zero rate shift at tenor point j
589 applyShift(j, shiftSize, up, shiftType, shiftTimes, zeros, times, shiftedZeros, true);
590
591 // store shifted discount curve for this index in the scenario
592 for (Size k = 0; k < n_ten; ++k) {
593 RiskFactorKey key(RFType::IndexCurve, indexName, k);
594
595 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
596 if (sensitivityData_->useSpreadedTermStructures()) {
597 Real discount = exp(-zeros[k] * times[k]);
598 scenario->add(key, shiftedDiscount / discount);
599 } else {
600 scenario->add(key, shiftedDiscount);
601 }
602
603 // Possibly store valid shift size
604 if (validShiftSize && j == k) {
605 storeShiftData(key, zeros[k], shiftedZeros[k]);
606 }
607 }
608
609 // add this scenario to the scenario vector
610 scenarios_.push_back(scenario);
612 scenario->label(to_string(scenarioDescriptions_.back()));
613 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
614 << " created for indexName " << indexName);
615
616 } // end of shift curve tenors
617 }
618 DLOG("Index curve scenarios done");
619}
620
622 Date asof = baseScenario_->asof();
623 // We can choose to shift fewer yield curves than listed in the market
624 // Log an ALERT if some yield curves in simmarket are excluded from the list
625 for (auto sim_yc : simMarketData_->yieldCurveNames()) {
626 if (sensitivityData_->yieldCurveShiftData().find(sim_yc) == sensitivityData_->yieldCurveShiftData().end()) {
627 WLOG("Yield Curve " << sim_yc << " in simmarket is not included in sensitivities analysis");
628 }
629 }
630
631 for (auto y : sensitivityData_->yieldCurveShiftData()) {
632 string name = y.first;
633 Size n_ten;
634 try {
635 n_ten = simMarketData_->yieldCurveTenors(name).size();
636 } catch (const std::exception& e) {
637 ALOG("skip scenario generation for yield curve " << name << ": " << e.what());
638 continue;
639 }
640 // original curves' buffer
641 std::vector<Real> zeros(n_ten);
642 std::vector<Real> times(n_ten);
643 // buffer for shifted zero curves
644 std::vector<Real> shiftedZeros(n_ten);
646 if (!isScenarioRelevant(up, data))
647 continue;
648 ShiftType shiftType = getShiftType(data);
649 DayCounter dc = Actual365Fixed();
650 try {
651 if (auto s = simMarket_.lock()) {
652 dc = s->yieldCurve(name)->dayCounter();
653 } else {
654 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
655 }
656 } catch (const std::exception&) {
657 WLOG("Day counter lookup in simulation market failed for yield curve " << name << ", using default A365");
658 }
659
660 Real quote = 0.0;
661 bool valid = true;
662 for (Size j = 0; j < n_ten; ++j) {
663 Date d = asof + simMarketData_->yieldCurveTenors(name)[j];
664 times[j] = dc.yearFraction(asof, d);
666 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, quote, continueOnError_);
667 zeros[j] = -std::log(quote) / times[j];
668 }
669 if (!valid)
670 continue;
671
672 const std::vector<Period>& shiftTenors = overrideTenors_ && simMarketData_->hasYieldCurveTenors(name)
673 ? simMarketData_->yieldCurveTenors(name)
674 : data.shiftTenors;
675 checkShiftTenors(shiftTenors, data.shiftTenors, "Yield Curve " + name, continueOnError_);
676 std::vector<Time> shiftTimes(shiftTenors.size());
677 for (Size j = 0; j < shiftTenors.size(); ++j)
678 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
679 Real shiftSize = getShiftSize(data);
680 QL_REQUIRE(shiftTenors.size() > 0, "Discount shift tenors not specified");
681
682 // Can we store a valid shift size?
683 bool validShiftSize = vectorEqual(times, shiftTimes);
684
685 for (Size j = 0; j < shiftTenors.size(); ++j) {
686
687 QuantLib::ext::shared_ptr<Scenario> scenario =
688 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
689
690 // apply zero rate shift at tenor point j
691 applyShift(j, shiftSize, up, shiftType, shiftTimes, zeros, times, shiftedZeros, true);
692
693 // store shifted discount curve in the scenario
694 for (Size k = 0; k < n_ten; ++k) {
695 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
696 RiskFactorKey key(RFType::YieldCurve, name, k);
697 if (sensitivityData_->useSpreadedTermStructures()) {
698 Real discount = exp(-zeros[k] * times[k]);
699 scenario->add(key, shiftedDiscount / discount);
700 } else {
701 scenario->add(key, shiftedDiscount);
702 }
703
704 // Possibly store valid shift size
705 if (validShiftSize && j == k) {
706 storeShiftData(key, zeros[k], shiftedZeros[k]);
707 }
708 }
709
710 // add this scenario to the scenario vector
711 scenarios_.push_back(scenario);
713 scenario->label(to_string(scenarioDescriptions_.back()));
714 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
715
716 } // end of shift curve tenors
717 }
718 DLOG("Yield curve scenarios done");
719}
720
722 Date asof = baseScenario_->asof();
723
724 // We can choose to shift fewer yield curves than listed in the market
725 // Log an ALERT if some yield curves in simmarket are excluded from the list
726 for (auto sim : simMarketData_->equityNames()) {
727 if (sensitivityData_->dividendYieldShiftData().find(sim) == sensitivityData_->dividendYieldShiftData().end()) {
728 WLOG("Equity " << sim << " in simmarket is not included in dividend yield sensitivity analysis");
729 }
730 }
731
732 for (auto d : sensitivityData_->dividendYieldShiftData()) {
733 string name = d.first;
734 Size n_ten;
735 try {
736 n_ten = simMarketData_->equityDividendTenors(name).size();
737 } catch (const std::exception& e) {
738 ALOG("skip scenario generation for div yield " << name << ": " << e.what());
739 continue;
740 }
741 // original curves' buffer
742 std::vector<Real> zeros(n_ten);
743 std::vector<Real> times(n_ten);
744 // buffer for shifted zero curves
745 std::vector<Real> shiftedZeros(n_ten);
747 if (!isScenarioRelevant(up, data))
748 continue;
749 ShiftType shiftType = getShiftType(data);
750 DayCounter dc = Actual365Fixed();
751 try {
752 if (auto s = simMarket_.lock()) {
753 dc = s->equityDividendCurve(name)->dayCounter();
754 } else {
755 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
756 }
757 } catch (const std::exception&) {
758 WLOG("Day counter lookup in simulation market failed for dividend yield curve " << name
759 << ", using default A365");
760 }
761
762 Real quote = 0.0;
763 bool valid = true;
764 for (Size j = 0; j < n_ten; ++j) {
765 Date d = asof + simMarketData_->equityDividendTenors(name)[j];
766 times[j] = dc.yearFraction(asof, d);
768 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, quote, continueOnError_);
769 zeros[j] = -std::log(quote) / times[j];
770 }
771 if (!valid)
772 continue;
773
774 const std::vector<Period>& shiftTenors = overrideTenors_ && simMarketData_->hasEquityDividendTenors(name)
775 ? simMarketData_->equityDividendTenors(name)
776 : data.shiftTenors;
777 checkShiftTenors(shiftTenors, data.shiftTenors, "Dividend Yield " + name, continueOnError_);
778 std::vector<Time> shiftTimes(shiftTenors.size());
779 for (Size j = 0; j < shiftTenors.size(); ++j)
780 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
781 Real shiftSize = getShiftSize(data);
782 QL_REQUIRE(shiftTenors.size() > 0, "Discount shift tenors not specified");
783
784 // Can we store a valid shift size?
785 bool validShiftSize = vectorEqual(times, shiftTimes);
786
787 for (Size j = 0; j < shiftTenors.size(); ++j) {
788
789 QuantLib::ext::shared_ptr<Scenario> scenario =
790 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
791
792 // apply zero rate shift at tenor point j
793 applyShift(j, shiftSize, up, shiftType, shiftTimes, zeros, times, shiftedZeros, true);
794
795 // store shifted discount curve in the scenario
796 for (Size k = 0; k < n_ten; ++k) {
797 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
798 RiskFactorKey key(RFType::DividendYield, name, k);
799 if (sensitivityData_->useSpreadedTermStructures()) {
800 Real discount = exp(-zeros[k] * times[k]);
801 scenario->add(key, shiftedDiscount / discount);
802 } else {
803 scenario->add(key, shiftedDiscount);
804 }
805
806 // Possibly store valid shift size
807 if (validShiftSize && j == k) {
808 storeShiftData(key, zeros[k], shiftedZeros[k]);
809 }
810 }
811
812 // add this scenario to the scenario vector
813 scenarios_.push_back(scenario);
815 scenario->label(to_string(scenarioDescriptions_.back()));
816 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
817
818 } // end of shift curve tenors
819 }
820 DLOG("Dividend yield curve scenarios done");
821}
822
824 Date asof = baseScenario_->asof();
825 // We can choose to shift fewer discount curves than listed in the market
826 // Log an ALERT if some FX vol pairs in simmarket are excluded from the list
827 for (auto sim_fx : simMarketData_->fxVolCcyPairs()) {
828 if (sensitivityData_->fxVolShiftData().find(sim_fx) == sensitivityData_->fxVolShiftData().end()) {
829 WLOG("FX pair " << sim_fx << " in simmarket is not included in sensitivities analysis");
830 }
831 }
832
833 for (auto f : sensitivityData_->fxVolShiftData()) {
834 string ccyPair = f.first;
835 QL_REQUIRE(ccyPair.length() == 6, "invalid ccy pair length");
836
837 Size n_fxvol_exp;
838 try {
839 n_fxvol_exp = simMarketData_->fxVolExpiries(ccyPair).size();
840 } catch (const std::exception& e) {
841 ALOG("skip scenario generation for fx vol " << ccyPair << ": " << e.what());
842 continue;
843 }
844 std::vector<Real> times(n_fxvol_exp);
845 Size n_fxvol_strikes;
846 vector<Real> vol_strikes;
847 if (!simMarketData_->fxVolIsSurface(ccyPair)) {
848 vol_strikes = {0.0};
849 n_fxvol_strikes = 1;
850 } else if (simMarketData_->fxUseMoneyness(ccyPair)) {
851 n_fxvol_strikes = simMarketData_->fxVolMoneyness(ccyPair).size();
852 vol_strikes = simMarketData_->fxVolMoneyness(ccyPair);
853 } else {
854 n_fxvol_strikes = simMarketData_->fxVolStdDevs(ccyPair).size();
855 vol_strikes = simMarketData_->fxVolStdDevs(ccyPair);
856 }
857 vector<vector<Real>> values(n_fxvol_exp, vector<Real>(n_fxvol_strikes, 0.0));
858
859 // buffer for shifted zero curves
860 vector<vector<Real>> shiftedValues(n_fxvol_exp, vector<Real>(n_fxvol_strikes, 0.0));
861
863 if (!isScenarioRelevant(up, data))
864 continue;
865 ShiftType shiftType = getShiftType(data);
866 std::vector<Period> shiftTenors = data.shiftExpiries;
867 std::vector<Real> shiftStrikes = data.shiftStrikes;
868 std::vector<Time> shiftTimes(shiftTenors.size());
869 Real shiftSize = getShiftSize(data);
870 QL_REQUIRE(shiftTenors.size() > 0, "FX vol shift tenors not specified");
871
872 DayCounter dc = Actual365Fixed();
873 try {
874 if (auto s = simMarket_.lock()) {
875 dc = s->fxVol(ccyPair)->dayCounter();
876 } else {
877 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
878 }
879 } catch (const std::exception&) {
880 WLOG("Day counter lookup in simulation market failed for fx vol surface " << ccyPair
881 << ", using default A365");
882 }
883 bool valid = true;
884 for (Size j = 0; j < n_fxvol_exp; ++j) {
885 Date d = asof + simMarketData_->fxVolExpiries(ccyPair)[j];
886 times[j] = dc.yearFraction(asof, d);
887 for (Size k = 0; k < n_fxvol_strikes; k++) {
888 Size idx = k * n_fxvol_exp + j;
890 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, values[j][k], continueOnError_);
891 }
892 }
893 if (!valid)
894 continue;
895
896 for (Size j = 0; j < shiftTenors.size(); ++j)
897 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
898
899 // Can we store a valid shift size?
900 bool validShiftSize = vectorEqual(times, shiftTimes) && (vectorEqual(vol_strikes, shiftStrikes) ||
901 (vol_strikes.size() == 1 && shiftStrikes.size() == 1));
902
903 for (Size j = 0; j < shiftTenors.size(); ++j) {
904 for (Size strikeBucket = 0; strikeBucket < shiftStrikes.size(); ++strikeBucket) {
905 QuantLib::ext::shared_ptr<Scenario> scenario =
906 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
907
908 applyShift(j, strikeBucket, shiftSize, up, shiftType, shiftTimes, shiftStrikes, times, vol_strikes,
909 values, shiftedValues, true);
910
911 for (Size k = 0; k < n_fxvol_strikes; ++k) {
912 for (Size l = 0; l < n_fxvol_exp; ++l) {
913 Size idx = k * n_fxvol_exp + l;
914 RiskFactorKey key(RFType::FXVolatility, ccyPair, idx);
915
916 if (sensitivityData_->useSpreadedTermStructures()) {
917 scenario->add(key, shiftedValues[l][k] - values[l][k]);
918 } else {
919 scenario->add(key, shiftedValues[l][k]);
920 }
921
922 // Possibly store valid shift size
923 if (validShiftSize && j == l && strikeBucket == k) {
924 storeShiftData(key, values[l][k], shiftedValues[l][k]);
925 }
926 }
927 }
928
929 // add this scenario to the scenario vector
930 scenarios_.push_back(scenario);
931 scenarioDescriptions_.push_back(
932 fxVolScenarioDescription(ccyPair, j, strikeBucket, up, getShiftScheme(data)));
933 scenario->label(to_string(scenarioDescriptions_.back()));
934 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
935 }
936 }
937 }
938 DLOG("FX vol scenarios done");
939}
940
942 Date asof = baseScenario_->asof();
943 // We can choose to shift fewer discount curves than listed in the market
944 // Log an ALERT if an Equity in simmarket are excluded from the simulation list
945 for (auto sim_equity : simMarketData_->equityVolNames()) {
946 if (sensitivityData_->equityVolShiftData().find(sim_equity) == sensitivityData_->equityVolShiftData().end()) {
947 WLOG("Equity " << sim_equity << " in simmarket is not included in sensitivities analysis");
948 }
949 }
950
951 for (auto e : sensitivityData_->equityVolShiftData()) {
952 string equity = e.first;
954 if (!isScenarioRelevant(up, data))
955 continue;
956
957 Size n_eqvol_exp;
958 try {
959 n_eqvol_exp = simMarketData_->equityVolExpiries(equity).size();
960 } catch (const std::exception& e) {
961 ALOG("skip scenario generation for eq vol " << equity << ": " << e.what());
962 continue;
963 }
964 Size n_eqvol_strikes;
965 vector<Real> vol_strikes;
966 if (!simMarketData_->equityVolIsSurface(equity)) {
967 vol_strikes = {0.0};
968 n_eqvol_strikes = 1;
969 } else if (simMarketData_->equityUseMoneyness(equity)) {
970 vol_strikes = simMarketData_->equityVolMoneyness(equity);
971 n_eqvol_strikes = simMarketData_->equityVolMoneyness(equity).size();
972 } else {
973 vol_strikes = simMarketData_->equityVolStandardDevs(equity);
974 n_eqvol_strikes = simMarketData_->equityVolStandardDevs(equity).size();
975 }
976
977 // [strike] x [expiry]
978 vector<vector<Real>> values(n_eqvol_strikes, vector<Real>(n_eqvol_exp, 0.0));
979 vector<Real> times(n_eqvol_exp);
980
981 // buffer for shifted vols
982 vector<vector<Real>> shiftedValues(n_eqvol_strikes, vector<Real>(n_eqvol_exp, 0.0));
983
984 ShiftType shiftType = getShiftType(data);
985 vector<Period> shiftTenors = data.shiftExpiries;
986 std::vector<Real> shiftStrikes = data.shiftStrikes;
987 vector<Time> shiftTimes(shiftTenors.size());
988 Real shiftSize = getShiftSize(data);
989 QL_REQUIRE(shiftTenors.size() > 0, "Equity vol shift tenors not specified");
990 DayCounter dc = Actual365Fixed();
991 try {
992 if (auto s = simMarket_.lock()) {
993 dc = s->equityVol(equity)->dayCounter();
994 } else {
995 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
996 }
997 } catch (const std::exception&) {
998 WLOG("Day counter lookup in simulation market failed for equity vol surface " << equity
999 << ", using default A365");
1000 }
1001 bool valid = true;
1002 for (Size j = 0; j < n_eqvol_exp; ++j) {
1003 Date d = asof + simMarketData_->equityVolExpiries(equity)[j];
1004 times[j] = dc.yearFraction(asof, d);
1005 for (Size k = 0; k < n_eqvol_strikes; k++) {
1006 Size idx = k * n_eqvol_exp + j;
1008 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, values[k][j], continueOnError_);
1009 }
1010 }
1011 if (!valid)
1012 continue;
1013
1014 for (Size j = 0; j < shiftTenors.size(); ++j) {
1015 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
1016 }
1017
1018 // Can we store a valid shift size?
1019 // Will only work currently if simulation market has a single strike
1020 bool validShiftSize = vectorEqual(times, shiftTimes);
1021 validShiftSize = validShiftSize && vectorEqual(vol_strikes, shiftStrikes);
1022
1023 for (Size j = 0; j < shiftTenors.size(); ++j) {
1024 for (Size strikeBucket = 0; strikeBucket < shiftStrikes.size(); ++strikeBucket) {
1025 QuantLib::ext::shared_ptr<Scenario> scenario =
1026 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1027
1028 applyShift(strikeBucket, j, shiftSize, up, shiftType, shiftStrikes, shiftTimes, vol_strikes, times,
1029 values, shiftedValues, true);
1030
1031 // update the scenario
1032 for (Size k = 0; k < n_eqvol_strikes; ++k) {
1033 for (Size l = 0; l < n_eqvol_exp; l++) {
1034 Size idx = k * n_eqvol_exp + l;
1035 RiskFactorKey key(RFType::EquityVolatility, equity, idx);
1036
1037 if (sensitivityData_->useSpreadedTermStructures()) {
1038 scenario->add(key, shiftedValues[k][l] - values[k][l]);
1039 } else {
1040 scenario->add(key, shiftedValues[k][l]);
1041 }
1042
1043 // Possibly store valid shift size
1044 if (validShiftSize && j == l && k == strikeBucket) {
1045 storeShiftData(key, values[k][l], shiftedValues[k][l]);
1046 }
1047 }
1048 }
1049
1050 // add this scenario to the scenario vector
1051 scenarios_.push_back(scenario);
1052 scenarioDescriptions_.push_back(
1053 equityVolScenarioDescription(equity, j, strikeBucket, up, getShiftScheme(data)));
1054 scenario->label(to_string(scenarioDescriptions_.back()));
1055 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
1056 }
1057 }
1058 }
1059 DLOG("Equity vol scenarios done");
1060}
1061
1063
1064 Date asof = baseScenario_->asof();
1065
1066 // set parameters for swaption resp. yield vol scenarios
1067
1068 bool atmOnly;
1069 map<string, SensitivityScenarioData::GenericYieldVolShiftData> shiftData;
1070 function<Size(string)> get_n_term;
1071 function<Size(string)> get_n_expiry;
1072 function<vector<Real>(string)> getVolStrikes;
1073 function<vector<Period>(string)> getVolExpiries;
1074 function<vector<Period>(string)> getVolTerms;
1075 function<string(string)> getDayCounter;
1076 function<ScenarioDescription(string, Size, Size, Size, bool, SensitivityScenarioData::ShiftData&)>
1077 getScenarioDescription;
1078
1079 if (rfType == RFType::SwaptionVolatility) {
1080 atmOnly = simMarketData_->simulateSwapVolATMOnly();
1081 shiftData = sensitivityData_->swaptionVolShiftData();
1082 get_n_term = [this](const string& k) { return simMarketData_->swapVolTerms(k).size(); };
1083 get_n_expiry = [this](const string& k) { return simMarketData_->swapVolExpiries(k).size(); };
1084 getVolStrikes = [this](const string& k) { return simMarketData_->swapVolStrikeSpreads(k); };
1085 getVolExpiries = [this](const string& k) { return simMarketData_->swapVolExpiries(k); };
1086 getVolTerms = [this](const string& k) { return simMarketData_->swapVolTerms(k); };
1087 getDayCounter = [this](const string& k) {
1088 try {
1089 if (auto s = simMarket_.lock()) {
1090 return to_string(s->swaptionVol(k)->dayCounter());
1091 } else {
1092 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1093 }
1094 } catch (const std::exception&) {
1095 WLOG("Day counter lookup in simulation market failed for swaption vol '" << k
1096 << "', using default A365");
1097 return std::string("A365F");
1098 }
1099 };
1100 getScenarioDescription = [this](string q, Size n, Size m, Size k, bool up,
1102 return swaptionVolScenarioDescription(q, n, m, k, up, getShiftScheme(data));
1103 };
1104 } else if (rfType == RFType::YieldVolatility) {
1105 atmOnly = true;
1106 shiftData = sensitivityData_->yieldVolShiftData();
1107 get_n_term = [this](const string& k) { return simMarketData_->yieldVolTerms().size(); };
1108 get_n_expiry = [this](const string& k) { return simMarketData_->yieldVolExpiries().size(); };
1109 getVolStrikes = [](const string& k) { return vector<Real>({0.0}); };
1110 getVolExpiries = [this](const string& k) { return simMarketData_->yieldVolExpiries(); };
1111 getVolTerms = [this](const string& k) { return simMarketData_->yieldVolTerms(); };
1112 getDayCounter = [this](const string& k) {
1113 try {
1114 if (auto s = simMarket_.lock()) {
1115 return to_string(s->yieldVol(k)->dayCounter());
1116 } else {
1117 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1118 }
1119 } catch (const std::exception&) {
1120 WLOG("Day counter lookup in simulation market failed for swaption vol '" << k
1121 << "', using default A365");
1122 return std::string("A365F");
1123 }
1124 };
1125 getScenarioDescription = [this](string q, Size n, Size m, Size k, bool up,
1127 return yieldVolScenarioDescription(q, n, m, up, getShiftScheme(data));
1128 };
1129 } else {
1130 QL_FAIL("SensitivityScenarioGenerator::generateGenericYieldVolScenarios: risk factor type " << rfType
1131 << " not handled.");
1132 }
1133
1134 // generate scenarios
1135 for (auto s : shiftData) {
1136 std::string qualifier = s.first;
1137
1138 Size n_term;
1139 try {
1140 n_term = get_n_term(qualifier);
1141 } catch (const std::exception& e) {
1142 ALOG("skip scenario generation for general yield vol " << qualifier << ": " << e.what());
1143 continue;
1144 }
1145 Size n_expiry = get_n_expiry(qualifier);
1146
1147 vector<Real> volExpiryTimes(n_expiry, 0.0);
1148 vector<Real> volTermTimes(n_term, 0.0);
1149 Size n_strike = getVolStrikes(qualifier).size();
1150
1151 vector<vector<vector<Real>>> volData(n_strike, vector<vector<Real>>(n_expiry, vector<Real>(n_term, 0.0)));
1152 vector<vector<vector<Real>>> shiftedVolData = volData;
1153
1155 if (!isScenarioRelevant(up, data))
1156 continue;
1157 ShiftType shiftType = getShiftType(data);
1158 Real shiftSize = getShiftSize(data);
1159
1160 vector<Real> shiftExpiryTimes(data.shiftExpiries.size(), 0.0);
1161 vector<Real> shiftTermTimes(data.shiftTerms.size(), 0.0);
1162
1163 vector<Real> shiftStrikes;
1164 if (!atmOnly) {
1165 shiftStrikes = data.shiftStrikes;
1166 QL_REQUIRE(data.shiftStrikes.size() == n_strike,
1167 "number of simulated strikes must equal number of sensitivity strikes");
1168 } else {
1169 shiftStrikes = {0.0};
1170 }
1171
1172 DayCounter dc = parseDayCounter(getDayCounter(qualifier));
1173
1174 // cache original vol data
1175 for (Size j = 0; j < n_expiry; ++j) {
1176 Date expiry = asof + getVolExpiries(qualifier)[j];
1177 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
1178 }
1179 for (Size j = 0; j < n_term; ++j) {
1180 Date term = asof + getVolTerms(qualifier)[j];
1181 volTermTimes[j] = dc.yearFraction(asof, term);
1182 }
1183
1184 bool valid = true;
1185 for (Size j = 0; j < n_expiry; ++j) {
1186 for (Size k = 0; k < n_term; ++k) {
1187 for (Size l = 0; l < n_strike; ++l) {
1188 Size idx = j * n_term * n_strike + k * n_strike + l;
1189 RiskFactorKey key(rfType, qualifier, idx);
1190 valid = valid &&
1191 tryGetBaseScenarioValue(baseScenarioAbsolute_, key, volData[l][j][k], continueOnError_);
1192 }
1193 }
1194 }
1195 if (!valid)
1196 continue;
1197
1198 // cache tenor times
1199 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
1200 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + data.shiftExpiries[j]);
1201 for (Size j = 0; j < shiftTermTimes.size(); ++j)
1202 shiftTermTimes[j] = dc.yearFraction(asof, asof + data.shiftTerms[j]);
1203
1204 // Can we store a valid shift size?
1205 bool validShiftSize = vectorEqual(volExpiryTimes, shiftExpiryTimes);
1206 validShiftSize = validShiftSize && vectorEqual(volTermTimes, shiftTermTimes);
1207 validShiftSize = validShiftSize && vectorEqual(getVolStrikes(qualifier), shiftStrikes);
1208
1209 // loop over shift expiries, terms and strikes
1210 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
1211 for (Size k = 0; k < shiftTermTimes.size(); ++k) {
1212 for (Size l = 0; l < shiftStrikes.size(); ++l) {
1213 Size strikeBucket = l;
1214 QuantLib::ext::shared_ptr<Scenario> scenario =
1215 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1216
1217 // if simulating atm only we shift all strikes otherwise we shift each strike individually
1218 Size loopStart = atmOnly ? 0 : l;
1219 Size loopEnd = atmOnly ? n_strike : loopStart + 1;
1220
1221 for (Size ll = loopStart; ll < loopEnd; ++ll) {
1222 applyShift(j, k, shiftSize, up, shiftType, shiftExpiryTimes, shiftTermTimes, volExpiryTimes,
1223 volTermTimes, volData[ll], shiftedVolData[ll], true);
1224 }
1225
1226 for (Size jj = 0; jj < n_expiry; ++jj) {
1227 for (Size kk = 0; kk < n_term; ++kk) {
1228 for (Size ll = 0; ll < n_strike; ++ll) {
1229
1230 Size idx = jj * n_term * n_strike + kk * n_strike + ll;
1231 RiskFactorKey key(rfType, qualifier, idx);
1232
1233 if (ll >= loopStart && ll < loopEnd) {
1234 if (sensitivityData_->useSpreadedTermStructures()) {
1235 scenario->add(key, shiftedVolData[ll][jj][kk] - volData[ll][jj][kk]);
1236 } else {
1237 scenario->add(key, shiftedVolData[ll][jj][kk]);
1238 }
1239 }
1240
1241 // Possibly store valid shift size
1242 if (validShiftSize && j == jj && k == kk && l == ll) {
1243 storeShiftData(key, volData[ll][jj][kk], shiftedVolData[ll][jj][kk]);
1244 }
1245 }
1246 }
1247 }
1248
1249 // add this scenario to the scenario vector
1250 scenarios_.push_back(scenario);
1251 scenarioDescriptions_.push_back(getScenarioDescription(qualifier, j, k, strikeBucket, up, data));
1252 scenario->label(to_string(scenarioDescriptions_.back()));
1253 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
1254 << " created for generic yield vol " << qualifier);
1255 }
1256 }
1257 }
1258 }
1259}
1260
1262 DLOG("starting swapVol sgen");
1263 // We can choose to shift fewer discount curves than listed in the market
1264 // Log an ALERT if some swaption currencies in simmarket are excluded from the list
1265 for (auto sim_key : simMarketData_->swapVolKeys()) {
1266 if (sensitivityData_->swaptionVolShiftData().find(sim_key) == sensitivityData_->swaptionVolShiftData().end()) {
1267 WLOG("Swaption key " << sim_key << " in simmarket is not included in sensitivities analysis");
1268 }
1269 }
1270 generateGenericYieldVolScenarios(up, RFType::SwaptionVolatility);
1271 DLOG("Swaption vol scenarios done");
1272}
1273
1275 DLOG("starting yieldVol sgen");
1276 // We can choose to shift fewer discount curves than listed in the market
1277 // Log an ALERT if some bond securityId in simmarket are excluded from the list
1278 for (auto sim_securityId : simMarketData_->yieldVolNames()) {
1279 if (sensitivityData_->yieldVolShiftData().find(sim_securityId) == sensitivityData_->yieldVolShiftData().end()) {
1280 WLOG("Bond securityId " << sim_securityId << " in simmarket is not included in sensitivities analysis");
1281 }
1282 }
1283 generateGenericYieldVolScenarios(up, RFType::YieldVolatility);
1284 DLOG("Yield vol scenarios done");
1285}
1286
1288 Date asof = baseScenario_->asof();
1289
1290 // Log an ALERT if some cap currencies in simmarket are excluded from the list
1291 for (auto sim_cap : simMarketData_->capFloorVolKeys()) {
1292 if (sensitivityData_->capFloorVolShiftData().find(sim_cap) == sensitivityData_->capFloorVolShiftData().end()) {
1293 WLOG("CapFloor key " << sim_cap << " in simmarket is not included in sensitivities analysis");
1294 }
1295 }
1296
1297 for (auto c : sensitivityData_->capFloorVolShiftData()) {
1298 std::string key = c.first;
1299
1300 vector<Real> volStrikes;
1301 try {
1302 volStrikes = simMarketData_->capFloorVolStrikes(key);
1303 } catch (const std::exception& e) {
1304 ALOG("skip scenario generation for cf vol " << key << ": " << e.what());
1305 continue;
1306 }
1307 // Strikes may be empty which indicates that the optionlet structure in the simulation market is an ATM curve
1308 if (volStrikes.empty()) {
1309 volStrikes = {0.0};
1310 }
1311 Size n_cfvol_strikes = volStrikes.size();
1312
1313 Size n_cfvol_exp = simMarketData_->capFloorVolExpiries(key).size();
1315 if (!isScenarioRelevant(up, data))
1316 continue;
1317 ShiftType shiftType = getShiftType(data);
1318 Real shiftSize = getShiftSize(data);
1319 vector<vector<Real>> volData(n_cfvol_exp, vector<Real>(n_cfvol_strikes, 0.0));
1320 vector<Real> volExpiryTimes(n_cfvol_exp, 0.0);
1321 vector<vector<Real>> shiftedVolData(n_cfvol_exp, vector<Real>(n_cfvol_strikes, 0.0));
1322
1323 std::vector<Period> expiries = overrideTenors_ && simMarketData_->hasCapFloorVolExpiries(key)
1324 ? simMarketData_->capFloorVolExpiries(key)
1325 : data.shiftExpiries;
1326 QL_REQUIRE(expiries.size() == data.shiftExpiries.size(), "mismatch between effective shift expiries ("
1327 << expiries.size() << ") and shift tenors ("
1328 << data.shiftExpiries.size());
1329 vector<Real> shiftExpiryTimes(expiries.size(), 0.0);
1330 vector<Real> shiftStrikes = data.shiftStrikes;
1331 // Has an ATM shift been configured?
1332 bool sensiIsAtm = false;
1333 if (shiftStrikes.size() == 1 && shiftStrikes[0] == 0.0 && data.isRelative) {
1334 sensiIsAtm = true;
1335 }
1336
1337 DayCounter dc = Actual365Fixed();
1338 try {
1339 if (auto s = simMarket_.lock()) {
1340 dc = s->capFloorVol(key)->dayCounter();
1341 } else {
1342 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1343 }
1344 } catch (const std::exception&) {
1345 WLOG("Day counter lookup in simulation market failed for cap/floor vol surface " << key
1346 << ", using default A365");
1347 }
1348
1349 // cache original vol data
1350 for (Size j = 0; j < n_cfvol_exp; ++j) {
1351 Date expiry = asof + simMarketData_->capFloorVolExpiries(key)[j];
1352 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
1353 }
1354 bool valid = true;
1355 for (Size j = 0; j < n_cfvol_exp; ++j) {
1356 for (Size k = 0; k < n_cfvol_strikes; ++k) {
1357 Size idx = j * n_cfvol_strikes + k;
1358 valid = valid &&
1359 tryGetBaseScenarioValue(baseScenarioAbsolute_,
1361 volData[j][k], continueOnError_);
1362 }
1363 }
1364 if (!valid)
1365 continue;
1366
1367 // cache tenor times
1368 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
1369 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + expiries[j]);
1370
1371 // Can we store a valid shift size?
1372 bool validShiftSize = vectorEqual(volExpiryTimes, shiftExpiryTimes);
1373 validShiftSize = validShiftSize && vectorEqual(volStrikes, shiftStrikes);
1374
1375 // loop over shift expiries and terms
1376 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
1377 for (Size k = 0; k < shiftStrikes.size(); ++k) {
1378 QuantLib::ext::shared_ptr<Scenario> scenario =
1379 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1380
1381 applyShift(j, k, shiftSize, up, shiftType, shiftExpiryTimes, shiftStrikes, volExpiryTimes, volStrikes,
1382 volData, shiftedVolData, true);
1383
1384 // add shifted vol data to the scenario
1385 for (Size jj = 0; jj < n_cfvol_exp; ++jj) {
1386 for (Size kk = 0; kk < n_cfvol_strikes; ++kk) {
1387 Size idx = jj * n_cfvol_strikes + kk;
1388 RiskFactorKey rfkey(RFType::OptionletVolatility, key, idx);
1389
1390 if (sensitivityData_->useSpreadedTermStructures()) {
1391 scenario->add(rfkey, shiftedVolData[jj][kk] - volData[jj][kk]);
1392 } else {
1393 scenario->add(rfkey, shiftedVolData[jj][kk]);
1394 }
1395
1396 // Possibly store valid shift size
1397 if (validShiftSize && j == jj && k == kk) {
1398 storeShiftData(rfkey, volData[jj][kk], shiftedVolData[jj][kk]);
1399 }
1400 }
1401 }
1402
1403 // Give the scenario a label
1404
1405 scenarios_.push_back(scenario);
1406 scenarioDescriptions_.push_back(
1407 capFloorVolScenarioDescription(key, j, k, up, sensiIsAtm, getShiftScheme(data)));
1408 scenario->label(to_string(scenarioDescriptions_.back()));
1409 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
1410 }
1411 }
1412 }
1413 DLOG("Optionlet vol scenarios done");
1414}
1415
1417 Date asof = baseScenario_->asof();
1418 // We can choose to shift fewer credit curves than listed in the market
1419 // Log an ALERT if some names in simmarket are excluded from the list
1420 for (auto sim_name : simMarketData_->defaultNames()) {
1421 if (sensitivityData_->creditCurveShiftData().find(sim_name) == sensitivityData_->creditCurveShiftData().end()) {
1422 WLOG("Credit Name " << sim_name << " in simmarket is not included in sensitivities analysis");
1423 }
1424 }
1425 Size n_ten;
1426
1427 // original curves' buffer
1428 std::vector<Real> times;
1429
1430 for (auto c : sensitivityData_->creditCurveShiftData()) {
1431 string name = c.first;
1432 try {
1433 n_ten = simMarketData_->defaultTenors(name).size();
1434 } catch (const std::exception& e) {
1435 ALOG("skip scenario generation for survival curve " << name << ": " << e.what());
1436 continue;
1437 }
1438 std::vector<Real> hazardRates(n_ten); // integrated hazard rates
1439 times.clear();
1440 times.resize(n_ten);
1441 // buffer for shifted survival prob curves
1442 std::vector<Real> shiftedHazardRates(n_ten);
1444 if (!isScenarioRelevant(up, data))
1445 continue;
1446 ShiftType shiftType = getShiftType(data);
1447 DayCounter dc = Actual365Fixed();
1448 try {
1449 if (auto s = simMarket_.lock()) {
1450 dc = s->defaultCurve(name)->curve()->dayCounter();
1451 } else {
1452 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1453 }
1454 } catch (const std::exception&) {
1455 WLOG("Day counter lookup in simulation market failed for default curve " << name << ", using default A365");
1456 }
1457 Calendar calendar = parseCalendar(simMarketData_->defaultCurveCalendar(name));
1458
1459 Real prob = 0.0;
1460 bool valid = true;
1461 for (Size j = 0; j < n_ten; ++j) {
1462 Date d = asof + simMarketData_->defaultTenors(name)[j];
1463 times[j] = dc.yearFraction(asof, d);
1465 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, prob, continueOnError_);
1466 // ensure we have a valid value, if prob = 0 we need to avoid nan to generate valid scenarios
1467 hazardRates[j] = -std::log(std::max(prob, 1E-8)) / times[j];
1468 }
1469 if (!valid)
1470 continue;
1471
1472 std::vector<Period> shiftTenors = overrideTenors_ && simMarketData_->hasDefaultTenors(name)
1473 ? simMarketData_->defaultTenors(name)
1474 : data.shiftTenors;
1475
1476 checkShiftTenors(shiftTenors, data.shiftTenors, "Default Curve " + name, continueOnError_);
1477
1478 std::vector<Time> shiftTimes(shiftTenors.size());
1479 for (Size j = 0; j < shiftTenors.size(); ++j)
1480 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
1481 Real shiftSize = getShiftSize(data);
1482 QL_REQUIRE(shiftTenors.size() > 0, "Discount shift tenors not specified");
1483
1484 // Can we store a valid shift size?
1485 bool validShiftSize = vectorEqual(times, shiftTimes);
1486
1487 for (Size j = 0; j < shiftTenors.size(); ++j) {
1488
1489 QuantLib::ext::shared_ptr<Scenario> scenario =
1490 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1491
1492 // apply averaged hazard rate shift at tenor point j
1493 applyShift(j, shiftSize, up, shiftType, shiftTimes, hazardRates, times, shiftedHazardRates, true);
1494
1495 // store shifted survival Prob in the scenario
1496 for (Size k = 0; k < n_ten; ++k) {
1497 RiskFactorKey key(RFType::SurvivalProbability, name, k);
1498 Real shiftedProb = exp(-shiftedHazardRates[k] * times[k]);
1499 if (sensitivityData_->useSpreadedTermStructures()) {
1500 Real prob = exp(-hazardRates[k] * times[k]);
1501 scenario->add(key, shiftedProb / prob);
1502 } else {
1503 scenario->add(key, shiftedProb);
1504 }
1505
1506 // Possibly store valid shift size
1507 if (validShiftSize && k == j) {
1508 storeShiftData(key, hazardRates[k], shiftedHazardRates[k]);
1509 }
1510 }
1511
1512 // add this scenario to the scenario vector
1513 scenarios_.push_back(scenario);
1515 scenario->label(to_string(scenarioDescriptions_.back()));
1516 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
1517
1518 } // end of shift curve tenors
1519 }
1520 DLOG("Discount curve scenarios done");
1521}
1522
1524 Date asof = baseScenario_->asof();
1525 // We can choose to shift fewer discount curves than listed in the market
1526 // Log an ALERT if some swaption currencies in simmarket are excluded from the list
1527 for (auto sim_name : simMarketData_->cdsVolNames()) {
1528 if (sensitivityData_->cdsVolShiftData().find(sim_name) == sensitivityData_->cdsVolShiftData().end()) {
1529 WLOG("CDS name " << sim_name << " in simmarket is not included in sensitivities analysis");
1530 }
1531 }
1532
1533 Size n_cdsvol_exp = simMarketData_->cdsVolExpiries().size();
1534
1535 vector<Real> volData(n_cdsvol_exp, 0.0);
1536 vector<Real> volExpiryTimes(n_cdsvol_exp, 0.0);
1537 vector<Real> shiftedVolData(n_cdsvol_exp, 0.0);
1538
1539 for (auto c : sensitivityData_->cdsVolShiftData()) {
1540 std::string name = c.first;
1542 if (!isScenarioRelevant(up, data))
1543 continue;
1544 ShiftType shiftType = getShiftType(data);
1545 Real shiftSize = getShiftSize(data);
1546
1547 vector<Time> shiftExpiryTimes(data.shiftExpiries.size(), 0.0);
1548
1549 DayCounter dc = Actual365Fixed();
1550 try {
1551 if (auto s = simMarket_.lock()) {
1552 dc = s->cdsVol(name)->dayCounter();
1553 } else {
1554 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1555 }
1556 } catch (const std::exception&) {
1557 WLOG("Day counter lookup in simulation market failed for cds vol surface " << name
1558 << ", using default A365");
1559 }
1560
1561 // cache original vol data
1562 for (Size j = 0; j < n_cdsvol_exp; ++j) {
1563 Date expiry = asof + simMarketData_->cdsVolExpiries()[j];
1564 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
1565 }
1566 bool valid = true;
1567 for (Size j = 0; j < n_cdsvol_exp; ++j) {
1569 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, volData[j], continueOnError_);
1570 }
1571 if (!valid)
1572 continue;
1573
1574 // cache tenor times
1575 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
1576 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + data.shiftExpiries[j]);
1577
1578 // Can we store a valid shift size?
1579 bool validShiftSize = vectorEqual(volExpiryTimes, shiftExpiryTimes);
1580
1581 // loop over shift expiries and terms
1582 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
1583 Size strikeBucket = 0; // FIXME
1584 QuantLib::ext::shared_ptr<Scenario> scenario =
1585 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1586
1587 applyShift(j, shiftSize, up, shiftType, shiftExpiryTimes, volData, volExpiryTimes, shiftedVolData, true);
1588 // add shifted vol data to the scenario
1589 for (Size jj = 0; jj < n_cdsvol_exp; ++jj) {
1590 RiskFactorKey key(RFType::CDSVolatility, name, jj);
1591 if (sensitivityData_->useSpreadedTermStructures()) {
1592 scenario->add(key, shiftedVolData[jj] - volData[jj]);
1593 } else {
1594 scenario->add(key, shiftedVolData[jj]);
1595 }
1596
1597 // Possibly store valid shift size
1598 if (validShiftSize && j == jj) {
1599 storeShiftData(key, volData[jj], shiftedVolData[jj]);
1600 }
1601 }
1602
1603 // add this scenario to the scenario vector
1604 scenarios_.push_back(scenario);
1605 scenarioDescriptions_.push_back(CdsVolScenarioDescription(name, j, strikeBucket, up, getShiftScheme(data)));
1606 scenario->label(to_string(scenarioDescriptions_.back()));
1607 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
1608 }
1609 }
1610 DLOG("CDS vol scenarios done");
1611}
1612
1614 Date asof = baseScenario_->asof();
1615 // We can choose to shift fewer discount curves than listed in the market
1616 // Log an ALERT if some ibor indices in simmarket are excluded from the list
1617 for (auto sim_idx : simMarketData_->zeroInflationIndices()) {
1618 if (sensitivityData_->zeroInflationCurveShiftData().find(sim_idx) ==
1619 sensitivityData_->zeroInflationCurveShiftData().end()) {
1620 WLOG("Zero Inflation Index " << sim_idx << " in simmarket is not included in sensitivities analysis");
1621 }
1622 }
1623
1624 for (auto z : sensitivityData_->zeroInflationCurveShiftData()) {
1625 string indexName = z.first;
1626 Size n_ten;
1627 try {
1628 n_ten = simMarketData_->zeroInflationTenors(indexName).size();
1629 } catch (const std::exception& e) {
1630 ALOG("skip scenario generation for zero inflation curve " << indexName << ": " << e.what());
1631 continue;
1632 }
1633 // original curves' buffer
1634 std::vector<Real> zeros(n_ten);
1635 std::vector<Real> times(n_ten);
1636 // buffer for shifted zero curves
1637 std::vector<Real> shiftedZeros(n_ten);
1639 if (!isScenarioRelevant(up, data))
1640 continue;
1641 ShiftType shiftType = getShiftType(data);
1642 DayCounter dc = Actual365Fixed();
1643 try {
1644 if (auto s = simMarket_.lock()) {
1645 dc = s->zeroInflationIndex(indexName)->zeroInflationTermStructure()->dayCounter();
1646 } else {
1647 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1648 }
1649 } catch (const std::exception&) {
1650 WLOG("Day counter lookup in simulation market failed for zero inflation index " << indexName
1651 << ", using default A365");
1652 }
1653 bool valid = true;
1654 for (Size j = 0; j < n_ten; ++j) {
1655 Date d = asof + simMarketData_->zeroInflationTenors(indexName)[j];
1657 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, zeros[j], continueOnError_);
1658 times[j] = dc.yearFraction(asof, d);
1659 }
1660 if (!valid)
1661 continue;
1662
1663 std::vector<Period> shiftTenors = overrideTenors_ && simMarketData_->hasZeroInflationTenors(indexName)
1664 ? simMarketData_->zeroInflationTenors(indexName)
1665 : data.shiftTenors;
1666 checkShiftTenors(shiftTenors, data.shiftTenors, "Zero Inflation " + indexName, continueOnError_);
1667 std::vector<Time> shiftTimes(shiftTenors.size());
1668 for (Size j = 0; j < shiftTenors.size(); ++j)
1669 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
1670 Real shiftSize = getShiftSize(data);
1671 QL_REQUIRE(shiftTenors.size() > 0, "Zero Inflation Index shift tenors not specified");
1672
1673 // Can we store a valid shift size?
1674 bool validShiftSize = vectorEqual(times, shiftTimes);
1675
1676 for (Size j = 0; j < shiftTenors.size(); ++j) {
1677
1678 QuantLib::ext::shared_ptr<Scenario> scenario =
1679 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1680
1681 // apply zero rate shift at tenor point j
1682 applyShift(j, shiftSize, up, shiftType, shiftTimes, zeros, times, shiftedZeros, true);
1683
1684 // store shifted discount curve for this index in the scenario
1685 for (Size k = 0; k < n_ten; ++k) {
1686 RiskFactorKey key(RFType::ZeroInflationCurve, indexName, k);
1687 if (sensitivityData_->useSpreadedTermStructures()) {
1688 scenario->add(key, shiftedZeros[k] - zeros[k]);
1689 } else {
1690 scenario->add(key, shiftedZeros[k]);
1691 }
1692
1693 // Possibly store valid shift size
1694 if (validShiftSize && j == k) {
1695 storeShiftData(key, zeros[k], shiftedZeros[k]);
1696 }
1697 }
1698
1699 // add this scenario to the scenario vector
1700 scenarios_.push_back(scenario);
1702 scenario->label(to_string(scenarioDescriptions_.back()));
1703 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
1704 << " created for indexName " << indexName);
1705
1706 } // end of shift curve tenors
1707 }
1708 DLOG("Zero Inflation Index curve scenarios done");
1709}
1710
1712 Date asof = baseScenario_->asof();
1713
1714 // We can choose to shift fewer discount curves than listed in the market
1715 std::vector<string> yoyInfIndexNames;
1716 // Log an ALERT if some ibor indices in simmarket are excluded from the list
1717 for (auto sim_idx : simMarketData_->yoyInflationIndices()) {
1718 if (sensitivityData_->yoyInflationCurveShiftData().find(sim_idx) ==
1719 sensitivityData_->yoyInflationCurveShiftData().end()) {
1720 WLOG("YoY Inflation Index " << sim_idx << " in simmarket is not included in sensitivities analysis");
1721 }
1722 }
1723
1724 for (auto y : sensitivityData_->yoyInflationCurveShiftData()) {
1725 string indexName = y.first;
1726 Size n_ten;
1727 try {
1728 n_ten = simMarketData_->yoyInflationTenors(indexName).size();
1729 } catch (const std::exception& e) {
1730 ALOG("skip scenario generation for yoy inflation curve " << indexName << ": " << e.what());
1731 continue;
1732 }
1733 // original curves' buffer
1734 std::vector<Real> yoys(n_ten);
1735 std::vector<Real> times(n_ten);
1736 // buffer for shifted zero curves
1737 std::vector<Real> shiftedYoys(n_ten);
1738 auto itr = sensitivityData_->yoyInflationCurveShiftData().find(indexName);
1739 QL_REQUIRE(itr != sensitivityData_->yoyInflationCurveShiftData().end(),
1740 "yoyinflation CurveShiftData not found for " << indexName);
1742 if (!isScenarioRelevant(up, data))
1743 continue;
1744 ShiftType shiftType = getShiftType(data);
1745 DayCounter dc = Actual365Fixed();
1746 try {
1747 if (auto s = simMarket_.lock()) {
1748 dc = s->yoyInflationIndex(indexName)->yoyInflationTermStructure()->dayCounter();
1749 } else {
1750 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1751 }
1752 } catch (const std::exception&) {
1753 WLOG("Day counter lookup in simulation market failed for yoy inflation index " << indexName
1754 << ", using default A365");
1755 }
1756 bool valid = true;
1757 for (Size j = 0; j < n_ten; ++j) {
1758 Date d = asof + simMarketData_->yoyInflationTenors(indexName)[j];
1760 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, yoys[j], continueOnError_);
1761 times[j] = dc.yearFraction(asof, d);
1762 }
1763 if (!valid)
1764 continue;
1765
1766 std::vector<Period> shiftTenors = overrideTenors_ && simMarketData_->hasYoyInflationTenors(indexName)
1767 ? simMarketData_->yoyInflationTenors(indexName)
1768 : data.shiftTenors;
1769 checkShiftTenors(shiftTenors, data.shiftTenors, "YoY Inflation " + indexName, continueOnError_);
1770 std::vector<Time> shiftTimes(shiftTenors.size());
1771 for (Size j = 0; j < shiftTenors.size(); ++j) {
1772 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
1773 }
1774 Real shiftSize = getShiftSize(data);
1775 QL_REQUIRE(shiftTenors.size() > 0, "YoY Inflation Index shift tenors not specified");
1776
1777 // Can we store a valid shift size?
1778 bool validShiftSize = vectorEqual(times, shiftTimes);
1779
1780 for (Size j = 0; j < shiftTenors.size(); ++j) {
1781
1782 QuantLib::ext::shared_ptr<Scenario> scenario =
1783 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1784
1785 // apply zero rate shift at tenor point j
1786 applyShift(j, shiftSize, up, shiftType, shiftTimes, yoys, times, shiftedYoys, true);
1787
1788 // store shifted discount curve for this index in the scenario
1789 for (Size k = 0; k < n_ten; ++k) {
1790 RiskFactorKey key(RFType::YoYInflationCurve, indexName, k);
1791 if (sensitivityData_->useSpreadedTermStructures()) {
1792 scenario->add(key, shiftedYoys[k] - yoys[k]);
1793 } else {
1794 scenario->add(key, shiftedYoys[k]);
1795 }
1796
1797 // Possibly store valid shift size
1798 if (validShiftSize && j == k) {
1799 storeShiftData(key, yoys[k], shiftedYoys[k]);
1800 }
1801 }
1802
1803 // add this scenario to the scenario vector
1804 scenarios_.push_back(scenario);
1806 scenario->label(to_string(scenarioDescriptions_.back()));
1807 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
1808 << " created for indexName " << indexName);
1809
1810 } // end of shift curve tenors
1811 }
1812 DLOG("YoY Inflation Index curve scenarios done");
1813}
1814
1816 Date asof = baseScenario_->asof();
1817 for (auto sim_yoy : simMarketData_->yoyInflationCapFloorVolNames()) {
1818 if (sensitivityData_->yoyInflationCapFloorVolShiftData().find(sim_yoy) ==
1819 sensitivityData_->yoyInflationCapFloorVolShiftData().end()) {
1820 WLOG("Inflation index " << sim_yoy << " in simmarket is not included in sensitivities analysis");
1821 }
1822 }
1823
1824 for (auto c : sensitivityData_->yoyInflationCapFloorVolShiftData()) {
1825 std::string name = c.first;
1826 Size n_yoyvol_strikes;
1827 try {
1828 n_yoyvol_strikes = simMarketData_->yoyInflationCapFloorVolStrikes(name).size();
1829 } catch (const std::exception& e) {
1830 ALOG("skip scenario generation for yoy inflation cf vol " << name << ": " << e.what());
1831 continue;
1832 }
1833 vector<Real> volStrikes = simMarketData_->yoyInflationCapFloorVolStrikes(name);
1834 Size n_yoyvol_exp = simMarketData_->yoyInflationCapFloorVolExpiries(name).size();
1836 if (!isScenarioRelevant(up, data))
1837 continue;
1838 ShiftType shiftType = getShiftType(data);
1839 Real shiftSize = getShiftSize(data);
1840 vector<vector<Real>> volData(n_yoyvol_exp, vector<Real>(n_yoyvol_strikes, 0.0));
1841 vector<Real> volExpiryTimes(n_yoyvol_exp, 0.0);
1842 vector<vector<Real>> shiftedVolData(n_yoyvol_exp, vector<Real>(n_yoyvol_strikes, 0.0));
1843
1844 std::vector<Period> expiries = overrideTenors_ && simMarketData_->hasYoYInflationCapFloorVolExpiries(name)
1845 ? simMarketData_->yoyInflationCapFloorVolExpiries(name)
1846 : data.shiftExpiries;
1847 QL_REQUIRE(expiries.size() == data.shiftExpiries.size(), "mismatch between effective shift expiries ("
1848 << expiries.size() << ") and shift tenors ("
1849 << data.shiftExpiries.size());
1850 vector<Real> shiftExpiryTimes(expiries.size(), 0.0);
1851 vector<Real> shiftStrikes = data.shiftStrikes;
1852
1853 DayCounter dc = Actual365Fixed();
1854 try {
1855 if (auto s = simMarket_.lock()) {
1856 dc = s->yoyCapFloorVol(name)->dayCounter();
1857 } else {
1858 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1859 }
1860 } catch (const std::exception&) {
1861 WLOG("Day counter lookup in simulation market failed for yoy cap/floor vol surface "
1862 << name << ", using default A365");
1863 }
1864
1865 // cache original vol data
1866 for (Size j = 0; j < n_yoyvol_exp; ++j) {
1867 Date expiry = asof + simMarketData_->yoyInflationCapFloorVolExpiries(name)[j];
1868 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
1869 }
1870 bool valid = true;
1871 for (Size j = 0; j < n_yoyvol_exp; ++j) {
1872 for (Size k = 0; k < n_yoyvol_strikes; ++k) {
1873 Size idx = j * n_yoyvol_strikes + k;
1875 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, volData[j][k], continueOnError_);
1876 }
1877 }
1878 if (!valid)
1879 continue;
1880
1881 // cache tenor times
1882 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
1883 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + expiries[j]);
1884
1885 bool validShiftSize = vectorEqual(volExpiryTimes, shiftExpiryTimes);
1886 validShiftSize = validShiftSize && vectorEqual(volStrikes, shiftStrikes);
1887
1888 // loop over shift expiries and terms
1889 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
1890 for (Size k = 0; k < shiftStrikes.size(); ++k) {
1891 QuantLib::ext::shared_ptr<Scenario> scenario =
1892 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
1893
1894 applyShift(j, k, shiftSize, up, shiftType, shiftExpiryTimes, shiftStrikes, volExpiryTimes, volStrikes,
1895 volData, shiftedVolData, true);
1896
1897 // add shifted vol data to the scenario
1898 for (Size jj = 0; jj < n_yoyvol_exp; ++jj) {
1899 for (Size kk = 0; kk < n_yoyvol_strikes; ++kk) {
1900 Size idx = jj * n_yoyvol_strikes + kk;
1901 RiskFactorKey key(RFType::YoYInflationCapFloorVolatility, name, idx);
1902 if (sensitivityData_->useSpreadedTermStructures()) {
1903 scenario->add(key, shiftedVolData[jj][kk] - volData[jj][kk]);
1904 } else {
1905 scenario->add(key, shiftedVolData[jj][kk]);
1906 }
1907
1908 // Possibly store valid shift size
1909 if (validShiftSize && j == jj && k == kk) {
1910 storeShiftData(key, volData[jj][kk], shiftedVolData[jj][kk]);
1911 }
1912 }
1913 }
1914
1915 // add this scenario to the scenario vector
1916 scenarios_.push_back(scenario);
1917 scenarioDescriptions_.push_back(
1919 scenario->label(to_string(scenarioDescriptions_.back()));
1920 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
1921 }
1922 }
1923 }
1924 DLOG("YoY inflation optionlet vol scenarios done");
1925}
1926
1928 Date asof = baseScenario_->asof();
1929 for (auto sim_zci : simMarketData_->zeroInflationCapFloorVolNames()) {
1930 if (sensitivityData_->zeroInflationCapFloorVolShiftData().find(sim_zci) ==
1931 sensitivityData_->zeroInflationCapFloorVolShiftData().end()) {
1932 WLOG("Inflation index " << sim_zci << " in simmarket is not included in sensitivities analysis");
1933 }
1934 }
1935
1936 for (auto c : sensitivityData_->zeroInflationCapFloorVolShiftData()) {
1937 std::string name = c.first;
1938 Size n_strikes;
1939 try {
1940 n_strikes = simMarketData_->zeroInflationCapFloorVolStrikes(name).size();
1941 } catch (const std::exception& e) {
1942 ALOG("skip scenario generation for zero inflation cf vol " << name << ": " << e.what());
1943 continue;
1944 }
1945 Size n_exp = simMarketData_->zeroInflationCapFloorVolExpiries(name).size();
1946 vector<Real> volStrikes = simMarketData_->zeroInflationCapFloorVolStrikes(name);
1948 if (!isScenarioRelevant(up, data))
1949 continue;
1950 ShiftType shiftType = getShiftType(data);
1951 Real shiftSize = getShiftSize(data);
1952 vector<vector<Real>> volData(n_exp, vector<Real>(n_strikes, 0.0));
1953 vector<Real> volExpiryTimes(n_exp, 0.0);
1954 vector<vector<Real>> shiftedVolData(n_exp, vector<Real>(n_strikes, 0.0));
1955
1956 std::vector<Period> expiries = overrideTenors_ && simMarketData_->hasZeroInflationCapFloorVolExpiries(name)
1957 ? simMarketData_->zeroInflationCapFloorVolExpiries(name)
1958 : data.shiftExpiries;
1959 QL_REQUIRE(expiries.size() == data.shiftExpiries.size(), "mismatch between effective shift expiries ("
1960 << expiries.size() << ") and shift tenors ("
1961 << data.shiftExpiries.size());
1962 vector<Real> shiftExpiryTimes(expiries.size(), 0.0);
1963 vector<Real> shiftStrikes = data.shiftStrikes;
1964
1965 DayCounter dc = Actual365Fixed();
1966 try {
1967 if (auto s = simMarket_.lock()) {
1968 dc = s->cpiInflationCapFloorVolatilitySurface(name)->dayCounter();
1969 } else {
1970 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
1971 }
1972 } catch (const std::exception&) {
1973 WLOG("Day counter lookup in simulation market failed for cpi cap/floor vol surface "
1974 << name << ", using default A365");
1975 }
1976
1977 // cache original vol data
1978 for (Size j = 0; j < n_exp; ++j) {
1979 Date expiry = asof + simMarketData_->zeroInflationCapFloorVolExpiries(name)[j];
1980 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
1981 }
1982 bool valid = true;
1983 for (Size j = 0; j < n_exp; ++j) {
1984 for (Size k = 0; k < n_strikes; ++k) {
1985 Size idx = j * n_strikes + k;
1987 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, volData[j][k], continueOnError_);
1988 }
1989 }
1990 if (!valid)
1991 continue;
1992
1993 // cache tenor times
1994 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
1995 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + expiries[j]);
1996
1997 bool validShiftSize = vectorEqual(volExpiryTimes, shiftExpiryTimes);
1998 validShiftSize = validShiftSize && vectorEqual(volStrikes, shiftStrikes);
1999
2000 // loop over shift expiries and terms
2001 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
2002 for (Size k = 0; k < shiftStrikes.size(); ++k) {
2003 QuantLib::ext::shared_ptr<Scenario> scenario =
2004 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2005
2006 applyShift(j, k, shiftSize, up, shiftType, shiftExpiryTimes, shiftStrikes, volExpiryTimes, volStrikes,
2007 volData, shiftedVolData, true);
2008
2009 // add shifted vol data to the scenario
2010 for (Size jj = 0; jj < n_exp; ++jj) {
2011 for (Size kk = 0; kk < n_strikes; ++kk) {
2012 Size idx = jj * n_strikes + kk;
2013 RiskFactorKey key(RFType::ZeroInflationCapFloorVolatility, name, idx);
2014 if (sensitivityData_->useSpreadedTermStructures()) {
2015 scenario->add(key, shiftedVolData[jj][kk] - volData[jj][kk]);
2016 } else {
2017 scenario->add(key, shiftedVolData[jj][kk]);
2018 }
2019
2020 // Possibly store valid shift size
2021 if (validShiftSize && j == jj && k == kk) {
2022 storeShiftData(key, volData[jj][kk], shiftedVolData[jj][kk]);
2023 }
2024 }
2025 }
2026
2027 // add this scenario to the scenario vector
2028 scenarios_.push_back(scenario);
2029 scenarioDescriptions_.push_back(
2031 scenario->label(to_string(scenarioDescriptions_.back()));
2032 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
2033 }
2034 }
2035 }
2036 DLOG("Zero inflation cap/floor vol scenarios done");
2037}
2038
2040 Date asof = baseScenario_->asof();
2041 // We can choose to shift fewer discount curves than listed in the market
2042 // Log an ALERT if some names in simmarket are excluded from the list
2043 for (auto name : simMarketData_->baseCorrelationNames()) {
2044 if (sensitivityData_->baseCorrelationShiftData().find(name) ==
2045 sensitivityData_->baseCorrelationShiftData().end()) {
2046 WLOG("Base Correlation " << name << " in simmarket is not included in sensitivities analysis");
2047 }
2048 }
2049
2050 Size n_bc_terms = simMarketData_->baseCorrelationTerms().size();
2051 Size n_bc_levels = simMarketData_->baseCorrelationDetachmentPoints().size();
2052
2053 vector<vector<Real>> bcData(n_bc_levels, vector<Real>(n_bc_terms, 0.0));
2054 vector<vector<Real>> shiftedBcData(n_bc_levels, vector<Real>(n_bc_levels, 0.0));
2055 vector<Real> termTimes(n_bc_terms, 0.0);
2056 vector<Real> levels = simMarketData_->baseCorrelationDetachmentPoints();
2057
2058 for (auto b : sensitivityData_->baseCorrelationShiftData()) {
2059 std::string name = b.first;
2061 if (!isScenarioRelevant(up, data))
2062 continue;
2063 ShiftType shiftType = getShiftType(data);
2064 Real shiftSize = getShiftSize(data);
2065
2066 vector<Real> shiftLevels = data.shiftLossLevels;
2067 vector<Real> shiftTermTimes(data.shiftTerms.size(), 0.0);
2068
2069 DayCounter dc = Actual365Fixed();
2070 try {
2071 if (auto s = simMarket_.lock()) {
2072 dc = s->baseCorrelation(name)->dayCounter();
2073 } else {
2074 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
2075 }
2076 } catch (const std::exception&) {
2077 WLOG("Day counter lookup in simulation market failed for base correlation structure "
2078 << name << ", using default A365");
2079 }
2080
2081 // cache original base correlation data
2082 for (Size j = 0; j < n_bc_terms; ++j) {
2083 Date term = asof + simMarketData_->baseCorrelationTerms()[j];
2084 termTimes[j] = dc.yearFraction(asof, term);
2085 }
2086 bool valid = true;
2087 for (Size j = 0; j < n_bc_levels; ++j) {
2088 for (Size k = 0; k < n_bc_terms; ++k) {
2090 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, bcData[j][k], continueOnError_);
2091 }
2092 }
2093 if (!valid)
2094 continue;
2095
2096 // cache tenor times
2097 for (Size j = 0; j < shiftTermTimes.size(); ++j)
2098 shiftTermTimes[j] = dc.yearFraction(asof, asof + data.shiftTerms[j]);
2099
2100 // Can we store a valid shift size?
2101 bool validShiftSize = vectorEqual(termTimes, shiftTermTimes);
2102 validShiftSize = validShiftSize && vectorEqual(levels, shiftLevels);
2103
2104 // loop over shift levels and terms
2105 for (Size j = 0; j < shiftLevels.size(); ++j) {
2106 for (Size k = 0; k < shiftTermTimes.size(); ++k) {
2107 QuantLib::ext::shared_ptr<Scenario> scenario =
2108 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2109
2110 applyShift(j, k, shiftSize, up, shiftType, shiftLevels, shiftTermTimes, levels, termTimes, bcData,
2111 shiftedBcData, true);
2112
2113 // add shifted vol data to the scenario
2114 for (Size jj = 0; jj < n_bc_levels; ++jj) {
2115 for (Size kk = 0; kk < n_bc_terms; ++kk) {
2116 Size idx = jj * n_bc_terms + kk;
2117 if (shiftedBcData[jj][kk] < 0.0) {
2118 ALOG("invalid shifted base correlation " << shiftedBcData[jj][kk] << " at lossLevelIndex "
2119 << jj << " and termIndex " << kk
2120 << " set to zero");
2121 shiftedBcData[jj][kk] = 0.0;
2122 } else if (shiftedBcData[jj][kk] > 1.0) {
2123 ALOG("invalid shifted base correlation " << shiftedBcData[jj][kk] << " at lossLevelIndex "
2124 << jj << " and termIndex " << kk
2125 << " set to 1 - epsilon");
2126 shiftedBcData[jj][kk] = 1.0 - QL_EPSILON;
2127 }
2128
2129 RiskFactorKey key(RFType::BaseCorrelation, name, idx);
2130 if (sensitivityData_->useSpreadedTermStructures()) {
2131 scenario->add(key, shiftedBcData[jj][kk] - bcData[jj][kk]);
2132 } else {
2133 scenario->add(key, shiftedBcData[jj][kk]);
2134 }
2135 // Possibly store valid shift size
2136 if (validShiftSize && j == jj && k == kk) {
2137 storeShiftData(key, bcData[jj][kk], shiftedBcData[jj][kk]);
2138 }
2139 }
2140 }
2141
2142 // add this scenario to the scenario vector
2143 scenarios_.push_back(scenario);
2144 scenarioDescriptions_.push_back(
2146 scenario->label(to_string(scenarioDescriptions_.back()));
2147 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
2148 }
2149 }
2150 }
2151 DLOG("Base correlation scenarios done");
2152}
2153
2155
2156 Date asof = baseScenario_->asof();
2157
2158 // Log an ALERT if some commodity curves in simulation market are not in the list
2159 for (const string& name : simMarketData_->commodityNames()) {
2160 if (sensitivityData_->commodityCurveShiftData().find(name) ==
2161 sensitivityData_->commodityCurveShiftData().end()) {
2162 ALOG("Commodity " << name
2163 << " in simulation market is not "
2164 "included in commodity sensitivity analysis");
2165 }
2166 }
2167
2168 for (auto c : sensitivityData_->commodityCurveShiftData()) {
2169 string name = c.first;
2170 // Tenors for this name in simulation market
2171 vector<Period> simMarketTenors;
2172 try {
2173 simMarketTenors = simMarketData_->commodityCurveTenors(name);
2174 } catch (const std::exception& e) {
2175 ALOG("skip scenario generation for comm curve " << name << ": " << e.what());
2176 continue;
2177 }
2178 DayCounter dc = Actual365Fixed();
2179 try {
2180 if (auto s = simMarket_.lock()) {
2181 dc = s->commodityPriceCurve(name)->dayCounter();
2182 } else {
2183 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
2184 }
2185 } catch (const std::exception&) {
2186 WLOG("Day counter lookup in simulation market failed for commodity price curve " << name
2187 << ", using default A365");
2188 }
2189
2190 vector<Real> times(simMarketTenors.size());
2191 vector<Real> basePrices(times.size());
2192 vector<Real> shiftedPrices(times.size());
2193
2194 // Get the base prices for this name from the base scenario
2195 bool valid = true;
2196 for (Size j = 0; j < times.size(); ++j) {
2197 times[j] = dc.yearFraction(asof, asof + simMarketTenors[j]);
2199 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, basePrices[j], continueOnError_);
2200 }
2201 if (!valid)
2202 continue;
2203
2204 // Get the sensitivity data for this name
2206 if (!isScenarioRelevant(up, data))
2207 continue;
2208 ShiftType shiftType = getShiftType(data);
2209 Real shiftSize = getShiftSize(data);
2210
2211 // Get the times at which we want to apply the shifts
2212 QL_REQUIRE(!data.shiftTenors.empty(), "Commodity curve shift tenors have not been given");
2213 vector<Time> shiftTimes(data.shiftTenors.size());
2214 for (Size j = 0; j < data.shiftTenors.size(); ++j) {
2215 shiftTimes[j] = dc.yearFraction(asof, asof + data.shiftTenors[j]);
2216 }
2217
2218 // Can we store a valid shift size?
2219 bool validShiftSize = vectorEqual(times, shiftTimes);
2220
2221 // Generate the scenarios for each shift
2222 for (Size j = 0; j < data.shiftTenors.size(); ++j) {
2223
2224 QuantLib::ext::shared_ptr<Scenario> scenario =
2225 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2226
2227 // Apply shift at tenor point j
2228 applyShift(j, shiftSize, up, shiftType, shiftTimes, basePrices, times, shiftedPrices, true);
2229
2230 // store shifted commodity price curve in the scenario
2231 for (Size k = 0; k < times.size(); ++k) {
2232 RiskFactorKey key(RFType::CommodityCurve, name, k);
2233 if (sensitivityData_->useSpreadedTermStructures()) {
2234 scenario->add(key, shiftedPrices[k] - basePrices[k]);
2235 } else {
2236 scenario->add(key, shiftedPrices[k]);
2237 }
2238
2239 // Possibly store valid shift size
2240 if (validShiftSize && j == k) {
2241 storeShiftData(key, basePrices[k], shiftedPrices[k]);
2242 }
2243 }
2244
2245 // add this scenario to the scenario vector
2246 scenarios_.push_back(scenario);
2248 scenario->label(to_string(scenarioDescriptions_.back()));
2249 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
2250 }
2251 }
2252 DLOG("Commodity curve scenarios done");
2253}
2254
2256
2257 // Log an ALERT if some commodity vol names in simulation market are not in the list
2258 for (const string& name : simMarketData_->commodityVolNames()) {
2259 if (sensitivityData_->commodityVolShiftData().find(name) == sensitivityData_->commodityVolShiftData().end()) {
2260 ALOG("Commodity volatility " << name
2261 << " in simulation market is not "
2262 "included in commodity sensitivity analysis");
2263 }
2264 }
2265
2266 // Loop over each commodity and create volatility scenario
2267 Date asof = baseScenario_->asof();
2268 for (auto c : sensitivityData_->commodityVolShiftData()) {
2269 string name = c.first;
2270 // Simulation market data for the current name
2271 vector<Period> expiries;
2272 try {
2273 expiries = simMarketData_->commodityVolExpiries(name);
2274 } catch (const std::exception& e) {
2275 ALOG("skip scenario generation for comm vol " << name << ": " << e.what());
2276 continue;
2277 }
2278 const vector<Real>& moneyness = simMarketData_->commodityVolMoneyness(name);
2279 QL_REQUIRE(!expiries.empty(), "Sim market commodity volatility expiries have not been specified for " << name);
2280 QL_REQUIRE(!moneyness.empty(), "Sim market commodity volatility moneyness has not been specified for " << name);
2281 // Store base scenario volatilities, strike x expiry
2282 vector<vector<Real>> baseValues(moneyness.size(), vector<Real>(expiries.size()));
2283 // Time to each expiry
2284 vector<Time> times(expiries.size());
2285 // Store shifted scenario volatilities
2286 vector<vector<Real>> shiftedValues = baseValues;
2287
2289 if (!isScenarioRelevant(up, sd))
2290 continue;
2291 QL_REQUIRE(!sd.shiftExpiries.empty(), "commodity volatility shift tenors must be specified");
2292
2293 ShiftType shiftType = getShiftType(sd);
2294 vector<Time> shiftTimes(sd.shiftExpiries.size());
2295 DayCounter dc = Actual365Fixed();
2296 try {
2297 if (auto s = simMarket_.lock()) {
2298 dc = s->commodityVolatility(name)->dayCounter();
2299 } else {
2300 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
2301 }
2302 } catch (const std::exception&) {
2303 WLOG("Day counter lookup in simulation market failed for commodity vol surface " << name
2304 << ", using default A365");
2305 }
2306
2307 // Get the base scenario volatility values
2308 bool valid = true;
2309 for (Size j = 0; j < expiries.size(); j++) {
2310 times[j] = dc.yearFraction(asof, asof + expiries[j]);
2311 for (Size i = 0; i < moneyness.size(); i++) {
2312 RiskFactorKey key(RiskFactorKey::KeyType::CommodityVolatility, name, i * expiries.size() + j);
2313 valid =
2314 valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, baseValues[i][j], continueOnError_);
2315 }
2316 }
2317 if (!valid)
2318 continue;
2319
2320 // Store the shift expiry times
2321 for (Size sj = 0; sj < sd.shiftExpiries.size(); ++sj) {
2322 shiftTimes[sj] = dc.yearFraction(asof, asof + sd.shiftExpiries[sj]);
2323 }
2324
2325 // Can we store a valid shift size?
2326 bool validShiftSize = vectorEqual(times, shiftTimes);
2327 validShiftSize = validShiftSize && vectorEqual(moneyness, sd.shiftStrikes);
2328
2329 // Loop and apply scenarios
2330 for (Size sj = 0; sj < sd.shiftExpiries.size(); ++sj) {
2331 for (Size si = 0; si < sd.shiftStrikes.size(); ++si) {
2332
2333 QuantLib::ext::shared_ptr<Scenario> scenario =
2334 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2335
2336 applyShift(si, sj, getShiftSize(sd), up, shiftType, sd.shiftStrikes, shiftTimes, moneyness, times,
2337 baseValues, shiftedValues, true);
2338
2339 Size counter = 0;
2340 for (Size i = 0; i < moneyness.size(); i++) {
2341 for (Size j = 0; j < expiries.size(); ++j) {
2342 RiskFactorKey key(RFType::CommodityVolatility, name, counter++);
2343 if (sensitivityData_->useSpreadedTermStructures()) {
2344 scenario->add(key, shiftedValues[i][j] - baseValues[i][j]);
2345 } else {
2346 scenario->add(key, shiftedValues[i][j]);
2347 }
2348 // Possibly store valid shift size
2349 if (validShiftSize && si == i && sj == j) {
2350 storeShiftData(key, baseValues[i][j], shiftedValues[i][j]);
2351 }
2352 }
2353 }
2354
2355 // Give the scenario a label
2356
2357 // Add the final scenario to the scenario vector
2359 scenario->label(to_string(scenarioDescriptions_.back()));
2360 scenarios_.push_back(scenario);
2361 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
2362 }
2363 }
2364 }
2365 DLOG("Commodity volatility scenarios done");
2366}
2367
2369 Date asof = baseScenario_->asof();
2370 for (auto sim_cap : simMarketData_->correlationPairs()) {
2371 if (sensitivityData_->correlationShiftData().find(sim_cap) == sensitivityData_->correlationShiftData().end()) {
2372 WLOG("Correlation " << sim_cap << " in simmarket is not included in sensitivities analysis");
2373 }
2374 }
2375
2376 Size n_c_strikes = simMarketData_->correlationStrikes().size();
2377 vector<Real> corrStrikes = simMarketData_->correlationStrikes();
2378
2379 for (auto c : sensitivityData_->correlationShiftData()) {
2380 std::string label = c.first;
2381 std::vector<std::string> tokens = ore::data::getCorrelationTokens(label);
2382 std::pair<string, string> pair(tokens[0], tokens[1]);
2383 Size n_c_exp = simMarketData_->correlationExpiries().size();
2385 if (!isScenarioRelevant(up, data))
2386 continue;
2387 ShiftType shiftType = getShiftType(data);
2388 Real shiftSize = getShiftSize(data);
2389 vector<vector<Real>> corrData(n_c_exp, vector<Real>(n_c_strikes, 0.0));
2390 vector<Real> corrExpiryTimes(n_c_exp, 0.0);
2391 vector<vector<Real>> shiftedCorrData(n_c_exp, vector<Real>(n_c_strikes, 0.0));
2392
2393 std::vector<Period> expiries = overrideTenors_ ? simMarketData_->correlationExpiries() : data.shiftExpiries;
2394 QL_REQUIRE(expiries.size() == data.shiftExpiries.size(), "mismatch between effective shift expiries ("
2395 << expiries.size() << ") and shift tenors ("
2396 << data.shiftExpiries.size());
2397 vector<Real> shiftExpiryTimes(expiries.size(), 0.0);
2398 vector<Real> shiftStrikes = data.shiftStrikes;
2399
2400 DayCounter dc = Actual365Fixed();
2401 try {
2402 if (auto s = simMarket_.lock()) {
2403 dc = s->correlationCurve(pair.first, pair.second)->dayCounter();
2404 } else {
2405 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
2406 }
2407 } catch (const std::exception&) {
2408 WLOG("Day counter lookup in simulation market failed for correlation curve "
2409 << pair.first << " - " << pair.second << ", using default A365");
2410 }
2411
2412 // cache original vol data
2413 for (Size j = 0; j < n_c_exp; ++j) {
2414 Date expiry = asof + simMarketData_->correlationExpiries()[j];
2415 corrExpiryTimes[j] = dc.yearFraction(asof, expiry);
2416 }
2417 bool valid = true;
2418 for (Size j = 0; j < n_c_exp; ++j) {
2419 for (Size k = 0; k < n_c_strikes; ++k) {
2420 Size idx = j * n_c_strikes + k;
2422 valid = valid && tryGetBaseScenarioValue(baseScenarioAbsolute_, key, corrData[j][k], continueOnError_);
2423 }
2424 }
2425 if (!valid)
2426 continue;
2427
2428 // cache tenor times
2429 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
2430 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + expiries[j]);
2431
2432 // Can we store a valid shift size?
2433 bool validShiftSize = vectorEqual(corrExpiryTimes, shiftExpiryTimes);
2434 validShiftSize = validShiftSize && vectorEqual(corrStrikes, shiftStrikes);
2435
2436 // loop over shift expiries and terms
2437 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
2438 for (Size k = 0; k < shiftStrikes.size(); ++k) {
2439 QuantLib::ext::shared_ptr<Scenario> scenario =
2440 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2441
2442 applyShift(j, k, shiftSize, up, shiftType, shiftExpiryTimes, shiftStrikes, corrExpiryTimes, corrStrikes,
2443 corrData, shiftedCorrData, true);
2444
2445 // add shifted vol data to the scenario
2446 for (Size jj = 0; jj < n_c_exp; ++jj) {
2447 for (Size kk = 0; kk < n_c_strikes; ++kk) {
2448 Size idx = jj * n_c_strikes + kk;
2449 RiskFactorKey key(RFType::Correlation, label, idx);
2450
2451 if (shiftedCorrData[jj][kk] > 1) {
2452 shiftedCorrData[jj][kk] = 1;
2453 } else if (shiftedCorrData[jj][kk] < -1) {
2454 shiftedCorrData[jj][kk] = -1;
2455 }
2456
2457 if (sensitivityData_->useSpreadedTermStructures()) {
2458 scenario->add(key, shiftedCorrData[jj][kk] - corrData[jj][kk]);
2459 } else {
2460 scenario->add(key, shiftedCorrData[jj][kk]);
2461 }
2462 // Possibly store valid shift size
2463 if (validShiftSize && j == jj && k == kk) {
2464 storeShiftData(key, corrData[jj][kk], shiftedCorrData[jj][kk]);
2465 }
2466 }
2467 }
2468
2469 // add this scenario to the scenario vector
2470 scenarios_.push_back(scenario);
2472 scenario->label(to_string(scenarioDescriptions_.back()));
2473 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label() << " created");
2474 }
2475 }
2476 }
2477 DLOG("Correlation scenarios done");
2478}
2479
2481 // We can choose to shift fewer discount curves than listed in the market
2482 Date asof = baseScenario_->asof();
2483 // Log an ALERT if some equities in simmarket are excluded from the sensitivities list
2484 for (auto sim_security : simMarketData_->securities()) {
2485 if (sensitivityData_->securityShiftData().find(sim_security) == sensitivityData_->securityShiftData().end()) {
2486 WLOG("Security " << sim_security << " in simmarket is not included in sensitivities analysis");
2487 }
2488 }
2489 for (auto s : sensitivityData_->securityShiftData()) {
2490 string bond = s.first;
2492 if (!isScenarioRelevant(up, data))
2493 continue;
2494 ShiftType type = getShiftType(data);
2495 Real size = up ? getShiftSize(data) : -1.0 * getShiftSize(data);
2496 bool relShift = (type == ShiftType::Relative);
2497
2498 QuantLib::ext::shared_ptr<Scenario> scenario =
2499 sensiScenarioFactory_->buildScenario(asof, !sensitivityData_->useSpreadedTermStructures());
2500
2502 Real base_spread;
2503 if (!tryGetBaseScenarioValue(baseScenarioAbsolute_, key, base_spread, continueOnError_))
2504 continue;
2505 Real newSpread = relShift ? base_spread * (1.0 + size) : (base_spread + size);
2506 // Real newRate = up ? rate * (1.0 + getShiftSize(data)) : rate * (1.0 - getShiftSize(data));
2507 scenario->add(key, sensitivityData_->useSpreadedTermStructures() ? newSpread - base_spread : newSpread);
2508
2509 storeShiftData(key, base_spread, newSpread);
2510
2511 scenarios_.push_back(scenario);
2513 scenario->label(to_string(scenarioDescriptions_.back()));
2514 DLOG("Sensitivity scenario # " << scenarios_.size() << ", label " << scenario->label()
2515 << " created: " << newSpread);
2516 }
2517 DLOG("Security scenarios done");
2518}
2519
2523 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2524 ScenarioDescription desc(type, key, "spot");
2525 shiftSchemes_[key] = shiftScheme;
2526 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2527 return desc;
2528}
2529
2533 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2534 ScenarioDescription desc(type, key, "spot");
2535 shiftSchemes_[key] = shiftScheme;
2536 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2537 return desc;
2538}
2539
2542 QL_REQUIRE(sensitivityData_->dividendYieldShiftData().find(name) !=
2543 sensitivityData_->dividendYieldShiftData().end(),
2544 "equity " << name << " not found in dividend yield shift data");
2545 QL_REQUIRE(bucket < sensitivityData_->dividendYieldShiftData()[name]->shiftTenors.size(),
2546 "bucket " << bucket << " out of range");
2548 std::ostringstream o;
2549 o << sensitivityData_->dividendYieldShiftData()[name]->shiftTenors[bucket];
2550 string text = o.str();
2551 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2552 ScenarioDescription desc(type, key, text);
2553 shiftSchemes_[key] = shiftScheme;
2554 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2555 return desc;
2556}
2557
2559SensitivityScenarioGenerator::discountScenarioDescription(string ccy, Size bucket, bool up, ShiftScheme shiftScheme) {
2560 QL_REQUIRE(sensitivityData_->discountCurveShiftData().find(ccy) != sensitivityData_->discountCurveShiftData().end(),
2561 "currency " << ccy << " not found in discount shift data");
2562 QL_REQUIRE(bucket < sensitivityData_->discountCurveShiftData()[ccy]->shiftTenors.size(),
2563 "bucket " << bucket << " out of range");
2565 std::ostringstream o;
2566 o << sensitivityData_->discountCurveShiftData()[ccy]->shiftTenors[bucket];
2567 string text = o.str();
2568 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2569 ScenarioDescription desc(type, key, text);
2570 shiftSchemes_[key] = shiftScheme;
2571 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2572 return desc;
2573}
2574
2576SensitivityScenarioGenerator::indexScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme) {
2577 QL_REQUIRE(sensitivityData_->indexCurveShiftData().find(index) != sensitivityData_->indexCurveShiftData().end(),
2578 "currency " << index << " not found in index shift data");
2579 QL_REQUIRE(bucket < sensitivityData_->indexCurveShiftData()[index]->shiftTenors.size(),
2580 "bucket " << bucket << " out of range");
2582 std::ostringstream o;
2583 o << sensitivityData_->indexCurveShiftData()[index]->shiftTenors[bucket];
2584 string text = o.str();
2585 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2586 ScenarioDescription desc(type, key, text);
2587 shiftSchemes_[key] = shiftScheme;
2588 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2589 return desc;
2590}
2591
2593SensitivityScenarioGenerator::yieldScenarioDescription(string name, Size bucket, bool up, ShiftScheme shiftScheme) {
2594 QL_REQUIRE(sensitivityData_->yieldCurveShiftData().find(name) != sensitivityData_->yieldCurveShiftData().end(),
2595 "currency " << name << " not found in index shift data");
2596 QL_REQUIRE(bucket < sensitivityData_->yieldCurveShiftData()[name]->shiftTenors.size(),
2597 "bucket " << bucket << " out of range");
2599 std::ostringstream o;
2600 o << sensitivityData_->yieldCurveShiftData()[name]->shiftTenors[bucket];
2601 string text = o.str();
2602 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2603 ScenarioDescription desc(type, key, text);
2604 shiftSchemes_[key] = shiftScheme;
2605 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2606 return desc;
2607}
2608
2610SensitivityScenarioGenerator::fxVolScenarioDescription(string ccypair, Size expiryBucket, Size strikeBucket, bool up,
2611 ShiftScheme shiftScheme) {
2612 QL_REQUIRE(sensitivityData_->fxVolShiftData().find(ccypair) != sensitivityData_->fxVolShiftData().end(),
2613 "currency pair " << ccypair << " not found in fx vol shift data");
2614 SensitivityScenarioData::VolShiftData data = sensitivityData_->fxVolShiftData()[ccypair];
2615 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2616 Size index = strikeBucket * data.shiftExpiries.size() + expiryBucket;
2618 std::ostringstream o;
2619 if (data.shiftStrikes.size() == 0 ||
2620 close_enough(data.shiftStrikes[strikeBucket], 0)) { // shiftStrikes defaults to {0.00}
2621 o << data.shiftExpiries[expiryBucket] << "/ATM";
2622 } else {
2623 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2624 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftStrikes[strikeBucket];
2625 }
2626 string text = o.str();
2627 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2628 ScenarioDescription desc(type, key, text);
2629 shiftSchemes_[key] = shiftScheme;
2630 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2631 return desc;
2632}
2633
2635SensitivityScenarioGenerator::equityVolScenarioDescription(string equity, Size expiryBucket, Size strikeBucket, bool up,
2636 ShiftScheme shiftScheme) {
2637 QL_REQUIRE(sensitivityData_->equityVolShiftData().find(equity) != sensitivityData_->equityVolShiftData().end(),
2638 "currency pair " << equity << " not found in fx vol shift data");
2639 SensitivityScenarioData::VolShiftData data = sensitivityData_->equityVolShiftData()[equity];
2640 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2641 Size index = strikeBucket * data.shiftExpiries.size() + expiryBucket;
2643 std::ostringstream o;
2644 if (data.shiftStrikes.size() == 0 || close_enough(data.shiftStrikes[strikeBucket], 0)) {
2645 o << data.shiftExpiries[expiryBucket] << "/ATM";
2646 } else {
2647 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2648 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftStrikes[strikeBucket];
2649 }
2650 string text = o.str();
2651 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2652 ScenarioDescription desc(type, key, text);
2653 shiftSchemes_[key] = shiftScheme;
2654 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2655 return desc;
2656}
2657
2659SensitivityScenarioGenerator::swaptionVolScenarioDescription(string ccy, Size expiryBucket, Size termBucket,
2660 Size strikeBucket, bool up, ShiftScheme shiftScheme) {
2661 QL_REQUIRE(sensitivityData_->swaptionVolShiftData().find(ccy) != sensitivityData_->swaptionVolShiftData().end(),
2662 "currency " << ccy << " not found in swaption vol shift data");
2664 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2665 QL_REQUIRE(termBucket < data.shiftTerms.size(), "term bucket " << termBucket << " out of range");
2666 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2667 Size index = expiryBucket * data.shiftStrikes.size() * data.shiftTerms.size() +
2668 termBucket * data.shiftStrikes.size() + strikeBucket;
2670 std::ostringstream o;
2671 if (data.shiftStrikes.size() == 0 || close_enough(data.shiftStrikes[strikeBucket], 0)) {
2672 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftTerms[termBucket] << "/ATM";
2673 } else {
2674 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftTerms[termBucket] << "/" << std::setprecision(4)
2675 << data.shiftStrikes[strikeBucket];
2676 }
2677 string text = o.str();
2678 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2679 ScenarioDescription desc(type, key, text);
2680 shiftSchemes_[key] = shiftScheme;
2681 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2682 return desc;
2683}
2684
2686SensitivityScenarioGenerator::yieldVolScenarioDescription(string securityId, Size expiryBucket, Size termBucket,
2687 bool up, ShiftScheme shiftScheme) {
2688 QL_REQUIRE(sensitivityData_->yieldVolShiftData().find(securityId) != sensitivityData_->yieldVolShiftData().end(),
2689 "currency " << securityId << " not found in yield vol shift data");
2691 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2692 QL_REQUIRE(termBucket < data.shiftTerms.size(), "term bucket " << termBucket << " out of range");
2693 Size index =
2694 expiryBucket * data.shiftStrikes.size() * data.shiftTerms.size() + termBucket * data.shiftStrikes.size();
2696 std::ostringstream o;
2697 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftTerms[termBucket] << "/ATM";
2698 string text = o.str();
2699 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2700 ScenarioDescription desc(type, key, text);
2701 shiftSchemes_[key] = shiftScheme;
2702 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2703 return desc;
2704}
2705
2707SensitivityScenarioGenerator::capFloorVolScenarioDescription(string ccy, Size expiryBucket, Size strikeBucket, bool up,
2708 bool isAtm, ShiftScheme shiftScheme) {
2709 QL_REQUIRE(sensitivityData_->capFloorVolShiftData().find(ccy) != sensitivityData_->capFloorVolShiftData().end(),
2710 "currency " << ccy << " not found in cap/floor vol shift data");
2712 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2713 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2714 Size index = expiryBucket * data.shiftStrikes.size() + strikeBucket;
2716 std::ostringstream o;
2717 o << data.shiftExpiries[expiryBucket] << "/";
2718 if (isAtm) {
2719 o << "ATM";
2720 } else {
2721 o << std::setprecision(4) << data.shiftStrikes[strikeBucket];
2722 }
2723 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2724 ScenarioDescription desc(type, key, o.str());
2725 shiftSchemes_[key] = shiftScheme;
2726 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2727 return desc;
2728}
2729
2732 ShiftScheme shiftScheme) {
2733 QL_REQUIRE(sensitivityData_->creditCurveShiftData().find(name) != sensitivityData_->creditCurveShiftData().end(),
2734 "Name " << name << " not found in credit shift data");
2735 QL_REQUIRE(bucket < sensitivityData_->creditCurveShiftData()[name]->shiftTenors.size(),
2736 "bucket " << bucket << " out of range");
2738 std::ostringstream o;
2739 o << sensitivityData_->creditCurveShiftData()[name]->shiftTenors[bucket];
2740 string text = o.str();
2741 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2742 ScenarioDescription desc(type, key, text);
2743 shiftSchemes_[key] = shiftScheme;
2744 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2745 return desc;
2746}
2747
2749SensitivityScenarioGenerator::CdsVolScenarioDescription(string name, Size expiryBucket, Size strikeBucket, bool up,
2750 ShiftScheme shiftScheme) {
2751 QL_REQUIRE(sensitivityData_->cdsVolShiftData().find(name) != sensitivityData_->cdsVolShiftData().end(),
2752 "name " << name << " not found in swaption name shift data");
2754 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2755 Size index = strikeBucket * data.shiftExpiries.size() + expiryBucket;
2757 std::ostringstream o;
2758 o << data.shiftExpiries[expiryBucket] << "/"
2759 << "ATM";
2760 string text = o.str();
2761 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2762 ScenarioDescription desc(type, key, text);
2763 shiftSchemes_[key] = shiftScheme;
2764 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2765 return desc;
2766}
2767
2769SensitivityScenarioGenerator::zeroInflationScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme) {
2770 QL_REQUIRE(sensitivityData_->zeroInflationCurveShiftData().find(index) !=
2771 sensitivityData_->zeroInflationCurveShiftData().end(),
2772 "inflation index " << index << " not found in zero inflation index shift data");
2773 QL_REQUIRE(bucket < sensitivityData_->zeroInflationCurveShiftData()[index]->shiftTenors.size(),
2774 "bucket " << bucket << " out of range");
2776 std::ostringstream o;
2777 o << sensitivityData_->zeroInflationCurveShiftData()[index]->shiftTenors[bucket];
2778 string text = o.str();
2779 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2780 ScenarioDescription desc(type, key, text);
2781 shiftSchemes_[key] = shiftScheme;
2782 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2783 return desc;
2784}
2785
2787SensitivityScenarioGenerator::yoyInflationScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme) {
2788 QL_REQUIRE(sensitivityData_->yoyInflationCurveShiftData().find(index) !=
2789 sensitivityData_->yoyInflationCurveShiftData().end(),
2790 "yoy inflation index " << index << " not found in zero inflation index shift data");
2791 QL_REQUIRE(bucket < sensitivityData_->yoyInflationCurveShiftData()[index]->shiftTenors.size(),
2792 "bucket " << bucket << " out of range");
2794 std::ostringstream o;
2795 o << sensitivityData_->yoyInflationCurveShiftData()[index]->shiftTenors[bucket];
2796 string text = o.str();
2797 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2798 ScenarioDescription desc(type, key, text);
2799 shiftSchemes_[key] = shiftScheme;
2800 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2801 return desc;
2802}
2803
2806 Size strikeBucket, bool up,
2807 ShiftScheme shiftScheme) {
2808 QL_REQUIRE(sensitivityData_->yoyInflationCapFloorVolShiftData().find(name) !=
2809 sensitivityData_->yoyInflationCapFloorVolShiftData().end(),
2810 "index " << name << " not found in yoy cap/floor vol shift data");
2811 SensitivityScenarioData::CapFloorVolShiftData data = *sensitivityData_->yoyInflationCapFloorVolShiftData()[name];
2812 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2813 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2814 Size index = expiryBucket * data.shiftStrikes.size() + strikeBucket;
2816 std::ostringstream o;
2817 // Currently CapFloorVolShiftData must have a collection of absolute strikes for yoy inflation cap/floor vols
2818 o << data.shiftExpiries[expiryBucket] << "/" << std::setprecision(4) << data.shiftStrikes[strikeBucket];
2819 string text = o.str();
2820 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2821 ScenarioDescription desc(type, key, text);
2822 shiftSchemes_[key] = shiftScheme;
2823 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2824 return desc;
2825}
2826
2829 Size strikeBucket, bool up,
2830 ShiftScheme shiftScheme) {
2831 QL_REQUIRE(sensitivityData_->zeroInflationCapFloorVolShiftData().find(name) !=
2832 sensitivityData_->zeroInflationCapFloorVolShiftData().end(),
2833 "currency " << name << " not found in zero inflation cap/floor vol shift data");
2834 SensitivityScenarioData::VolShiftData data = *sensitivityData_->zeroInflationCapFloorVolShiftData()[name];
2835 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2836 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2837 Size index = expiryBucket * data.shiftStrikes.size() + strikeBucket;
2839 std::ostringstream o;
2840 if (data.shiftStrikes.size() == 0 || close_enough(data.shiftStrikes[strikeBucket], 0)) {
2841 o << data.shiftExpiries[expiryBucket] << "/ATM";
2842 } else {
2843 o << data.shiftExpiries[expiryBucket] << "/" << std::setprecision(4) << data.shiftStrikes[strikeBucket];
2844 }
2845 string text = o.str();
2846 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2847 ScenarioDescription desc(type, key, text);
2848 shiftSchemes_[key] = shiftScheme;
2849 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2850 return desc;
2851}
2852
2855 Size termBucket, bool up, ShiftScheme shiftScheme) {
2856 QL_REQUIRE(sensitivityData_->baseCorrelationShiftData().find(indexName) !=
2857 sensitivityData_->baseCorrelationShiftData().end(),
2858 "name " << indexName << " not found in base correlation shift data");
2859 SensitivityScenarioData::BaseCorrelationShiftData data = sensitivityData_->baseCorrelationShiftData()[indexName];
2860 QL_REQUIRE(termBucket < data.shiftTerms.size(), "term bucket " << termBucket << " out of range");
2861 QL_REQUIRE(lossLevelBucket < data.shiftLossLevels.size(),
2862 "loss level bucket " << lossLevelBucket << " out of range");
2863 Size index = lossLevelBucket * data.shiftTerms.size() + termBucket;
2865 std::ostringstream o;
2866 o << data.shiftLossLevels[lossLevelBucket] << "/" << data.shiftTerms[termBucket];
2867 string text = o.str();
2868 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2869 ScenarioDescription desc(type, key, text);
2870 shiftSchemes_[key] = shiftScheme;
2871 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2872 return desc;
2873}
2874
2876SensitivityScenarioGenerator::commodityCurveScenarioDescription(const string& commodityName, Size bucket, bool up,
2877 ShiftScheme shiftScheme) {
2878
2879 QL_REQUIRE(sensitivityData_->commodityCurveShiftData().count(commodityName) > 0,
2880 "Name " << commodityName << " not found in commodity curve shift data");
2881 vector<Period>& shiftTenors = sensitivityData_->commodityCurveShiftData()[commodityName]->shiftTenors;
2882 QL_REQUIRE(bucket < shiftTenors.size(), "bucket " << bucket << " out of commodity curve bucket range");
2883
2884 RiskFactorKey key(RiskFactorKey::KeyType::CommodityCurve, commodityName, bucket);
2885 ostringstream oss;
2886 oss << shiftTenors[bucket];
2887 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2888 shiftSchemes_[key] = shiftScheme;
2889 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2890 return ScenarioDescription(type, key, oss.str());
2891}
2892
2894SensitivityScenarioGenerator::commodityVolScenarioDescription(const string& commodityName, Size expiryBucket,
2895 Size strikeBucket, bool up, ShiftScheme shiftScheme) {
2896
2897 QL_REQUIRE(sensitivityData_->commodityVolShiftData().count(commodityName) > 0,
2898 "commodity " << commodityName << " not found in commodity vol shift data");
2899
2900 SensitivityScenarioData::VolShiftData data = sensitivityData_->commodityVolShiftData()[commodityName];
2901 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2902 Size index = strikeBucket * data.shiftExpiries.size() + expiryBucket;
2904 ostringstream o;
2905 if (data.shiftStrikes.size() == 0 || close_enough(data.shiftStrikes[strikeBucket], 1.0)) {
2906 o << data.shiftExpiries[expiryBucket] << "/ATM";
2907 } else {
2908 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2909 o << data.shiftExpiries[expiryBucket] << "/" << data.shiftStrikes[strikeBucket];
2910 }
2911 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2912 shiftSchemes_[key] = shiftScheme;
2913 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2914 return ScenarioDescription(type, key, o.str());
2915}
2916
2918SensitivityScenarioGenerator::correlationScenarioDescription(string pair, Size expiryBucket, Size strikeBucket, bool up,
2919 ShiftScheme shiftScheme) {
2920 QL_REQUIRE(sensitivityData_->correlationShiftData().find(pair) != sensitivityData_->correlationShiftData().end(),
2921 "pair " << pair << " not found in correlation shift data");
2922 SensitivityScenarioData::VolShiftData data = sensitivityData_->correlationShiftData()[pair];
2923 QL_REQUIRE(expiryBucket < data.shiftExpiries.size(), "expiry bucket " << expiryBucket << " out of range");
2924 QL_REQUIRE(strikeBucket < data.shiftStrikes.size(), "strike bucket " << strikeBucket << " out of range");
2925 // Size index = strikeBucket * data.shiftExpiries.size() + expiryBucket;
2926 Size index = expiryBucket * data.shiftStrikes.size() + strikeBucket;
2928 std::ostringstream o;
2929 if (data.shiftStrikes.size() == 0 || close_enough(data.shiftStrikes[strikeBucket], 0)) {
2930 o << data.shiftExpiries[expiryBucket] << "/ATM";
2931 } else {
2932 o << data.shiftExpiries[expiryBucket] << "/" << std::setprecision(4) << data.shiftStrikes[strikeBucket];
2933 }
2934 string text = o.str();
2935 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2936 ScenarioDescription desc(type, key, text);
2937 shiftSchemes_[key] = shiftScheme;
2938 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2939 return desc;
2940}
2941
2945 ScenarioDescription::Type type = up ? ScenarioDescription::Type::Up : ScenarioDescription::Type::Down;
2946 ScenarioDescription desc(type, key, "spread");
2947 shiftSchemes_[key] = shiftScheme;
2948 storeShiftData(key, 0.0, 0.0); // default, only used if not popoulated before
2949 return desc;
2950}
2951
2952} // namespace analytics
2953} // namespace ore
Data types stored in the scenario class.
Definition: scenario.hpp:48
KeyType
Risk Factor types.
Definition: scenario.hpp:51
const std::map< RiskFactorKey, QuantLib::Real > & baseValues() const
ScenarioDescription fxVolScenarioDescription(string ccypair, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription indexScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme)
std::map< RiskFactorKey, ShiftScheme > shiftSchemes_
Holds the delta shift schemes for each risk factor key.
QuantLib::ext::shared_ptr< ScenarioFactory > sensiScenarioFactory_
ScenarioDescription correlationScenarioDescription(string pair, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
QuantLib::ext::shared_ptr< SensitivityScenarioData > sensitivityData_
SensitivityScenarioGenerator(const QuantLib::ext::shared_ptr< SensitivityScenarioData > &sensitivityData, const QuantLib::ext::shared_ptr< Scenario > &baseScenario, const QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > &simMarketData, const QuantLib::ext::shared_ptr< ScenarioSimMarket > &simMarket, const QuantLib::ext::shared_ptr< ScenarioFactory > &sensiScenarioFactory, const bool overrideTenors, const std::string &sensitivityTemplate=std::string(), const bool continueOnError=false, const QuantLib::ext::shared_ptr< Scenario > &baseScenarioAbsolute=nullptr)
Constructor.
ScenarioDescription yieldScenarioDescription(string name, Size bucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription commodityVolScenarioDescription(const std::string &commodityName, QuantLib::Size expiryBucket, QuantLib::Size strikeBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription fxScenarioDescription(string ccypair, bool up, ShiftScheme shiftScheme)
ScenarioDescription dividendYieldScenarioDescription(string equity, Size bucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription zeroInflationCapFloorVolScenarioDescription(string name, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription securitySpreadScenarioDescription(string bond, bool up, ShiftScheme shiftScheme)
ScenarioDescription yoyInflationScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription zeroInflationScenarioDescription(string index, Size bucket, bool up, ShiftScheme shiftScheme)
std::map< RiskFactorKey, QuantLib::Real > baseValues_
Holds the base valuesfor each risk factor key.
void storeShiftData(const RiskFactorKey &key, const Real rate, const Real newRate)
std::map< RiskFactorKey, QuantLib::Real > shiftSizes_
Holds the shift sizes for each risk factor key.
ScenarioDescription baseCorrelationScenarioDescription(string indexName, Size lossLevelBucket, Size termBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription survivalProbabilityScenarioDescription(string name, Size bucket, bool up, ShiftScheme shiftScheme)
QuantLib::ext::shared_ptr< Scenario > baseScenarioAbsolute_
ScenarioDescription equityVolScenarioDescription(string equity, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription CdsVolScenarioDescription(string name, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription yoyInflationCapFloorVolScenarioDescription(string name, Size expiryBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
bool isScenarioRelevant(bool up, SensitivityScenarioData::ShiftData &data) const
void generateGenericYieldVolScenarios(bool up, RiskFactorKey::KeyType rfType)
ScenarioDescription yieldVolScenarioDescription(string securityId, Size expiryBucket, Size termBucket, bool up, ShiftScheme shiftScheme)
ShiftScheme getShiftScheme(SensitivityScenarioData::ShiftData &data) const
ScenarioDescription discountScenarioDescription(string ccy, Size bucket, bool up, ShiftScheme shiftScheme)
ScenarioDescription swaptionVolScenarioDescription(string ccy, Size expiryBucket, Size termBucket, Size strikeBucket, bool up, ShiftScheme shiftScheme)
ShiftType getShiftType(SensitivityScenarioData::ShiftData &data) const
ScenarioDescription commodityCurveScenarioDescription(const std::string &commodityName, QuantLib::Size bucket, bool up, ShiftScheme shiftScheme)
Real getShiftSize(SensitivityScenarioData::ShiftData &data) const
ScenarioDescription capFloorVolScenarioDescription(string ccy, Size expiryBucket, Size strikeBucket, bool up, bool isAtm, ShiftScheme shiftScheme)
ScenarioDescription equityScenarioDescription(string equity, bool up, ShiftScheme shiftScheme)
std::vector< ScenarioDescription > scenarioDescriptions_
std::map< RiskFactorKey, std::string > keyToFactor_
std::vector< QuantLib::ext::shared_ptr< Scenario > > scenarios_
const QuantLib::ext::shared_ptr< Scenario > baseScenario_
void applyShift(Size j, Real shiftSize, bool up, ShiftType type, const vector< Time > &shiftTimes, const vector< Real > &values, const vector< Time > &times, vector< Real > &shiftedValues, bool initialise)
Apply 1d triangular shift to 1d data such as yield curves, public to allow test suite access.
std::map< std::string, RiskFactorKey > factorToKey_
const QuantLib::ext::weak_ptr< ScenarioSimMarket > simMarket_
const QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > simMarketData_
SafeStack< ValueType > value
Calendar parseCalendar(const string &s)
DayCounter parseDayCounter(const string &s)
data
#define LOG(text)
#define DLOG(text)
#define ALOG(text)
#define WLOG(text)
Calendar calendar
RandomVariable exp(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
bool close(const Real &t_1, const Real &t_2)
bool vectorEqual(const vector< Real > &v_1, const vector< Real > &v_2)
std::vector< std::string > getCorrelationTokens(const std::string &name)
Size size(const ValueType &v)
std::string to_string(const LocationInfo &l)
Sensitivity scenario generation.
Structured analytics error.
Date asof(14, Jun, 2018)
string name