2525namespace ore {
2626namespace 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
5030DecomposedSensitivityStream::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
166235std::vector<SensitivityRecord>
167236DecomposedSensitivityStream::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-
243293void 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-
311320std::string DecomposedSensitivityStream::curveCurrency (const std::string& name,
312321 ore::data::CurveSpec::CurveType curveType) const {
313322 std::string curveCurrency;
0 commit comments