23#include <ql/instrument.hpp>
24#include <ql/instruments/capfloor.hpp>
33const static std::set<RiskFactorKey::KeyType> supportedCurveShiftTypes = {
38double computeTargetRate(
const double fairRate,
const double shift,
const ShiftType shiftType) {
39 double shiftedRate = fairRate;
43 shiftedRate *= (1.0 + shift);
48QuantLib::Period getYieldCurvePeriod(
const RiskFactorKey& rfKey,
49 const boost::shared_ptr<ScenarioSimMarketParameters>& params) {
50 QL_REQUIRE(rfKey.index < params->yieldCurveTenors(rfKey.name).size(),
"Please align pillars, internal error");
51 return params->yieldCurveTenors(rfKey.name)[rfKey.index];
55double getCurveStressShift(
const RiskFactorKey& key,
const StressTestScenarioData::StressTestData& stressScenario) {
56 const std::vector<double>* shifts =
nullptr;
58 switch (key.keytype) {
60 auto it = stressScenario.discountCurveShifts.find(key.name);
61 if (it != stressScenario.discountCurveShifts.end()) {
62 shifts = &(it->second.shifts);
67 auto it = stressScenario.yieldCurveShifts.find(key.name);
68 if (it != stressScenario.yieldCurveShifts.end()) {
69 shifts = &(it->second.shifts);
74 auto it = stressScenario.indexCurveShifts.find(key.name);
75 if (it != stressScenario.indexCurveShifts.end()) {
76 shifts = &(it->second.shifts);
81 auto it = stressScenario.survivalProbabilityShifts.find(key.name);
82 if (it != stressScenario.survivalProbabilityShifts.end()) {
83 shifts = &(it->second.shifts);
88 QL_FAIL(
"ParStressScenario to ZeroConversion: Unsupported riskfactor, can not compute time to maturity "
91 if (shifts !=
nullptr && key.index < shifts->size()) {
92 shift = shifts->at(key.index);
97double getCapFloorStressShift(
const RiskFactorKey& key,
const StressTestScenarioData::StressTestData& stressScenario,
98 const boost::shared_ptr<ScenarioSimMarketParameters>& params) {
100 auto it = stressScenario.capVolShifts.find(key.name);
101 if (it != stressScenario.capVolShifts.end()) {
102 const auto& cfData = it->second;
103 size_t nStrikes = params->capFloorVolStrikes(key.name).size();
104 size_t n = key.index;
105 size_t tenorId = n / nStrikes;
106 size_t strikeId = n % nStrikes;
107 const Period& tenor = cfData.shiftExpiries[tenorId];
108 if (cfData.shiftStrikes.empty()) {
109 shift = cfData.shifts.at(tenor).front();
111 shift = cfData.shifts.at(tenor)[strikeId];
118StressTestScenarioData::StressTestData
119removeParShiftsCopy(
const StressTestScenarioData::StressTestData& parStressScenario) {
120 StressTestScenarioData::StressTestData zeroStressScenario = parStressScenario;
121 if (parStressScenario.irCapFloorParShifts) {
122 zeroStressScenario.capVolShifts.clear();
124 if (parStressScenario.creditCurveParShifts) {
125 zeroStressScenario.survivalProbabilityShifts.clear();
127 if (parStressScenario.irCurveParShifts) {
128 zeroStressScenario.discountCurveShifts.clear();
129 zeroStressScenario.indexCurveShifts.clear();
130 zeroStressScenario.yieldCurveShifts.clear();
132 zeroStressScenario.irCapFloorParShifts =
false;
133 zeroStressScenario.irCurveParShifts =
false;
134 zeroStressScenario.creditCurveParShifts =
false;
135 return zeroStressScenario;
139std::set<RiskFactorKey::KeyType>
disabledParRates(
bool irCurveParRates,
bool irCapFloorParRates,
bool creditParRates) {
141 if (!irCurveParRates) {
146 if (!irCapFloorParRates) {
149 if (!creditParRates) {
157 const std::map<std::string, QuantLib::ext::shared_ptr<SensitivityScenarioData::CurveShiftData>>& sensiData) {
158 auto it = sensiData.find(
name);
159 if (it == sensiData.end()) {
161 "StressScenario",
name,
"Par Shift to zero conversion",
162 "no par sensitivity scenario found. Please add par sensi config").
log();
165 auto parShiftData = ext::dynamic_pointer_cast<SensitivityScenarioData::CurveShiftParData>(it->second);
166 if (parShiftData ==
nullptr) {
168 "no par sensitivity scenario found. Please add par sensi config")
172 const size_t nParShifts = parShiftData->shiftTenors.size();
173 const size_t nStressShifts = stressShiftData.
shiftTenors.size();
174 if (nParShifts != nStressShifts) {
176 "StressScenario",
name,
"Par Shift to zero conversion",
177 "mismatch between tenors, we have " +
to_string(nParShifts) +
" parInstruments defined but " +
179 " shifts in the scenario. Please align pillars of stress test and par sensi config")
183 for (
size_t i = 0; i < nParShifts; ++i){
184 if (parShiftData->shiftTenors[i] != stressShiftData.
shiftTenors[i]){
186 "tenors are not aligned, " +
to_string(i) +
" par Pillar is " +
187 to_string(parShiftData->shiftTenors[i]) +
188 " vs stress shift piller " +
190 ". Please align pillars of stress test and par sensi config")
201 const std::map<std::string, QuantLib::ext::shared_ptr<SensitivityScenarioData::CapFloorVolShiftData>>& sensiData){
203 auto it = sensiData.find(
name);
204 if (it == sensiData.end()) {
206 "StressScenario",
name,
"Par Shift to zero conversion",
207 "no par cap floor sensitivity scenario found. Please add par sensi config")
211 auto parShiftData = ext::dynamic_pointer_cast<SensitivityScenarioData::CapFloorVolShiftParData>(it->second);
212 if (parShiftData ==
nullptr) {
214 "StressScenario",
name,
"Par Shift to zero conversion",
215 "no par cap floor sensitivity scenario found. Please add par sensi config")
219 const size_t nParShifts = parShiftData->shiftExpiries.size();
220 const size_t nStressShifts = stressShiftData.
shiftExpiries.size();
221 if (nParShifts != nStressShifts) {
223 "StressScenario",
name,
"Par Shift to zero conversion",
224 "mismatch between capFloor expiries, we have " +
to_string(nParShifts) +
" parInstruments defined but " +
226 " shifts in the scenario. Please align pillars of stress test and par sensi config")
230 for (
size_t i = 0; i < nParShifts; ++i) {
231 if (parShiftData->shiftExpiries[i] != stressShiftData.
shiftExpiries[i]) {
233 "StressScenario",
name,
"Par Shift to zero conversion",
234 "CapFloor expiries are not aligned, " +
to_string(i) +
" CapFloor Pillar is " +
235 to_string(parShiftData->shiftExpiries[i]) +
" vs stress shift piller " +
237 ". Please align pillars of stress test and par sensi config")
243 const size_t nParStrikes = parShiftData->shiftStrikes.size();
244 const size_t nSressStrikes = stressShiftData.
shiftStrikes.size();
246 if (nParStrikes != nSressStrikes) {
248 "StressScenario",
name,
"Par Shift to zero conversion",
249 "mismatch between capFloor strikes, we have " +
to_string(nParStrikes) +
" par strikes defined but " +
251 " strikes in the scenario. Please align strikes of stress test and par sensi config")
255 for (
size_t i = 0; i < nParStrikes; ++i) {
256 if (parShiftData->shiftStrikes[i] != stressShiftData.
shiftStrikes[i]) {
258 "StressScenario",
name,
"Par Shift to zero conversion",
259 "CapFloor expiries are not aligned, " +
to_string(i) +
" CapFloor strike is " +
260 to_string(parShiftData->shiftStrikes[i]) +
" vs stress shift strike " +
262 ". Please align strikes of stress test and par sensi config")
274 DLOG(
"Check if the par stresstest scenario is compatible with the parInstruments");
279 DLOG(
"Check if pillars between stress test and sensi config are alligned for discount curve " << ccy);
283 for (
const auto& [indexName, curveShifts] : parStressScenario.
indexCurveShifts) {
284 DLOG(
"Check if pillars between stress test and sensi config are alligned for index curve " << indexName);
288 for (
const auto& [curveName, curveShifts] : parStressScenario.
yieldCurveShifts) {
289 DLOG(
"Check if pillars between stress test and sensi config are alligned for yield curve " << curveName);
296 DLOG(
"Check if pillars between stress test and sensi config are alligned for credit curve " << curveName);
302 for (
const auto& [capSurfaceName, capShifts] : parStressScenario.
capVolShifts) {
303 DLOG(
"Check if pillars and strikes between stress test and sensi config are alligned for cap floor surface "
314 const QuantLib::Date&
asof,
const std::vector<RiskFactorKey>& sortedParInstrumentRiskFactorKeys,
315 const QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarketParameters>& simMarketParams,
316 const QuantLib::ext::shared_ptr<ore::analytics::SensitivityScenarioData>& sensiScenarioData,
317 const QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarket>& simMarket,
319 : asof_(
asof), sortedParInstrumentRiskFactorKeys_(sortedParInstrumentRiskFactorKeys),
320 simMarketParams_(simMarketParams), sensiScenarioData_(sensiScenarioData), simMarket_(simMarket),
321 parInstruments_(parInstruments), useSpreadedTermStructure_(useSpreadedTermStructure) {}
327 return parStressScenario;
331 WLOG(
"Can not convert scenario " << parStressScenario.
label <<
" Skip it and apply all shifts as zero shifts.");
332 return parStressScenario;
337 LOG(
"ParStressConverter: Scenario " << parStressScenario.
label <<
" has IR Curve Par Shifts = "
340 LOG(
"ParStressConverter: Scenario " << parStressScenario.
label <<
" has CapFloor Par Shifts = "
343 LOG(
"ParStressConverter: Scenario " << parStressScenario.
label <<
" has Credit Par Shifts = "
346 std::set<RiskFactorKey::KeyType> excludedParRates =
350 LOG(
"ParStressConverter: Copy scenario and remove parShifts from scenario");
351 auto zeroStressScenario = removeParShiftsCopy(parStressScenario);
352 DLOG(
"ParStressConverter: Clone base scenario");
353 auto zeroSimMarketScenario =
simMarket_->baseScenario()->clone();
356 std::vector<RiskFactorKey> relevantParKeys;
357 std::map<RiskFactorKey, double> fairRates;
358 std::map<RiskFactorKey, double> baseScenarioValues;
359 std::map<RiskFactorKey, double> targets;
360 LOG(
"ParStressConverter: Compute fair rate and target rate for all ParInstruments");
362 if (excludedParRates.count(rfKey.keytype) == 0 &&
363 (supportedCurveShiftTypes.count(rfKey.keytype) == 1 ||
365 relevantParKeys.push_back(rfKey);
368 const double target =
370 const double baseScenarioValue =
simMarket_->baseScenario()->get(rfKey);
371 const double baseScenarioAbsoluteValue =
simMarket_->baseScenarioAbsolute()->get(rfKey);
372 DLOG(
"ParStressConverter: ParInstrument "
373 << rfKey <<
", fair rate = " << fairRate <<
", target rate = " << target <<
", baseScenarioValue = "
374 << baseScenarioValue <<
", baseScenarioAbsoluteValue = " << baseScenarioAbsoluteValue);
375 fairRates[rfKey] = fairRate;
376 targets[rfKey] = target;
377 baseScenarioValues[rfKey] = baseScenarioValue;
379 DLOG(
"Skip parInsturment " << rfKey <<
" the shifts for this risk factor type are in zero domain.");
383 LOG(
"ParStressConverter: Imply zero shifts");
384 std::map<RiskFactorKey, double> shifts;
385 for (
const auto& rfKey : relevantParKeys) {
386 DLOG(
"Imply zero shifts for parInstrument " << rfKey);
387 const double target = targets[rfKey];
388 auto targetFunction = [
this, &target, &rfKey, &zeroSimMarketScenario](
double x) {
389 zeroSimMarketScenario->add(rfKey, x);
390 simMarket_->applyScenario(zeroSimMarketScenario);
396 DLOG(
"ParStressConverter: Try to imply zero rate" << rfKey <<
" with bounds << " <<
lowerBound(rfKey) <<
","
400 }
catch (
const std::exception& e) {
401 ALOG(
"ParStressConverter: Couldn't find a solution to imply a zero rate for parRate " << rfKey <<
", got "
403 targetValue =
simMarket_->baseScenario()->get(rfKey);
405 zeroSimMarketScenario->add(rfKey, targetValue);
409 simMarket_->applyScenario(zeroSimMarketScenario);
410 DLOG(
"ParStressConverter: Implied Scenario");
411 DLOG(
"parInstrument;fairRate;targetFairRate;zeroBaseValue;shift");
412 for (
const auto& rfKey : relevantParKeys) {
413 DLOG(rfKey <<
";" <<
impliedParRate(rfKey) <<
";" << targets[rfKey] <<
";" << baseScenarioValues[rfKey] <<
";"
417 return zeroStressScenario;
421 boost::shared_ptr<QuantLib::TermStructure> ts;
422 QuantLib::Period tenor;
433 ts = *
simMarket_->iborIndex(rfKey.
name)->forwardingTermStructure();
439 "Please align pillars, internal error");
447 "Internal Error: ParStressScenarioConversion, simmarket and par sensitivity instruments are not aligned.");
451 QL_FAIL(
"ParStressScenario to ZeroConversion: Unsupported riskfactor, can not compute time to maturity "
454 return ts->dayCounter().yearFraction(
asof_,
asof_ + tenor);
459 size_t n = rfKey.
index;
460 size_t tenorId = n / nStrikes;
461 size_t strikeId = n % nStrikes;
462 return {tenorId, strikeId};
466 double baseValue)
const {
467 DLOG(
"compute shift for" << rfKey <<
" targetZeroValue " << targetValue <<
" baseValue " << baseValue);
477 shift = -std::log(targetValue / baseValue) / ttm;
479 shift = -std::log(targetValue) / ttm;
481 DLOG(
"Shift = " << shift);
486 shift = targetValue - baseValue;
492 QL_FAIL(
"ShiftSizeForScenario: Unsupported par instruments type " << rfKey.
keytype);
500 }
else if (supportedCurveShiftTypes.count(key.
keytype) == 1) {
503 "Internal error, trying to compute parRate but havent build parRateHelper");
506 QL_FAIL(
"Unsupported parRate");
515 return getCurveStressShift(key, stressScenario);
double maturityTime(const RiskFactorKey &key) const
compute the time to tenor of the risk factor key
bool useSpreadedTermStructure_
bool scenarioCanBeConverted(const StressTestScenarioData::StressTestData &parStressScenario) const
check if the scenario can be converted
QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarketParameters > simMarketParams_
double shiftsSizeForScenario(const RiskFactorKey rfKey, double targetValue, double baseValue) const
convert the scenario value to the corresponding zero shift size for the stress test data
double getStressShift(const RiskFactorKey &key, const StressTestScenarioData::StressTestData &stressScenario) const
get the par stress shift size from stress test data
QuantLib::ext::shared_ptr< ore::analytics::SensitivityScenarioData > sensiScenarioData_
ore::analytics::StressTestScenarioData::StressTestData convertScenario(const StressTestScenarioData::StressTestData &scenario) const
Convert par shifts in a stress scenario to zero shifts.
double impliedParRate(const RiskFactorKey &key) const
Compute the implied fair rate of the par instrument.
const std::vector< RiskFactorKey > sortedParInstrumentRiskFactorKeys_
double maxDiscountFactor_
ParStressScenarioConverter(const QuantLib::Date &asof, const std::vector< RiskFactorKey > &sortedParInstrumentRiskFactorKeys, const QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarketParameters > &simMarketParams, const QuantLib::ext::shared_ptr< ore::analytics::SensitivityScenarioData > &sensiScenarioData, const QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarket > &simMarket, const ore::analytics::ParSensitivityInstrumentBuilder::Instruments &parInstruments, bool useSpreadedTermStructure)
double upperBound(const RiskFactorKey key) const
double minDiscountFactor_
const ore::analytics::ParSensitivityInstrumentBuilder::Instruments & parInstruments_
void updateTargetStressTestScenarioData(StressTestScenarioData::StressTestData &stressScenario, const RiskFactorKey &key, const double zeroShift) const
add zero shifts in the stress test data
std::pair< size_t, size_t > getCapFloorTenorAndStrikeIds(const RiskFactorKey &rfKey) const
get the strike and tenor from a optionlet riskfactor key
double lowerBound(const RiskFactorKey key) const
QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarket > simMarket_
Data types stored in the scenario class.
std::string name
Key name.
std::set< RiskFactorKey::KeyType > disabledParRates(bool irCurveParRates, bool irCapFloorParRates, bool creditParRates)
Volatility impliedVolatility(const QuantLib::CapFloor &cap, Real targetValue, const Handle< YieldTermStructure > &d, Volatility guess, VolatilityType type, Real displacement)
Real impliedQuote(const QuantLib::ext::shared_ptr< Instrument > &i)
bool checkCapFloorShiftData(const std::string &name, const StressTestScenarioData::CapFloorVolShiftData &stressShiftData, const std::map< std::string, QuantLib::ext::shared_ptr< SensitivityScenarioData::CapFloorVolShiftData > > &sensiData)
Checks the the strikes and expiries of cap floors in stresstest scenario are alligned with par sensit...
bool checkCurveShiftData(const std::string &name, const StressTestScenarioData::CurveShiftData &stressShiftData, const std::map< std::string, QuantLib::ext::shared_ptr< SensitivityScenarioData::CurveShiftData > > &sensiData)
Checks the the tenors for curves in a stresstest scenario are alligned with par sensitivity config.
std::string to_string(const LocationInfo &l)
Convert all par shifts in a single stress test scenario to a zero shifts.
A class to hold the parametrisation for building sensitivity scenarios.
std::map< ore::analytics::RiskFactorKey, QuantLib::ext::shared_ptr< QuantLib::Instrument > > parHelpers_
par helpers (all except cap/floors)
std::map< Period, vector< Real > > shifts
vector< Period > shiftExpiries
vector< double > shiftStrikes
vector< Period > shiftTenors
map< string, CurveShiftData > discountCurveShifts
bool creditCurveParShifts
map< string, CurveShiftData > yieldCurveShifts
bool containsParShifts() const
map< string, CurveShiftData > survivalProbabilityShifts
map< string, CapFloorVolShiftData > capVolShifts
map< string, CurveShiftData > indexCurveShifts