Skip to content

Commit ab62068

Browse files
mgronckijenkins
authored andcommitted
QPR-12173 refactoring DecomposedSensitivityStream
1 parent 44e8d85 commit ab62068

5 files changed

Lines changed: 168 additions & 141 deletions

File tree

OREAnalytics/orea/engine/decomposedsensitivitystream.cpp

Lines changed: 126 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,7 @@
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
28+
namespace {} // namespace
4929

5030
DecomposedSensitivityStream::DecomposedSensitivityStream(
5131
const boost::shared_ptr<SensitivityStream>& ss, const std::string& baseCurrency,
@@ -98,12 +78,18 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decompose(const Sens
9878
if (isSurvivalProbSensi && tradeIdValidSurvival && isNotCrossGamma) {
9979
return decomposeSurvivalProbability(record);
10080
} else if (isEquitySpotSensi && tradeIdValid && hasEquityIndexRefData && isNotCrossGamma) {
101-
return decomposeEquityRisk(record);
81+
auto decompResults =
82+
indexDecomposition(record.delta, record.key_1.name, ore::data::CurveSpec::CurveType::Equity);
83+
return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, decompResults.indexCurrency,
84+
record);
10285
} else if (isEquitySpotSensi && tradeIdValid && hasCurrencyHedgedIndexRefData && isNotCrossGamma) {
10386
return decomposeCurrencyHedgedIndexRisk(record);
10487
} else if ((isEquitySpotSensi || isCommoditySpotSensi) && tradeIdValid && hasCommodityRefData &&
10588
isNotCrossGamma) {
106-
return decomposeCommodityRisk(record);
89+
auto decompResults =
90+
indexDecomposition(record.delta, record.key_1.name, ore::data::CurveSpec::CurveType::Commodity);
91+
return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, decompResults.indexCurrency,
92+
record);
10793
} else if ((isEquitySpotSensi || isCommoditySpotSensi) && tradeIdValid && isNotCrossGamma) {
10894
auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
10995
StructuredAnalyticsErrorMessage(
@@ -142,30 +128,113 @@ DecomposedSensitivityStream::decomposeSurvivalProbability(const SensitivityRecor
142128
return results;
143129
}
144130

145-
std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeEquityRisk(const SensitivityRecord& sr) const {
146-
std::string indexName = sr.key_1.name;
147-
auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Equity);
148-
if (refDataManager_->hasData("EquityIndex", indexName)) {
149-
auto refDatum = refDataManager_->getData("EquityIndex", indexName);
150-
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
151-
auto decompResults = decomposeIndex(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Equity);
152-
scaleFxRisk(decompResults.fxRisk, indexName);
153-
return createDecompositionRecords(
154-
decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency, sr);
155-
} else {
156-
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
157-
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
158-
"Cannot decompose equity index delta (" + sr.key_1.name +
159-
") for trade: no reference data found. Continuing without decomposition.",
160-
subFields)
161-
.log();
162-
return {sr};
131+
//! Decompose
132+
std::map<std::string, double> DecomposedSensitivityStream::constituentSpotRiskFromDecomposition(
133+
const double spotDelta, const std::map<std::string, double>& indexWeights) const {
134+
std::map<std::string, double> results;
135+
for (const auto& [constituent, weight] : indexWeights) {
136+
results[constituent] = weight * spotDelta;
137+
}
138+
return results;
139+
}
140+
141+
std::map<std::string, double> DecomposedSensitivityStream::fxRiskFromDecomposition(
142+
const std::map<std::string, double>& spotRisk,
143+
const std::map<std::string, std::vector<std::string>>& constituentCurrencies,
144+
const std::map<std::string, double>& fxSpotShiftSize, const double eqShiftSize) const {
145+
std::map<std::string, double> results;
146+
for (const auto& [currency, constituents] : constituentCurrencies) {
147+
if (currency != baseCurrency_) {
148+
QL_REQUIRE(fxSpotShiftSize.count(currency) == 1, "Can not find fxSpotShiftSize for currency " << currency);
149+
for (const auto& constituent : constituents) {
150+
QL_REQUIRE(spotRisk.count(constituent) == 1, "Can not find spotDelta for " << constituent);
151+
results[currency] += spotRisk.at(constituent) * fxSpotShiftSize.at(currency) / eqShiftSize;
152+
}
153+
}
154+
}
155+
return results;
156+
}
157+
158+
double DecomposedSensitivityStream::fxRiskShiftSize(const std::string ccy) const {
159+
auto fxpair = ccy + baseCurrency_;
160+
auto fxShiftSizeIt = ssd_->fxShiftData().find(fxpair);
161+
QL_REQUIRE(fxShiftSizeIt != ssd_->fxShiftData().end(), "Couldn't find shiftsize for " << fxpair);
162+
QL_REQUIRE(fxShiftSizeIt->second.shiftType == "Relative",
163+
"Requires a relative fxSpot shift for index decomposition");
164+
return fxShiftSizeIt->second.shiftSize;
165+
}
166+
167+
std::map<std::string, double>
168+
DecomposedSensitivityStream::fxRiskShiftSizes(const std::map<std::string, std::vector<std::string>>& currencies) const {
169+
std::map<std::string, double> results;
170+
for (const auto& [ccy,_] : currencies) {
171+
if (ccy != baseCurrency_) {
172+
double shiftSize = fxRiskShiftSize(ccy);
173+
results[ccy] = shiftSize;
174+
}
163175
}
176+
return results;
177+
}
178+
179+
double DecomposedSensitivityStream::assetSpotShiftSize(const std::string name) const {
180+
auto eqShiftSizeIt = ssd_->equityShiftData().find(name);
181+
QL_REQUIRE(eqShiftSizeIt != ssd_->equityShiftData().end(), "Couldn't find a shift size for " << name);
182+
QL_REQUIRE(eqShiftSizeIt->second.shiftType == "Relative",
183+
"Requires a relative eqSpot shift for index decomposition");
184+
return eqShiftSizeIt->second.shiftSize;
185+
}
186+
187+
std::map<std::string, std::vector<std::string>>
188+
DecomposedSensitivityStream::getConstituentCurrencies(const std::map<std::string, double>& constituents,
189+
const std::string& indexCurrency,
190+
const ore::data::CurveSpec::CurveType curveType) const {
191+
std::map<std::string, std::vector<std::string>> results;
192+
for (const auto& [constituent, _] : constituents) {
193+
auto ccy = curveCurrency(constituent, curveType);
194+
if (ccy.empty()) {
195+
ccy = indexCurrency;
196+
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition",
197+
"Cannot find currency for equity " + constituent +
198+
" from curve configs, fallback to use index currency (" +
199+
indexCurrency + ")")
200+
.log();
201+
}
202+
if (ccy != baseCurrency_) {
203+
results[ccy].push_back(constituent);
204+
}
205+
206+
}
207+
return results;
208+
}
209+
210+
DecomposedSensitivityStream::IndexDecompositionResult
211+
DecomposedSensitivityStream::indexDecomposition(double delta, const std::string& indexName,
212+
const ore::data::CurveSpec::CurveType curveType) const {
213+
IndexDecompositionResult result;
214+
std::string refDataType = curveType == ore::data::CurveSpec::CurveType::Equity ? "EquityIndex" : "CommodityIndex";
215+
216+
QL_REQUIRE(refDataManager_->hasData(refDataType, indexName),
217+
"Cannot decompose equity index delta ("
218+
<< indexName << ") for trade: no reference data found. Continuing without decomposition.");
219+
220+
auto refDatum = refDataManager_->getData(refDataType, indexName);
221+
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
222+
std::string indexCurrency = curveCurrency(indexName, curveType);
223+
std::map<string, double> indexWeights = indexRefDatum->underlyings();
224+
auto spotRisk = constituentSpotRiskFromDecomposition(delta, indexWeights);
225+
auto currencies = getConstituentCurrencies(spotRisk, indexCurrency, curveType);
226+
auto fxShifts = fxRiskShiftSizes(currencies);
227+
auto spotShift = assetSpotShiftSize(indexName);
228+
auto fxRisk = fxRiskFromDecomposition(spotRisk, currencies, fxShifts, spotShift);
229+
result.spotRisk = spotRisk;
230+
result.fxRisk = fxRisk;
231+
result.indexCurrency = indexCurrency;
232+
return result;
164233
}
165234

166235
std::vector<SensitivityRecord>
167236
DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityRecord& sr) const {
168-
237+
169238
auto indexName = sr.key_1.name;
170239
auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Equity);
171240

