Skip to content

Commit 8cd91c1

Browse files
mgronckijenkins
authored andcommitted
QPR-12173 take shift sizes in decomposedsensitivitystream into account
1 parent 9139a3b commit 8cd91c1

2 files changed

Lines changed: 109 additions & 44 deletions

File tree

OREAnalytics/orea/engine/decomposedsensitivitystream.cpp

Lines changed: 96 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,43 @@
2525
namespace ore {
2626
namespace analytics {
2727

28+
namespace {
29+
30+
double fxRiskShiftSize(const std::string ccy, const std::string baseCcy,
31+
boost::shared_ptr<SensitivityScenarioData> ssd) {
32+
auto fxpair = ccy + baseCcy;
33+
auto fxShiftSizeIt = ssd->fxShiftData().find(fxpair);
34+
QL_REQUIRE(fxShiftSizeIt != ssd->fxShiftData().end(), "Couldn't find shiftsize for " << fxpair);
35+
QL_REQUIRE(fxShiftSizeIt->second.shiftType == "Relative",
36+
"Requires a relative fxSpot shift for index decomposition");
37+
return fxShiftSizeIt->second.shiftSize;
38+
}
39+
40+
double eqRiskShiftSize(const std::string equityName, boost::shared_ptr<SensitivityScenarioData> ssd) {
41+
auto eqShiftSizeIt = ssd->equityShiftData().find(equityName);
42+
QL_REQUIRE(eqShiftSizeIt != ssd->equityShiftData().end(), "Couldn't find a shift size for " << equityName);
43+
QL_REQUIRE(eqShiftSizeIt->second.shiftType == "Relative",
44+
"Requires a relative eqSpot shift for index decomposition");
45+
return eqShiftSizeIt->second.shiftSize;
46+
}
47+
48+
} // namespace
49+
2850
DecomposedSensitivityStream::DecomposedSensitivityStream(
29-
const boost::shared_ptr<SensitivityStream>& ss,
51+
const boost::shared_ptr<SensitivityStream>& ss, const std::string& baseCurrency,
3052
std::map<std::string, std::map<std::string, double>> defaultRiskDecompositionWeights,
3153
const std::set<std::string>& eqComDecompositionTradeIds,
3254
const std::map<std::string, std::map<std::string, double>>& currencyHedgedIndexQuantities,
3355
const boost::shared_ptr<ore::data::ReferenceDataManager>& refDataManager,
3456
const boost::shared_ptr<ore::data::CurveConfigurations>& curveConfigs,
57+
const boost::shared_ptr<SensitivityScenarioData>& scenarioData,
3558
const boost::shared_ptr<ore::data::Market>& todaysMarket)
36-
: ss_(ss), defaultRiskDecompositionWeights_(defaultRiskDecompositionWeights),
59+
: ss_(ss), baseCurrency_(baseCurrency), defaultRiskDecompositionWeights_(defaultRiskDecompositionWeights),
3760
eqComDecompositionTradeIds_(eqComDecompositionTradeIds),
3861
currencyHedgedIndexQuantities_(currencyHedgedIndexQuantities), refDataManager_(refDataManager),
39-
curveConfigs_(curveConfigs), todaysMarket_(todaysMarket) {
62+
curveConfigs_(curveConfigs), ssd_(scenarioData), todaysMarket_(todaysMarket) {
4063
reset();
41-
decompose_ = !defaultRiskDecompositionWeights_.empty() ||
42-
(!eqComDecompositionTradeIds_.empty() && refDataManager_ != nullptr);
64+
decompose_ = !defaultRiskDecompositionWeights_.empty() || !eqComDecompositionTradeIds_.empty();
4365
}
4466

4567
//! Returns the next SensitivityRecord in the stream after filtering
@@ -64,13 +86,13 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decompose(const Sens
6486
bool isSurvivalProbSensi = record.key_1.keytype == RiskFactorKey::KeyType::SurvivalProbability;
6587
bool isEquitySpotSensi = record.key_1.keytype == RiskFactorKey::KeyType::EquitySpot;
6688
bool isCommoditySpotSensi = record.key_1.keytype == RiskFactorKey::KeyType::CommodityCurve;
67-
68-
89+
6990
bool hasEquityIndexRefData =
7091
refDataManager_ != nullptr && refDataManager_->hasData("EquityIndex", record.key_1.name);
7192
bool hasCurrencyHedgedIndexRefData =
7293
refDataManager_ != nullptr && refDataManager_->hasData("CurrencyHedgedEquityIndex", record.key_1.name);
73-
bool hasCommodityRefData = refDataManager_ != nullptr && refDataManager_->hasData("CommodityIndex", record.key_1.name);
94+
bool hasCommodityRefData =
95+
refDataManager_ != nullptr && refDataManager_->hasData("CommodityIndex", record.key_1.name);
7496

7597
try {
7698
if (isSurvivalProbSensi && tradeIdValidSurvival && isNotCrossGamma) {
@@ -79,21 +101,30 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decompose(const Sens
79101
return decomposeEquityRisk(record);
80102
} else if (isEquitySpotSensi && tradeIdValid && hasCurrencyHedgedIndexRefData && isNotCrossGamma) {
81103
return decomposeCurrencyHedgedIndexRisk(record);
82-
} else if ((isEquitySpotSensi || isCommoditySpotSensi) && tradeIdValid && hasCommodityRefData && isNotCrossGamma) {
104+
} else if ((isEquitySpotSensi || isCommoditySpotSensi) && tradeIdValid && hasCommodityRefData &&
105+
isNotCrossGamma) {
83106
return decomposeCommodityRisk(record);
84107
} else if ((isEquitySpotSensi || isCommoditySpotSensi) && tradeIdValid && isNotCrossGamma) {
85108
auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
86109
StructuredAnalyticsErrorMessage(
87-
"CRIF Generation", "Index decomposition failed",
110+
"Sensitivity Decomposition", "Index decomposition failed",
88111
"Cannot decompose equity index delta (" + record.key_1.name +
89112
") for trade: no reference data found. Continuing without decomposition.",
90113
subFields)
91114
.log();
92115
}
93116
} catch (const std::exception& e) {
94-
117+
auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
118+
StructuredAnalyticsErrorMessage(
119+
"Sensitivity Decomposition", "Index decomposition failed",
120+
"Cannot decompose equity index delta (" + record.key_1.name + ") for trade:" + e.what(), subFields)
121+
.log();
95122
} catch (...) {
96-
123+
auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
124+
StructuredAnalyticsErrorMessage(
125+
"Sensitivity Decomposition", "Index decomposition failed",
126+
"Cannot decompose equity index delta (" + record.key_1.name + ") for trade: unkown error", subFields)
127+
.log();
97128
}
98129
return {record};
99130
}
@@ -117,7 +148,9 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeEquityRisk(
117148
if (decomposeEquityIndexData != nullptr) {
118149
auto decompResults =
119150
decomposeEqComIndexRisk(sr.delta, decomposeEquityIndexData, ore::data::CurveSpec::CurveType::Equity);
120-
return createDecompositionRecords(sr, decompResults);
151+
scaleFxRisk(decompResults.fxRisk, decomposeEquityIndexData->id());
152+
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency,
153+
sr);
121154
} else {
122155
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
123156
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
@@ -153,19 +186,27 @@ DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityR
153186
QL_REQUIRE(quantity != QuantLib::Null<double>(),
154187
"CurrencyHedgedIndexDecomposition failed, index quantity cannot be NULL.");
155188

156-
double unhedgedDelta =
157-
decomposeCurrencyHedgedIndexHelper->unhedgedDelta(sr.delta, quantity, today, todaysMarket_);
189+
double hedgedExposure = sr.delta / eqRiskShiftSize(sr.key_1.name, ssd_);
190+
191+
double unhedgedExposure =
192+
decomposeCurrencyHedgedIndexHelper->unhedgedSpotExposure(hedgedExposure, quantity, today, todaysMarket_);
193+
194+
double unhedgedDelta = unhedgedExposure * eqRiskShiftSize(sr.key_1.name, ssd_);
158195

159196
auto decompResults =
160197
decomposeEqComIndexRisk(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingRefData(),
161198
ore::data::CurveSpec::CurveType::Equity);
162-
199+
scaleFxRisk(decompResults.fxRisk, decomposeCurrencyHedgedIndexHelper->indexName());
163200
// Correct FX Delta from FxForwards
164201
for (const auto& [ccy, fxRisk] :
165-
decomposeCurrencyHedgedIndexHelper->fxSpotRiskFromForwards(quantity, today, todaysMarket_)) {
166-
decompResults.fxRisk[ccy] = decompResults.fxRisk[ccy] - fxRisk;
202+
decomposeCurrencyHedgedIndexHelper->fxSpotRiskFromForwards(quantity, today, todaysMarket_, 1.0)) {
203+
decompResults.fxRisk[ccy] =
204+
decompResults.fxRisk[ccy] -
205+
fxRisk *
206+
fxRiskShiftSize(ccy, baseCurrency_, ssd_); //*todaysMarket_->fxSpot(ccy + baseCurrency_)->value();
167207
}
168-
return createDecompositionRecords(sr, decompResults);
208+
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk,
209+
decomposeCurrencyHedgedIndexHelper->indexCurrency(), sr);
169210
} else {
170211
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
171212
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
@@ -183,20 +224,22 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeCommodityRi
183224
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
184225
auto decompResults =
185226
decomposeEqComIndexRisk(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Commodity);
186-
return createDecompositionRecords(sr, decompResults);
187-
} else if (refDataManager_->hasData("EquityIndex", sr.key_1.name)) {
188-
auto refDatum = refDataManager_->getData("EquityIndex", sr.key_1.name);
189-
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
190-
auto decompResults =
191-
decomposeEqComIndexRisk(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Commodity);
192-
return createDecompositionRecords(sr, decompResults);
227+
scaleFxRisk(decompResults.fxRisk, indexRefDatum->id());
228+
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency,
229+
sr);
193230
} else {
231+
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
232+
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
233+
"Cannot decompose equity index delta (" + sr.key_1.name +
234+
") for trade: no reference data found. Continuing without decomposition.",
235+
subFields)
236+
.log();
194237
return {sr};
195238
}
196239
}
197-
240+
198241
void DecomposedSensitivityStream::reset() {
199-
ss_.reset();
242+
ss_->reset();
200243
decomposedRecords_.clear();
201244
itCurrent_ = decomposedRecords_.begin();
202245
}
@@ -227,42 +270,55 @@ DecomposedSensitivityStream::decomposeEqComIndexRisk(double delta,
227270
for (auto c : ird->underlyings()) {
228271
results.equityDelta[c.first] += delta * c.second;
229272
// try look up currency in reference data and add if FX delta risk if necessary
273+
std::string constituentCcy = results.indexCurrency;
230274
if (curveConfigs_->has(curveType, c.first)) {
231275
auto constituentCcy = curveType == ore::data::CurveSpec::CurveType::Equity
232276
? curveConfigs_->equityCurveConfig(c.first)->currency()
233277
: curveConfigs_->commodityCurveConfig(c.first)->currency();
234-
results.fxRisk[constituentCcy] += delta * c.second;
278+
235279
} else {
236280
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition",
237281
"Cannot find currency for equity " + c.first +
238282
" from curve configs, fallback to use index currency (" +
239283
results.indexCurrency + ")")
240284
.log();
241-
results.fxRisk[results.indexCurrency] += delta * c.second;
285+
}
286+
if (constituentCcy != baseCurrency_) {
287+
results.fxRisk[constituentCcy] +=
288+
delta * c.second; //* todaysMarket_->fxSpot(constituentCcy+baseCurrency_)->value();
242289
}
243290
}
244291
return results;
245292
}
246293

