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
49+
2850DecomposedSensitivityStream::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+
198241void 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
247294std::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
0 commit comments