@@ -190,24 +259,26 @@ DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityR
190259
QL_REQUIRE(quantity != QuantLib::Null<double>(),
191260
"CurrencyHedgedIndexDecomposition failed, index quantity cannot be NULL.");
192261

193-
double hedgedExposure = sr.delta / eqRiskShiftSize(indexName, ssd_);
262+
double assetSensiShift = assetSpotShiftSize(indexName);
263+
264+
double hedgedExposure = sr.delta / assetSensiShift;
194265

195266
double unhedgedExposure =
196267
decomposeCurrencyHedgedIndexHelper->unhedgedSpotExposure(hedgedExposure, quantity, today, todaysMarket_);
197268

198-
double unhedgedDelta = unhedgedExposure * eqRiskShiftSize(indexName, ssd_);
269+
double unhedgedDelta = unhedgedExposure * assetSensiShift;
270+
271+
auto decompResults =
272+
indexDecomposition(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingIndexName(),
273+
ore::data::CurveSpec::CurveType::Equity);
199274

200-
auto decompResults = decomposeIndex(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingRefData(),
201-
ore::data::CurveSpec::CurveType::Equity);
202-
scaleFxRisk(decompResults.fxRisk, decomposeCurrencyHedgedIndexHelper->indexName());
203275
// Correct FX Delta from FxForwards
204276
for (const auto& [ccy, fxRisk] :
205277
decomposeCurrencyHedgedIndexHelper->fxSpotRiskFromForwards(quantity, today, todaysMarket_, 1.0)) {
206-
decompResults.fxRisk[ccy] = decompResults.fxRisk[ccy] - fxRisk * fxRiskShiftSize(ccy, baseCurrency_, ssd_);
278+
decompResults.fxRisk[ccy] = decompResults.fxRisk[ccy] - fxRisk * fxRiskShiftSize(ccy);
207279
}
208-
// Convert into the correct currency
209-
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk,
210-
indexCurrency, sr);
280+
281+
return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, indexCurrency, sr);
211282
} else {
212283
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
213284
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
@@ -219,69 +290,16 @@ DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityR
219290
}
220291
}
221292