247294
std::vector<SensitivityRecord> DecomposedSensitivityStream::createDecompositionRecords(
248-
const SensitivityRecord& sr,
249-
const DecomposedSensitivityStream::EqComIndexDecompositionResults& decompResults) const {
295+
const std::map<std::string, double>& eqDeltas, const std::map<std::string, double>& fxDeltas,
296+
const std::string indexCurrency, const SensitivityRecord& sr) const {
250297
std::vector<SensitivityRecord> records;
251-
for (auto [underlying, delta] : decompResults.equityDelta) {
298+
for (auto [underlying, delta] : eqDeltas) {
252299
RiskFactorKey underlyingKey(sr.key_1.keytype, underlying, sr.key_1.index);
253-
records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, "", sr.shift_1, RiskFactorKey(), "",
254-
sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
300+
records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, sr.desc_1, sr.shift_1, RiskFactorKey(),
301+
"", sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
255302
}
256303
// Add aggregated FX Deltas
257-
for (auto [ccy, delta] : decompResults.fxRisk) {
258-
if (ccy != decompResults.indexCurrency) {
259-
RiskFactorKey underlyingKey(RiskFactorKey::KeyType::FXSpot, ccy + "USD", 0);
260-
records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, "", sr.shift_1, RiskFactorKey(),
261-
"", sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
304+
for (auto [ccy, delta] : fxDeltas) {
305+
if (ccy != indexCurrency) {
306+
RiskFactorKey underlyingKey(RiskFactorKey::KeyType::FXSpot, ccy + baseCurrency_, 0);
307+
records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, sr.desc_1, sr.shift_1,
308+
RiskFactorKey(), "", sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
262309
}
263310
}
264311
return records;
265312
}
266313

314+
void DecomposedSensitivityStream::scaleFxRisk(std::map<std::string, double>& fxRisk,
315+
const std::string& equityName) const {
316+
// Eq/Comm Shift to FX Shift Conversion
317+
auto eqShift = eqRiskShiftSize(equityName, ssd_);
318+
for (auto& [ccy, fxdelta] : fxRisk) {
319+
fxdelta = fxdelta * fxRiskShiftSize(ccy, baseCurrency_, ssd_) / eqShift;
320+
}
321+
}
322+
267323
} // namespace analytics
268324
} // namespace ore

OREAnalytics/orea/engine/decomposedsensitivitystream.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#pragma once
2424

25+
#include <orea/scenario/sensitivityscenariodata.hpp>
2526
#include <orea/engine/sensitivitystream.hpp>
2627
#include <ored/configuration/curveconfigurations.hpp>
2728
#include <ored/marketdata/market.hpp>
@@ -40,12 +41,13 @@ class DecomposedSensitivityStream : public SensitivityStream {
4041
/*! Constructor providing the weights for the credit index decomposition and the ids and reference data used for
4142
*/
4243
DecomposedSensitivityStream(
43-
const boost::shared_ptr<SensitivityStream>& ss,
44+
const boost::shared_ptr<SensitivityStream>& ss,const std::string& baseCurrency,
4445
std::map<std::string, std::map<std::string, double>> defaultRiskDecompositionWeights = {},
4546
const std::set<std::string>& eqComDecompositionTradeIds = {},
4647
const std::map<std::string, std::map<std::string, double>>& currencyHedgedIndexQuantities = {},
4748
const boost::shared_ptr<ore::data::ReferenceDataManager>& refDataManager = nullptr,
4849
const boost::shared_ptr<ore::data::CurveConfigurations>& curveConfigs = nullptr,
50+
const boost::shared_ptr<SensitivityScenarioData>& scenarioData = nullptr,
4951
const boost::shared_ptr<ore::data::Market>& todaysMarket = nullptr);
5052
//! Returns the next SensitivityRecord in the stream after filtering
5153
SensitivityRecord next() override;
@@ -70,14 +72,20 @@ class DecomposedSensitivityStream : public SensitivityStream {
7072
const boost::shared_ptr<ore::data::IndexReferenceDatum>& ird,
7173
ore::data::CurveSpec::CurveType curveType) const;
7274

73-
std::vector<SensitivityRecord>
74-
createDecompositionRecords(const SensitivityRecord& sr, const EqComIndexDecompositionResults& decompResults) const;
75+
std::vector<SensitivityRecord> createDecompositionRecords(const std::map<std::string, double>& eqDeltas,
76+
const std::map<std::string, double>& fxDeltas,
77+
const std::string indexCurrency,
78+
const SensitivityRecord& sr) const;
79+
80+
// Scale the fx risk entries from the index decomposition
81+
void scaleFxRisk(std::map<std::string, double>& fxRisk, const std::string& equityName) const;
7582

7683
std::vector<SensitivityRecord> decomposedRecords_;
7784
std::vector<SensitivityRecord>::iterator itCurrent_;
7885

7986
//! The underlying sensitivity stream that has been wrapped
8087
boost::shared_ptr<SensitivityStream> ss_;
88+
std::string baseCurrency_;
8189
//! map of trade ids to the basket consituents with their resp. weights
8290
std::map<std::string, std::map<std::string, double>> defaultRiskDecompositionWeights_;
8391
//! list of trade id, for which a equity index decomposition should be applied
@@ -87,9 +95,10 @@ class DecomposedSensitivityStream : public SensitivityStream {
8795
//! refDataManager holding the equity and commodity index decomposition weights
8896
boost::shared_ptr<ore::data::ReferenceDataManager> refDataManager_;
8997
boost::shared_ptr<ore::data::CurveConfigurations> curveConfigs_;
98+
boost::shared_ptr<SensitivityScenarioData> ssd_;
9099
// needed for currency hedged index decomposition
91100
boost::shared_ptr<ore::data::Market> todaysMarket_;
92-
101+
// flag if decompose is possible
93102
bool decompose_;
94103
};
95104

0 commit comments

Comments
 (0)