Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
stressscenariogenerator.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#include <ql/time/calendars/target.hpp>
22
23using namespace QuantLib;
24using namespace QuantExt;
25using namespace std;
26
27namespace ore {
28namespace analytics {
29
30StressScenarioGenerator::StressScenarioGenerator(const QuantLib::ext::shared_ptr<StressTestScenarioData>& stressData,
31 const QuantLib::ext::shared_ptr<Scenario>& baseScenario,
32 const QuantLib::ext::shared_ptr<ScenarioSimMarketParameters>& simMarketData,
33 const QuantLib::ext::shared_ptr<ScenarioSimMarket>& simMarket,
34 const QuantLib::ext::shared_ptr<ScenarioFactory>& stressScenarioFactory,
35 const QuantLib::ext::shared_ptr<Scenario>& baseScenarioAbsolute)
36 : ShiftScenarioGenerator(baseScenario, simMarketData, simMarket), stressData_(stressData),
37 stressScenarioFactory_(stressScenarioFactory),
38 baseScenarioAbsolute_(baseScenarioAbsolute == nullptr ? baseScenario : baseScenarioAbsolute) {
39
40 QL_REQUIRE(stressData_, "StressScenarioGenerator: stressData is null");
41
43}
44
46 Date asof = baseScenario_->asof();
47 for (Size i = 0; i < stressData_->data().size(); ++i) {
49 DLOG("Generate stress scenario #" << i << " '" << data.label << "'");
50 QuantLib::ext::shared_ptr<Scenario> scenario =
51 stressScenarioFactory_->buildScenario(asof, !stressData_->useSpreadedTermStructures(), data.label);
52
53 if (simMarketData_->simulateFxSpots())
54 addFxShifts(data, scenario);
55 addEquityShifts(data, scenario);
57 addIndexCurveShifts(data, scenario);
58 addYieldCurveShifts(data, scenario);
59 if (simMarketData_->simulateFXVols())
60 addFxVolShifts(data, scenario);
61 if (simMarketData_->simulateEquityVols())
62 addEquityVolShifts(data, scenario);
63 if (simMarketData_->simulateSwapVols())
64 addSwaptionVolShifts(data, scenario);
65 if (simMarketData_->simulateCapFloorVols())
66 addCapFloorVolShifts(data, scenario);
67 if (simMarketData_->securitySpreadsSimulate())
69 if (simMarketData_->simulateRecoveryRates())
70 addRecoveryRateShifts(data, scenario);
71 if (simMarketData_->simulateSurvivalProbabilities())
73
74 scenarios_.push_back(scenario);
75 }
76
77 DLOG("stress scenario generator: all scenarios generated.");
78}
79
81 QuantLib::ext::shared_ptr<Scenario>& scenario) {
82 for (auto d : std.fxShifts) {
83 string ccypair = d.first; // foreign + domestic;
84
85 // Is this too strict?
86 // - implemented to avoid cases where input cross FX rates are not consistent
87 // - Consider an example (baseCcy = EUR) of a GBPUSD FX trade - two separate routes to pricing
88 // - (a) call GBPUSD FX rate from sim market
89 // - (b) call GBPEUR and EURUSD FX rates, manually join them to obtain GBPUSD
90 // - now, if GBPUSD is an explicit risk factor in sim market, consider what happens
91 // - if we bump GBPUSD value and leave other FX rates unchanged (for e.g. a sensitivity analysis)
92 // - (a) the value of the trade changes
93 // - (b) the value of the GBPUSD trade stays the same
94 // in light of the above we restrict the universe of FX pairs that we support here for the time being
95 string baseCcy = simMarketData_->baseCcy();
96 string foreign = ccypair.substr(0, 3);
97 string domestic = ccypair.substr(3);
98 QL_REQUIRE((domestic == baseCcy) || (foreign == baseCcy),
99 "SensitivityScenarioGenerator does not support cross FX pairs("
100 << ccypair << ", but base currency is " << baseCcy << ")");
101
102 TLOG("Apply stress scenario to fx " << ccypair);
103
105 ShiftType type = data.shiftType;
106 bool relShift = (type == ShiftType::Relative);
107 // QL_REQUIRE(type == ShiftType::Relative, "FX scenario type must be relative");
108 Real size = data.shiftSize;
109
111 Real rate = scenario->get(key);
112 Real newRate = relShift ? rate * (1.0 + size) : (rate + size);
113 scenario->add(RiskFactorKey(RiskFactorKey::KeyType::FXSpot, ccypair),
114 stressData_->useSpreadedTermStructures() ? newRate / rate : newRate);
115 }
116 DLOG("FX scenarios done");
117}
118
120 QuantLib::ext::shared_ptr<Scenario>& scenario) {
121 for (auto d : std.equityShifts) {
122 string equity = d.first;
124 ShiftType type = data.shiftType;
125 bool relShift = (type == ShiftType::Relative);
126 // QL_REQUIRE(type == ShiftType::Relative, "FX scenario type must be relative");
127 Real size = data.shiftSize;
128
130 Real rate = baseScenarioAbsolute_->get(key);
131
132 Real newRate = relShift ? rate * (1.0 + size) : (rate + size);
134 stressData_->useSpreadedTermStructures() ? newRate / rate : newRate);
135 }
136 DLOG("Equity scenarios done");
137}
138
140 QuantLib::ext::shared_ptr<Scenario>& scenario) {
141 Date asof = baseScenario_->asof();
142
143 for (auto d : std.discountCurveShifts) {
144 string ccy = d.first;
145 TLOG("Apply stress scenario to discount curve " << ccy);
146
147 Size n_ten = simMarketData_->yieldCurveTenors(ccy).size();
148 // original curves' buffer
149 std::vector<Real> zeros(n_ten);
150 std::vector<Real> times(n_ten);
151 // buffer for shifted zero curves
152 std::vector<Real> shiftedZeros(n_ten);
153
155 ShiftType shiftType = data.shiftType;
156 //DayCounter dc = parseDayCounter(simMarketData_->yieldCurveDayCounter(ccy));
157 DayCounter dc;
158 if(auto s = simMarket_.lock()) {
159 dc = s->discountCurve(ccy)->dayCounter();
160 } else {
161 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
162 }
163
164 for (Size j = 0; j < n_ten; ++j) {
165 Date d = asof + simMarketData_->yieldCurveTenors(ccy)[j];
166 times[j] = dc.yearFraction(asof, d);
168 Real quote = baseScenarioAbsolute_->get(key);
169 zeros[j] = -std::log(quote) / times[j];
170 }
171
172 std::vector<Period> shiftTenors = data.shiftTenors;
173 QL_REQUIRE(shiftTenors.size() > 0, "Discount shift tenors not specified");
174 std::vector<Real> shifts = data.shifts;
175 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
176 std::vector<Time> shiftTimes(shiftTenors.size());
177 for (Size j = 0; j < shiftTenors.size(); ++j)
178 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
179
180 // apply zero rate shift at tenor point j
181 for (Size j = 0; j < shiftTenors.size(); ++j)
182 applyShift(j, shifts[j], true, shiftType, shiftTimes, zeros, times, shiftedZeros, j == 0 ? true : false);
183
184 // store shifted discount curve in the scenario
185 for (Size k = 0; k < n_ten; ++k) {
187 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
188 if (stressData_->useSpreadedTermStructures()) {
189 Real discount = exp(-zeros[k] * times[k]);
190 scenario->add(key, shiftedDiscount / discount);
191 } else {
192 scenario->add(key, shiftedDiscount);
193 }
194 }
195 }
196 DLOG("Discount curve stress scenarios done");
197}
198
200 QuantLib::ext::shared_ptr<Scenario>& scenario) {
201 Date asof = baseScenario_->asof();
202
203 for (auto d : std.survivalProbabilityShifts) {
204 string name = d.first;
205 TLOG("Apply stress scenario to " << name);
206
207 Size n_ten = simMarketData_->defaultTenors(name).size();
208 // original curves' buffer
209 std::vector<Real> zeros(n_ten);
210 std::vector<Real> times(n_ten);
211 // buffer for shifted zero curves
212 std::vector<Real> shiftedZeros(n_ten);
213
215 ShiftType shiftType = data.shiftType;
216 //DayCounter dc = parseDayCounter(simMarketData_->defaultCurveDayCounter(name));
217 DayCounter dc;
218 if(auto s = simMarket_.lock()) {
219 dc = s->defaultCurve(name)->curve()->dayCounter();
220 } else {
221 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
222 }
223
224 for (Size j = 0; j < n_ten; ++j) {
225 Date d = asof + simMarketData_->defaultTenors(name)[j];
226 times[j] = dc.yearFraction(asof, d);
228 Real quote = baseScenarioAbsolute_->get(key);
229 zeros[j] = -std::log(quote) / times[j];
230 }
231
232 std::vector<Period> shiftTenors = data.shiftTenors;
233 QL_REQUIRE(shiftTenors.size() > 0, "Survival Probability shift tenors not specified");
234 std::vector<Real> shifts = data.shifts;
235 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
236 std::vector<Time> shiftTimes(shiftTenors.size());
237 for (Size j = 0; j < shiftTenors.size(); ++j)
238 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
239
240 // apply zero rate shift at tenor point j
241 for (Size j = 0; j < shiftTenors.size(); ++j)
242 applyShift(j, shifts[j], true, shiftType, shiftTimes, zeros, times, shiftedZeros, j == 0 ? true : false);
243
244 // store shifted discount curve in the scenario
245 for (Size k = 0; k < n_ten; ++k) {
247 Real shiftedSurvivalProbability = exp(-shiftedZeros[k] * times[k]);
248 if (stressData_->useSpreadedTermStructures()) {
249 Real survivalProbability = exp(-zeros[k] * times[k]);
250 scenario->add(key, shiftedSurvivalProbability / survivalProbability);
251 } else {
252 scenario->add(key, shiftedSurvivalProbability);
253 }
254 }
255 }
256 DLOG("Default Curve stress scenarios done");
257}
258
260 QuantLib::ext::shared_ptr<Scenario>& scenario) {
261 Date asof = baseScenario_->asof();
262
263 for (auto d : std.indexCurveShifts) {
264 string indexName = d.first;
265 TLOG("Apply stress scenario to index curve " << indexName);
266
267 Size n_ten = simMarketData_->yieldCurveTenors(indexName).size();
268
269 // original curves' buffer
270 std::vector<Real> zeros(n_ten);
271 std::vector<Real> times(n_ten);
272
273 // buffer for shifted zero curves
274 std::vector<Real> shiftedZeros(n_ten);
275
277 ShiftType shiftType = data.shiftType;
278 //DayCounter dc = parseDayCounter(simMarketData_->yieldCurveDayCounter(indexName));
279 DayCounter dc;
280 if(auto s = simMarket_.lock()) {
281 dc = s->iborIndex(indexName)->forwardingTermStructure()->dayCounter();
282 } else {
283 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
284 }
285
286 for (Size j = 0; j < n_ten; ++j) {
287 Date d = asof + simMarketData_->yieldCurveTenors(indexName)[j];
288 times[j] = dc.yearFraction(asof, d);
290 Real quote = baseScenarioAbsolute_->get(key);
291 zeros[j] = -std::log(quote) / times[j];
292 }
293
294 std::vector<Period> shiftTenors = data.shiftTenors;
295 QL_REQUIRE(shiftTenors.size() > 0, "Index curve shift tenors not specified");
296 std::vector<Real> shifts = data.shifts;
297 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
298 std::vector<Time> shiftTimes(shiftTenors.size());
299 for (Size j = 0; j < shiftTenors.size(); ++j)
300 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
301
302 for (Size j = 0; j < shiftTenors.size(); ++j)
303 applyShift(j, shifts[j], true, shiftType, shiftTimes, zeros, times, shiftedZeros, j == 0 ? true : false);
304
305 // store shifted discount curve for this index in the scenario
306 for (Size k = 0; k < n_ten; ++k) {
308 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
309 if (stressData_->useSpreadedTermStructures()) {
310 Real discount = exp(-zeros[k] * times[k]);
311 scenario->add(key, shiftedDiscount / discount);
312 } else {
313 scenario->add(key, shiftedDiscount);
314 }
315 }
316 }
317 DLOG("Index curve scenarios done");
318}
319
321 QuantLib::ext::shared_ptr<Scenario>& scenario) {
322 Date asof = baseScenario_->asof();
323
324 for (auto d : std.yieldCurveShifts) {
325 string name = d.first;
326 TLOG("Apply stress scenario to yield curve " << name);
327
328 Size n_ten = simMarketData_->yieldCurveTenors(name).size();
329
330 // original curves' buffer
331 std::vector<Real> zeros(n_ten);
332 std::vector<Real> times(n_ten);
333
334 // buffer for shifted zero curves
335 std::vector<Real> shiftedZeros(n_ten);
336
338 ShiftType shiftType = data.shiftType;
339 //DayCounter dc = parseDayCounter(simMarketData_->yieldCurveDayCounter(name));
340 DayCounter dc;
341 if(auto s = simMarket_.lock()) {
342 dc = s->yieldCurve(name)->dayCounter();
343 } else {
344 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
345 }
346
347 for (Size j = 0; j < n_ten; ++j) {
348 Date d = asof + simMarketData_->yieldCurveTenors(name)[j];
349 times[j] = dc.yearFraction(asof, d);
351 Real quote = baseScenarioAbsolute_->get(key);
352 zeros[j] = -std::log(quote) / times[j];
353 }
354
355 std::vector<Period> shiftTenors = data.shiftTenors;
356 QL_REQUIRE(shiftTenors.size() > 0, "Yield curve shift tenors not specified");
357 std::vector<Real> shifts = data.shifts;
358 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
359 std::vector<Time> shiftTimes(shiftTenors.size());
360 for (Size j = 0; j < shiftTenors.size(); ++j)
361 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
362
363 for (Size j = 0; j < shiftTenors.size(); ++j) {
364 // DLOG("apply yield curve shift " << shifts[j] << " to curve " << name << " at tenor " << shiftTenors[j]
365 // << ", time " << shiftTimes[j]);
366 // apply zero rate shift at tenor point j
367 applyShift(j, shifts[j], true, shiftType, shiftTimes, zeros, times, shiftedZeros, j == 0 ? true : false);
368 }
369
370 // store shifted discount curve in the scenario
371 for (Size k = 0; k < n_ten; ++k) {
373 Real shiftedDiscount = exp(-shiftedZeros[k] * times[k]);
374 if (stressData_->useSpreadedTermStructures()) {
375 Real discount = exp(-zeros[k] * times[k]);
376 scenario->add(key, shiftedDiscount / discount);
377 } else {
378 scenario->add(key, shiftedDiscount);
379 }
380 }
381 } // end of shift curve tenors
382 DLOG("Yield curve scenarios done");
383}
384
386 QuantLib::ext::shared_ptr<Scenario>& scenario) {
387 Date asof = baseScenario_->asof();
388
389 for (auto d : std.fxVolShifts) {
390 string ccypair = d.first;
391 TLOG("Apply stress scenario to fx vol structure " << ccypair);
392
393 Size n_fxvol_exp = simMarketData_->fxVolExpiries(ccypair).size();
394
395 std::vector<Real> values(n_fxvol_exp);
396 std::vector<Real> times(n_fxvol_exp);
397
398 // buffer for shifted zero curves
399 std::vector<Real> shiftedValues(n_fxvol_exp);
400
402
403 //DayCounter dc = parseDayCounter(simMarketData_->fxVolDayCounter(ccypair));
404 DayCounter dc;
405 if (auto s = simMarket_.lock()) {
406 dc = s->fxVol(ccypair)->dayCounter();
407 } else {
408 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
409 }
410 for (Size j = 0; j < n_fxvol_exp; ++j) {
411 Date d = asof + simMarketData_->fxVolExpiries(ccypair)[j];
412
414 values[j] = baseScenarioAbsolute_->get(key);
415
416 times[j] = dc.yearFraction(asof, d);
417 }
418
419 ShiftType shiftType = data.shiftType;
420 std::vector<Period> shiftTenors = data.shiftExpiries;
421 std::vector<Time> shiftTimes(shiftTenors.size());
422 vector<Real> shifts = data.shifts;
423 QL_REQUIRE(shiftTenors.size() > 0, "FX vol shift tenors not specified");
424 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
425
426 for (Size j = 0; j < shiftTenors.size(); ++j)
427 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
428
429 // FIXME: Apply same shifts to non-ATM vectors if present
430 for (Size j = 0; j < shiftTenors.size(); ++j) {
431 // apply shift at tenor point j
432 applyShift(j, shifts[j], true, shiftType, shiftTimes, values, times, shiftedValues, j == 0 ? true : false);
433 }
434
435 for (Size k = 0; k < n_fxvol_exp; ++k) {
437 if (stressData_->useSpreadedTermStructures()) {
438 scenario->add(key, shiftedValues[k] - values[k]);
439 } else {
440 scenario->add(key, shiftedValues[k]);
441 }
442 }
443 }
444 DLOG("FX vol scenarios done");
445}
446
448 QuantLib::ext::shared_ptr<Scenario>& scenario) {
449 Date asof = baseScenario_->asof();
450
451 for (auto d : std.equityVolShifts) {
452 string equity = d.first;
453 TLOG("Apply stress scenario to equity vol structure " << equity);
454 Size n_eqvol_exp = simMarketData_->equityVolExpiries(equity).size();
455
456 std::vector<Real> values(n_eqvol_exp);
457 std::vector<Real> times(n_eqvol_exp);
458
459 // buffer for shifted zero curves
460 std::vector<Real> shiftedValues(n_eqvol_exp);
461
463
464 //DayCounter dc = parseDayCounter(simMarketData_->equityVolDayCounter(equity));
465 DayCounter dc;
466 if(auto s = simMarket_.lock()) {
467 dc = s->equityVol(equity)->dayCounter();
468 } else {
469 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
470 }
471 for (Size j = 0; j < n_eqvol_exp; ++j) {
472 Date d = asof + simMarketData_->equityVolExpiries(equity)[j];
473
475 values[j] = baseScenarioAbsolute_->get(key);
476
477 times[j] = dc.yearFraction(asof, d);
478 }
479
480 ShiftType shiftType = data.shiftType;
481 std::vector<Period> shiftTenors = data.shiftExpiries;
482 std::vector<Time> shiftTimes(shiftTenors.size());
483 vector<Real> shifts = data.shifts;
484 QL_REQUIRE(shiftTenors.size() > 0, "Equity vol shift tenors not specified");
485 QL_REQUIRE(shiftTenors.size() == shifts.size(), "shift tenor and shift size vectors do not match");
486
487 for (Size j = 0; j < shiftTenors.size(); ++j)
488 shiftTimes[j] = dc.yearFraction(asof, asof + shiftTenors[j]);
489
490 // FIXME: Apply same shifts to non-ATM vectors if present
491 for (Size j = 0; j < shiftTenors.size(); ++j) {
492 // apply shift at tenor point j
493 applyShift(j, shifts[j], true, shiftType, shiftTimes, values, times, shiftedValues, j == 0 ? true : false);
494 }
495
496 for (Size k = 0; k < n_eqvol_exp; ++k) {
498 if (stressData_->useSpreadedTermStructures()) {
499 scenario->add(key, shiftedValues[k] - values[k]);
500 } else {
501 scenario->add(key, shiftedValues[k]);
502 }
503 }
504 }
505 DLOG("Equity vol scenarios done");
506}
507
509 QuantLib::ext::shared_ptr<Scenario>& scenario) {
510 Date asof = baseScenario_->asof();
511
512 for (auto d : std.swaptionVolShifts) {
513 std::string key = d.first;
514 TLOG("Apply stress scenario to swaption vol structure '" << key << "'");
515
516 Size n_swvol_term = simMarketData_->swapVolTerms(key).size();
517 Size n_swvol_exp = simMarketData_->swapVolExpiries(key).size();
518
519 vector<vector<Real>> volData(n_swvol_exp, vector<Real>(n_swvol_term, 0.0));
520 vector<Real> volExpiryTimes(n_swvol_exp, 0.0);
521 vector<Real> volTermTimes(n_swvol_term, 0.0);
522 vector<vector<Real>> shiftedVolData(n_swvol_exp, vector<Real>(n_swvol_term, 0.0));
523
525 ShiftType shiftType = data.shiftType;
526 map<pair<Period, Period>, Real> shifts = data.shifts;
527
528 vector<Real> shiftExpiryTimes(data.shiftExpiries.size(), 0.0);
529 vector<Real> shiftTermTimes(data.shiftTerms.size(), 0.0);
530
531 DayCounter dc;
532 if(auto s = simMarket_.lock()) {
533 dc = s->swaptionVol(key)->dayCounter();
534 } else {
535 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
536 }
537
538 // cache original vol data
539 for (Size j = 0; j < n_swvol_exp; ++j) {
540 Date expiry = asof + simMarketData_->swapVolExpiries(key)[j];
541 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
542 }
543 for (Size j = 0; j < n_swvol_term; ++j) {
544 Date term = asof + simMarketData_->swapVolTerms(key)[j];
545 volTermTimes[j] = dc.yearFraction(asof, term);
546 }
547 for (Size j = 0; j < n_swvol_exp; ++j) {
548 for (Size k = 0; k < n_swvol_term; ++k) {
549 Size idx = j * n_swvol_term + k;
550
552 volData[j][k] = baseScenarioAbsolute_->get(rf);
553 }
554 }
555
556 // cache tenor times
557 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
558 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + data.shiftExpiries[j]);
559 for (Size j = 0; j < shiftTermTimes.size(); ++j)
560 shiftTermTimes[j] = dc.yearFraction(asof, asof + data.shiftTerms[j]);
561
562 // loop over shift expiries and terms
563 // FIXME: apply same shifts to all strikes when present
564 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
565 for (Size k = 0; k < shiftTermTimes.size(); ++k) {
566 // Size strikeBucket = 0; // FIXME
567 Real shift = 0.0;
568 pair<Period, Period> key(data.shiftExpiries[j], data.shiftTerms[k]);
569 if (shifts.size() == 0)
570 shift = data.parallelShiftSize;
571 else {
572 QL_REQUIRE(shifts.find(key) != shifts.end(), "swaption vol shift not found for expiry "
573 << data.shiftExpiries[j] << " and term "
574 << data.shiftTerms[k]);
575 shift = shifts[key];
576 }
577 applyShift(j, k, shift, true, shiftType, shiftExpiryTimes, shiftTermTimes, volExpiryTimes, volTermTimes,
578 volData, shiftedVolData, j == 0 && k == 0);
579 }
580 }
581
582 // add shifted vol data to the scenario
583 for (Size jj = 0; jj < n_swvol_exp; ++jj) {
584 for (Size kk = 0; kk < n_swvol_term; ++kk) {
585 Size idx = jj * n_swvol_term + kk;
587 if (stressData_->useSpreadedTermStructures()) {
588 scenario->add(rfkey, shiftedVolData[jj][kk] - volData[jj][kk]);
589 } else {
590 scenario->add(rfkey, shiftedVolData[jj][kk]);
591 }
592 }
593 }
594 }
595 DLOG("Swaption vol scenarios done");
596}
597
599 QuantLib::ext::shared_ptr<Scenario>& scenario) {
600 Date asof = baseScenario_->asof();
601
602 for (auto d : std.capVolShifts) {
603 std::string key = d.first;
604 TLOG("Apply stress scenario to cap/floor vol structure " << key);
605
606 vector<Real> volStrikes = simMarketData_->capFloorVolStrikes(key);
607 // Strikes may be empty which indicates that the optionlet structure in the simulation market is an ATM curve
608 if (volStrikes.empty()) {
609 volStrikes = {0.0};
610 }
611 Size n_cfvol_strikes = volStrikes.size();
612
613 Size n_cfvol_exp = simMarketData_->capFloorVolExpiries(key).size();
614 vector<vector<Real>> volData(n_cfvol_exp, vector<Real>(n_cfvol_strikes, 0.0));
615 vector<Real> volExpiryTimes(n_cfvol_exp, 0.0);
616 vector<vector<Real>> shiftedVolData(n_cfvol_exp, vector<Real>(n_cfvol_strikes, 0.0));
617
619
620 ShiftType shiftType = data.shiftType;
621
622 vector<Real> shiftExpiryTimes(data.shiftExpiries.size(), 0.0);
623 vector<Real> shiftStrikes = data.shiftStrikes.empty() ? volStrikes : data.shiftStrikes;
624
625 vector<vector<Real>> shifts;
626 for (size_t i = 0; i < data.shiftExpiries.size(); ++i) {
627 const auto tenor = data.shiftExpiries[i];
628 if (data.shiftStrikes.empty()){
629 const double shift = data.shifts[tenor].front();
630 shifts.push_back(std::vector<Real>(volStrikes.size(), shift));
631 } else{
632 shifts.push_back(data.shifts[tenor]);
633 }
634 }
635
636 // DayCounter dc = parseDayCounter(simMarketData_->capFloorVolDayCounter(key));
637 DayCounter dc;
638 if (auto s = simMarket_.lock()) {
639 dc = s->capFloorVol(key)->dayCounter();
640 } else {
641 QL_FAIL("Internal error: could not lock simMarket. Contact dev.");
642 }
643
644 // cache original vol data
645 for (Size j = 0; j < n_cfvol_exp; ++j) {
646 Date expiry = asof + simMarketData_->capFloorVolExpiries(key)[j];
647 volExpiryTimes[j] = dc.yearFraction(asof, expiry);
648 }
649 for (Size j = 0; j < n_cfvol_exp; ++j) {
650 for (Size k = 0; k < n_cfvol_strikes; ++k) {
651 Size idx = j * n_cfvol_strikes + k;
652 volData[j][k] =
654 }
655 }
656
657 // cache tenor times
658 for (Size j = 0; j < shiftExpiryTimes.size(); ++j)
659 shiftExpiryTimes[j] = dc.yearFraction(asof, asof + data.shiftExpiries[j]);
660
661 // loop over shift expiries, apply same shifts across all strikes
662
663 for (Size j = 0; j < shiftExpiryTimes.size(); ++j) {
664 for (Size k = 0; k < shiftStrikes.size(); ++k) {
665 applyShift(j, k, shifts[j][k], true, shiftType, shiftExpiryTimes, shiftStrikes, volExpiryTimes, volStrikes,
666 volData, shiftedVolData, j == 0 && k == 0);
667 }
668 }
669
670 // add shifted vol data to the scenario
671 for (Size jj = 0; jj < n_cfvol_exp; ++jj) {
672 for (Size kk = 0; kk < n_cfvol_strikes; ++kk) {
673 Size idx = jj * n_cfvol_strikes + kk;
675 if (stressData_->useSpreadedTermStructures()) {
676 scenario->add(rfkey, shiftedVolData[jj][kk] - volData[jj][kk]);
677 } else {
678 scenario->add(rfkey, shiftedVolData[jj][kk]);
679 }
680 }
681 }
682 }
683 DLOG("Optionlet vol scenarios done");
684}
685
687 QuantLib::ext::shared_ptr<Scenario>& scenario) {
688 for (auto d : std.securitySpreadShifts) {
689 string bond = d.first;
690 TLOG("Apply stress scenario to security spread " << bond);
692 ShiftType type = data.shiftType;
693 bool relShift = (type == ShiftType::Relative);
694 Real size = data.shiftSize;
695
697 Real base_spread = baseScenarioAbsolute_->get(key);
698
699 Real newSpread = relShift ? base_spread * (1.0 + size) : (base_spread + size);
701 stressData_->useSpreadedTermStructures() ? newSpread - base_spread : newSpread);
702 }
703 DLOG("Security spread scenarios done");
704}
705
707 QuantLib::ext::shared_ptr<Scenario>& scenario) {
708 for (auto d : std.recoveryRateShifts) {
709 string isin = d.first;
710 TLOG("Apply stress scenario to recovery rate " << isin);
712 ShiftType type = data.shiftType;
713 bool relShift = (type == ShiftType::Relative);
714 Real size = data.shiftSize;
715
717 Real base_recoveryRate = baseScenarioAbsolute_->get(key);
718 Real new_recoveryRate = relShift ? base_recoveryRate * (1.0 + size) : (base_recoveryRate + size);
720 stressData_->useSpreadedTermStructures() ? new_recoveryRate - base_recoveryRate
721 : new_recoveryRate);
722 }
723 DLOG("Recovery rate scenarios done");
724}
725
726} // namespace analytics
727} // namespace ore
Data types stored in the scenario class.
Definition: scenario.hpp:48
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.
const QuantLib::ext::weak_ptr< ScenarioSimMarket > simMarket_
const QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > simMarketData_
void addCapFloorVolShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addFxVolShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addSwaptionVolShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
StressScenarioGenerator(const QuantLib::ext::shared_ptr< StressTestScenarioData > &stressData, 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 > &stressScenarioFactory, const QuantLib::ext::shared_ptr< Scenario > &baseScenarioAbsolute=nullptr)
Constructor.
void addDiscountCurveShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
QuantLib::ext::shared_ptr< Scenario > baseScenarioAbsolute_
void addIndexCurveShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addYieldCurveShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addEquityShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addEquityVolShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addSecuritySpreadShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
QuantLib::ext::shared_ptr< ScenarioFactory > stressScenarioFactory_
void addRecoveryRateShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
void addSurvivalProbabilityShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
QuantLib::ext::shared_ptr< StressTestScenarioData > stressData_
void addFxShifts(StressTestScenarioData::StressTestData &data, QuantLib::ext::shared_ptr< Scenario > &scenario)
data
#define DLOG(text)
#define TLOG(text)
RandomVariable exp(RandomVariable x)
Size size(const ValueType &v)
Stress scenario generation.
Date asof(14, Jun, 2018)
string name