222-
std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeCommodityRisk(const SensitivityRecord& sr) const {
223-
std::string indexName = sr.key_1.name;
224-
if (refDataManager_->hasData("CommodityIndex", indexName)) {
225-
auto refDatum = refDataManager_->getData("CommodityIndex", indexName);
226-
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
227-
auto decompResults = decomposeIndex(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Commodity);
228-
scaleFxRisk(decompResults.fxRisk, indexName);
229-
auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Commodity);
230-
return createDecompositionRecords(
231-
decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency, sr);
232-
} else {
233-
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
234-
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
235-
"Cannot decompose equity index delta (" + sr.key_1.name +
236-
") for trade: no reference data found. Continuing without decomposition.",
237-
subFields)
238-
.log();
239-
return {sr};
240-
}
241-
}
242-
243293
void DecomposedSensitivityStream::reset() {
244294
ss_->reset();
245295
decomposedRecords_.clear();
246296
itCurrent_ = decomposedRecords_.begin();
247297
}
248298

249-
DecomposedSensitivityStream::DecompositionResults
250-
DecomposedSensitivityStream::decomposeIndex(double delta, const boost::shared_ptr<ore::data::IndexReferenceDatum>& ird,
251-
ore::data::CurveSpec::CurveType curveType) const {
252-
QL_REQUIRE(ird, "Can not decompose equity risk, no EquityIndexReferenceData giving");
253-
QL_REQUIRE(curveConfigs_, "Can not decompose equity risk, no CurveConfig giving");
254-
QL_REQUIRE(curveType == ore::data::CurveSpec::CurveType::Equity ||
255-
curveType == ore::data::CurveSpec::CurveType::Commodity,
256-
"internal error decomposeEquityRisk supports only Equity and Commodity curves");
257-
258-
DecompositionResults results;
259-
results.indexCurrency = curveCurrency(ird->id(), curveType);
260-
QL_REQUIRE(!results.indexCurrency.empty(),
261-
"Cannot perform equity risk decomposition find currency index " + ird->id() + " from curve configs.");
262-
263-
for (const auto& [constituent, weight] : ird->underlyings()) {
264-
results.equityDelta[constituent] += delta * weight;
265-
// try look up currency in reference data and add if FX delta risk if necessary
266-
std::string constituentCcy = curveCurrency(constituent, curveType);
267-
if (constituentCcy.empty()) {
268-
constituentCcy = results.indexCurrency;
269-
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition",
270-
"Cannot find currency for equity " + constituent +
271-
" from curve configs, fallback to use index currency (" +
272-
results.indexCurrency + ")")
273-
.log();
274-
}
275-
if (constituentCcy != baseCurrency_) {
276-
results.fxRisk[constituentCcy] += delta * weight;
277-
}
278-
}
279-
return results;
280-
}
281-
282-
std::vector<SensitivityRecord> DecomposedSensitivityStream::createDecompositionRecords(
283-
const std::map<std::string, double>& eqDeltas, const std::map<std::string, double>& fxDeltas,
284-
const std::string indexCurrency, const SensitivityRecord& sr) const {
299+
std::vector<SensitivityRecord>
300+
DecomposedSensitivityStream::sensitivityRecords(const std::map<std::string, double>& eqDeltas,
301+
const std::map<std::string, double>& fxDeltas,
302+
const std::string indexCurrency, const SensitivityRecord& sr) const {
285303
std::vector<SensitivityRecord> records;
286304
for (auto [underlying, delta] : eqDeltas) {
287305
RiskFactorKey underlyingKey(sr.key_1.keytype, underlying, sr.key_1.index);
@@ -299,15 +317,6 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::createDecompositionR
299317
return records;
300318
}
301319

302-
void DecomposedSensitivityStream::scaleFxRisk(std::map<std::string, double>& fxRisk,
303-
const std::string& equityName) const {
304-
// Eq/Comm Shift to FX Shift Conversion
305-
auto eqShift = eqRiskShiftSize(equityName, ssd_);
306-
for (auto& [ccy, fxdelta] : fxRisk) {
307-
fxdelta = fxdelta * fxRiskShiftSize(ccy, baseCurrency_, ssd_) / eqShift;
308-
}
309-
}
310-
311320
std::string DecomposedSensitivityStream::curveCurrency(const std::string& name,
312321
ore::data::CurveSpec::CurveType curveType) const {
313322
std::string curveCurrency;

0 commit comments

Comments
 (0)