Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
decomposedsensitivitystream.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2023 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
24
25namespace ore {
26namespace analytics {
27
28namespace {} // namespace
29
31 const QuantLib::ext::shared_ptr<SensitivityStream>& ss, const std::string& baseCurrency,
32 std::map<std::string, std::map<std::string, double>> defaultRiskDecompositionWeights,
33 const std::set<std::string>& eqComDecompositionTradeIds,
34 const std::map<std::string, std::map<std::string, double>>& currencyHedgedIndexQuantities,
35 const QuantLib::ext::shared_ptr<ore::data::ReferenceDataManager>& refDataManager,
36 const QuantLib::ext::shared_ptr<ore::data::CurveConfigurations>& curveConfigs,
37 const QuantLib::ext::shared_ptr<SensitivityScenarioData>& scenarioData,
38 const QuantLib::ext::shared_ptr<ore::data::Market>& todaysMarket)
39 : ss_(ss), baseCurrency_(baseCurrency), defaultRiskDecompositionWeights_(defaultRiskDecompositionWeights),
40 eqComDecompositionTradeIds_(eqComDecompositionTradeIds),
41 currencyHedgedIndexQuantities_(currencyHedgedIndexQuantities), refDataManager_(refDataManager),
42 curveConfigs_(curveConfigs), ssd_(scenarioData), todaysMarket_(todaysMarket) {
43 reset();
45}
46
47//! Returns the next SensitivityRecord in the stream after filtering
49 if (!decompose_)
50 return ss_->next();
51 // No decomposed records left, so continue to the next record
52 if (itCurrent_ == decomposedRecords_.end()) {
55 }
56 return *(itCurrent_++);
57}
58
59std::vector<SensitivityRecord> DecomposedSensitivityStream::decompose(const SensitivityRecord& record) const {
60 std::vector<SensitivityRecord> results;
61
62 bool tradeMarkedForDecompositionDefaultRisk =
64 bool tradeMarkedForDecomposition =
66 bool isNotCrossGamma = record.isCrossGamma() == false;
67 bool isSurvivalProbSensi = record.key_1.keytype == RiskFactorKey::KeyType::SurvivalProbability;
68 bool isEquitySpotSensi = record.key_1.keytype == RiskFactorKey::KeyType::EquitySpot;
69 bool isCommoditySpotSensi = record.key_1.keytype == RiskFactorKey::KeyType::CommodityCurve;
70
71 bool decomposeEquitySpot = tradeMarkedForDecomposition && isEquitySpotSensi && refDataManager_ != nullptr &&
72 refDataManager_->hasData("EquityIndex", record.key_1.name);
73 bool decomposeCurrencyHedgedSpot = tradeMarkedForDecomposition && isEquitySpotSensi && refDataManager_ != nullptr &&
74 refDataManager_->hasData("CurrencyHedgedEquityIndex", record.key_1.name);
75 bool decomposeCommoditySpot = tradeMarkedForDecomposition && (isCommoditySpotSensi || isEquitySpotSensi) &&
76 refDataManager_ != nullptr &&
77 refDataManager_->hasData("CommodityIndex", record.key_1.name);
78
79 try {
80 if (tradeMarkedForDecompositionDefaultRisk && isSurvivalProbSensi && isNotCrossGamma) {
81 return decomposeSurvivalProbability(record);
82 } else if (decomposeEquitySpot && isNotCrossGamma) {
83 auto decompResults =
84 indexDecomposition(record.delta, record.key_1.name, ore::data::CurveSpec::CurveType::Equity);
85 return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, decompResults.indexCurrency,
86 record);
87 } else if (decomposeCurrencyHedgedSpot && isNotCrossGamma) {
89 } else if (decomposeCommoditySpot && isNotCrossGamma) {
90 auto decompResults =
91 indexDecomposition(record.delta, record.key_1.name, ore::data::CurveSpec::CurveType::Commodity);
92 return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, decompResults.indexCurrency,
93 record);
94 } else if (tradeMarkedForDecomposition && (isCommoditySpotSensi || isEquitySpotSensi) && isNotCrossGamma) {
95 auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
97 "Sensitivity Decomposition", "Index decomposition failed",
98 "Cannot decompose equity index delta (" + record.key_1.name +
99 ") for trade: no reference data found. Continuing without decomposition.",
100 subFields)
101 .log();
102 }
103 } catch (const std::exception& e) {
104 auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
106 "Sensitivity Decomposition", "Index decomposition failed",
107 "Cannot decompose equity index delta (" + record.key_1.name + ") for trade:" + e.what(), subFields)
108 .log();
109 } catch (...) {
110 auto subFields = std::map<std::string, std::string>({{"tradeId", record.tradeId}});
112 "Sensitivity Decomposition", "Index decomposition failed",
113 "Cannot decompose equity index delta (" + record.key_1.name + ") for trade: unkown error", subFields)
114 .log();
115 }
116 return {record};
117}
118
119std::vector<SensitivityRecord>
121 std::vector<SensitivityRecord> results;
122 auto decompRecord = record;
123 for (const auto& [constituent, weight] : defaultRiskDecompositionWeights_.at(record.tradeId)) {
124 decompRecord.key_1 = RiskFactorKey(record.key_1.keytype, constituent, record.key_1.index);
125 decompRecord.delta = record.delta * weight;
126 decompRecord.gamma = record.gamma * weight;
127 results.push_back(decompRecord);
128 }
129 return results;
130}
131
132//! Decompose
134 const double spotDelta, const std::map<std::string, double>& indexWeights) const {
135 std::map<std::string, double> results;
136 for (const auto& [constituent, weight] : indexWeights) {
137 results[constituent] = weight * spotDelta;
138 }
139 return results;
140}
141
143 const std::map<std::string, double>& spotRisk,
144 const std::map<std::string, std::vector<std::string>>& constituentCurrencies,
145 const std::map<std::string, double>& fxSpotShiftSize, const double eqShiftSize) const {
146 std::map<std::string, double> results;
147 for (const auto& [currency, constituents] : constituentCurrencies) {
148 if (currency != baseCurrency_) {
149 QL_REQUIRE(fxSpotShiftSize.count(currency) == 1, "Can not find fxSpotShiftSize for currency " << currency);
150 for (const auto& constituent : constituents) {
151 QL_REQUIRE(spotRisk.count(constituent) == 1, "Can not find spotDelta for " << constituent);
152 results[currency] += spotRisk.at(constituent) * fxSpotShiftSize.at(currency) / eqShiftSize;
153 }
154 }
155 }
156 return results;
157}
158
159double DecomposedSensitivityStream::fxRiskShiftSize(const std::string ccy) const {
160 auto fxpair = ccy + baseCurrency_;
161 auto fxShiftSizeIt = ssd_->fxShiftData().find(fxpair);
162 QL_REQUIRE(fxShiftSizeIt != ssd_->fxShiftData().end(), "Couldn't find shiftsize for " << fxpair);
163 QL_REQUIRE(fxShiftSizeIt->second.shiftType == ore::analytics::ShiftType::Relative,
164 "Requires a relative fxSpot shift for index decomposition");
165 return fxShiftSizeIt->second.shiftSize;
166}
167
168std::map<std::string, double>
169DecomposedSensitivityStream::fxRiskShiftSizes(const std::map<std::string, std::vector<std::string>>& currencies) const {
170 std::map<std::string, double> results;
171 for (const auto& [ccy,_] : currencies) {
172 if (ccy != baseCurrency_) {
173 double shiftSize = fxRiskShiftSize(ccy);
174 results[ccy] = shiftSize;
175 }
176 }
177 return results;
178}
179
180double DecomposedSensitivityStream::equitySpotShiftSize(const std::string name) const {
181 auto eqShiftSizeIt = ssd_->equityShiftData().find(name);
182 QL_REQUIRE(eqShiftSizeIt != ssd_->equityShiftData().end(), "Couldn't find a equity shift size for " << name);
183 QL_REQUIRE(eqShiftSizeIt->second.shiftType == ore::analytics::ShiftType::Relative,
184 "Requires a relative eqSpot shift for index decomposition");
185 return eqShiftSizeIt->second.shiftSize;
186}
187
188double DecomposedSensitivityStream::assetSpotShiftSize(const std::string indexName,
189 const ore::data::CurveSpec::CurveType curveType) const {
190 if (curveType == ore::data::CurveSpec::CurveType::Equity) {
191 return equitySpotShiftSize(indexName);
192 } else if (curveType == ore::data::CurveSpec::CurveType::Commodity) {
193 return commoditySpotShiftSize(indexName);
194 } else {
195 QL_FAIL("unsupported curveType, got "
196 << curveType << ". Only Equity and Commodity curves are supported for decomposition.");
197 }
198}
199
200double DecomposedSensitivityStream::commoditySpotShiftSize(const std::string name) const {
201 auto commShiftSizeIt = ssd_->commodityCurveShiftData().find(name);
202 if (commShiftSizeIt != ssd_->commodityCurveShiftData().end()) {
203 QL_REQUIRE(commShiftSizeIt->second->shiftType == ore::analytics::ShiftType::Relative,
204 "Requires a relative eqSpot shift for index decomposition");
205 return commShiftSizeIt->second->shiftSize;
206 } else {
207 LOG("Could not find a commodity shift size for commodity index "
208 << name << ". Try to find a equity spot shift size as fallback")
210 }
211}
212
213std::map<std::string, std::vector<std::string>>
214DecomposedSensitivityStream::getConstituentCurrencies(const std::map<std::string, double>& constituents,
215 const std::string& indexCurrency,
216 const ore::data::CurveSpec::CurveType curveType) const {
217 std::map<std::string, std::vector<std::string>> results;
218 for (const auto& [constituent, _] : constituents) {
219 auto ccy = curveCurrency(constituent, curveType);
220 if (ccy.empty()) {
221 ccy = indexCurrency;
222 StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition",
223 "Cannot find currency for equity " + constituent +
224 " from curve configs, fallback to use index currency (" +
225 indexCurrency + ")")
226 .log();
227 }
228 if (ccy != baseCurrency_) {
229 results[ccy].push_back(constituent);
230 }
231
232 }
233 return results;
234}
235
237DecomposedSensitivityStream::indexDecomposition(double delta, const std::string& indexName,
238 const ore::data::CurveSpec::CurveType curveType) const {
240 std::string refDataType = curveType == ore::data::CurveSpec::CurveType::Equity ? "EquityIndex" : "CommodityIndex";
241
242 QL_REQUIRE(refDataManager_->hasData(refDataType, indexName),
243 "Cannot decompose equity index delta ("
244 << indexName << ") for trade: no reference data found. Continuing without decomposition.");
245
246 auto refDatum = refDataManager_->getData(refDataType, indexName);
247 auto indexRefDatum = QuantLib::ext::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
248 std::string indexCurrency = curveCurrency(indexName, curveType);
249 std::map<string, double> indexWeights = indexRefDatum->underlyings();
250 auto spotRisk = constituentSpotRiskFromDecomposition(delta, indexWeights);
251 auto currencies = getConstituentCurrencies(spotRisk, indexCurrency, curveType);
252 auto fxShifts = fxRiskShiftSizes(currencies);
253 double spotShift = assetSpotShiftSize(indexName, curveType);
254 auto fxRisk = fxRiskFromDecomposition(spotRisk, currencies, fxShifts, spotShift);
255 result.spotRisk = spotRisk;
256 result.fxRisk = fxRisk;
257 result.indexCurrency = indexCurrency;
258 return result;
259}
260
261std::vector<SensitivityRecord>
263
264 auto indexName = sr.key_1.name;
265 auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Equity);
266
267 QuantLib::ext::shared_ptr<ore::data::CurrencyHedgedEquityIndexDecomposition> decomposeCurrencyHedgedIndexHelper;
268 decomposeCurrencyHedgedIndexHelper =
270 if (decomposeCurrencyHedgedIndexHelper != nullptr) {
271 QL_REQUIRE(currencyHedgedIndexQuantities_.count(sr.tradeId) > 0,
272 "CurrencyHedgedIndexDecomposition failed, there is no index quantity for trade "
273 << sr.tradeId << " and equity index EQ-" << indexName);
274 QL_REQUIRE(currencyHedgedIndexQuantities_.at(sr.tradeId).count("EQ-" + indexName) > 0,
275 "CurrencyHedgedIndexDecomposition failed, there is no index quantity for trade "
276 << sr.tradeId << " and equity index EQ-" << indexName);
277 QL_REQUIRE(todaysMarket_ != nullptr,
278 "CurrencyHedgedIndexDecomposition failed, there is no market given quantity for trade "
279 << sr.tradeId);
280
281 Date today = QuantLib::Settings::instance().evaluationDate();
282
283 auto quantity = currencyHedgedIndexQuantities_.at(sr.tradeId).find("EQ-" + indexName)->second;
284
285 QL_REQUIRE(quantity != QuantLib::Null<double>(),
286 "CurrencyHedgedIndexDecomposition failed, index quantity cannot be NULL.");
287
288 double assetSensiShift = assetSpotShiftSize(indexName, ore::data::CurveSpec::CurveType::Equity);
289
290 double hedgedExposure = sr.delta / assetSensiShift;
291
292 double unhedgedExposure =
293 decomposeCurrencyHedgedIndexHelper->unhedgedSpotExposure(hedgedExposure, quantity, today, todaysMarket_);
294
295 double unhedgedDelta = unhedgedExposure * assetSensiShift;
296
297 auto decompResults =
298 indexDecomposition(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingIndexName(),
299 ore::data::CurveSpec::CurveType::Equity);
300
301 // Correct FX Delta from FxForwards
302 for (const auto& [ccy, fxRisk] :
303 decomposeCurrencyHedgedIndexHelper->fxSpotRiskFromForwards(quantity, today, todaysMarket_, 1.0)) {
304 decompResults.fxRisk[ccy] = decompResults.fxRisk[ccy] - fxRisk * fxRiskShiftSize(ccy);
305 }
306
307 return sensitivityRecords(decompResults.spotRisk, decompResults.fxRisk, indexCurrency, sr);
308 } else {
309 auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
310 StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
311 "Cannot decompose equity index delta (" + indexName +
312 ") for trade: no reference data found. Continuing without decomposition.",
313 subFields)
314 .log();
315 return {sr};
316 }
317}
318
320 ss_->reset();
321 decomposedRecords_.clear();
323}
324
325std::vector<SensitivityRecord>
326DecomposedSensitivityStream::sensitivityRecords(const std::map<std::string, double>& eqDeltas,
327 const std::map<std::string, double>& fxDeltas,
328 const std::string indexCurrency, const SensitivityRecord& sr) const {
329 std::vector<SensitivityRecord> records;
330 for (auto [underlying, delta] : eqDeltas) {
331 RiskFactorKey underlyingKey(sr.key_1.keytype, underlying, sr.key_1.index);
332 records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, sr.desc_1, sr.shift_1, RiskFactorKey(),
333 "", sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
334 }
335 // Add aggregated FX Deltas
336 for (auto [ccy, delta] : fxDeltas) {
337 if (ccy != indexCurrency && ccy != baseCurrency_) {
339 records.push_back(SensitivityRecord(sr.tradeId, sr.isPar, underlyingKey, sr.desc_1, sr.shift_1,
340 RiskFactorKey(), "", sr.shift_2, sr.currency, sr.baseNpv, delta, 0.0));
341 }
342 }
343 return records;
344}
345
346std::string DecomposedSensitivityStream::curveCurrency(const std::string& name,
347 ore::data::CurveSpec::CurveType curveType) const {
348 std::string curveCurrency;
349 if (curveConfigs_->has(curveType, name)) {
350 curveCurrency = curveType == ore::data::CurveSpec::CurveType::Equity
351 ? curveConfigs_->equityCurveConfig(name)->currency()
352 : curveConfigs_->commodityCurveConfig(name)->currency();
353 } else if (curveConfigs_->hasEquityCurveConfig(name)) {
354 // if we use a equity curve as proxy fall back to lookup the currency from the proxy config
355 curveCurrency = curveConfigs_->equityCurveConfig(name)->currency();
356 }
357 return curveCurrency;
358}
359} // namespace analytics
360} // namespace ore
QuantLib::ext::shared_ptr< ore::data::CurveConfigurations > curveConfigs_
std::map< std::string, std::vector< std::string > > getConstituentCurrencies(const std::map< std::string, double > &constituents, const std::string &indexCurrency, const ore::data::CurveSpec::CurveType curveType) const
QuantLib::ext::shared_ptr< ore::data::Market > todaysMarket_
IndexDecompositionResult indexDecomposition(double delta, const std::string &indexName, const ore::data::CurveSpec::CurveType curveType) const
double commoditySpotShiftSize(const std::string name) const
double assetSpotShiftSize(const std::string name, const ore::data::CurveSpec::CurveType curveType) const
Return the asset spot shift size.
DecomposedSensitivityStream(const QuantLib::ext::shared_ptr< SensitivityStream > &ss, const std::string &baseCurrency, std::map< std::string, std::map< std::string, double > > defaultRiskDecompositionWeights={}, const std::set< std::string > &eqComDecompositionTradeIds={}, const std::map< std::string, std::map< std::string, double > > &currencyHedgedIndexQuantities={}, const QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > &refDataManager=nullptr, const QuantLib::ext::shared_ptr< ore::data::CurveConfigurations > &curveConfigs=nullptr, const QuantLib::ext::shared_ptr< SensitivityScenarioData > &scenarioData=nullptr, const QuantLib::ext::shared_ptr< ore::data::Market > &todaysMarket=nullptr)
std::string curveCurrency(const std::string &name, ore::data::CurveSpec::CurveType curveType) const
double equitySpotShiftSize(const std::string name) const
QuantLib::ext::shared_ptr< SensitivityScenarioData > ssd_
std::vector< SensitivityRecord > decomposeSurvivalProbability(const SensitivityRecord &record) const
QuantLib::ext::shared_ptr< SensitivityStream > ss_
The underlying sensitivity stream that has been wrapped.
std::vector< SensitivityRecord > decompose(const SensitivityRecord &record) const
Decompose the record and add it to the internal storage;.
std::map< std::string, double > fxRiskFromDecomposition(const std::map< std::string, double > &spotRisk, const std::map< std::string, std::vector< std::string > > &constituentCurrencies, const std::map< std::string, double > &fxSpotShiftSize, const double eqShiftSize) const
Compute the resulting fx risks from a given equity/commodity decomposition.
QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > refDataManager_
refDataManager holding the equity and commodity index decomposition weights
double fxRiskShiftSize(const std::string ccy) const
Return the sensi shift size for the shifting the ccy-baseCurrency spot quote.
std::vector< SensitivityRecord >::iterator itCurrent_
std::map< std::string, std::map< std::string, double > > currencyHedgedIndexQuantities_
list of trade id, for which a commodity index decomposition should be applied
std::vector< SensitivityRecord > decomposeCurrencyHedgedIndexRisk(const SensitivityRecord &record) const
std::map< std::string, double > constituentSpotRiskFromDecomposition(const double spotDelta, const std::map< std::string, double > &indexWeights) const
Decompose a equity/commodity spot sensitivity into the constituent spot sensistivities.
std::map< std::string, double > fxRiskShiftSizes(const std::map< std::string, std::vector< std::string > > &constituentCurrencies) const
Returns the shift sizes for all currencies in the map.
std::vector< SensitivityRecord > sensitivityRecords(const std::map< std::string, double > &eqDeltas, const std::map< std::string, double > &fxDeltas, const std::string indexCurrency, const SensitivityRecord &orginialRecord) const
void reset() override
Resets the stream so that SensitivityRecord objects can be streamed again.
SensitivityRecord next() override
Returns the next SensitivityRecord in the stream after filtering.
std::set< std::string > eqComDecompositionTradeIds_
list of trade id, for which a equity index decomposition should be applied
std::map< std::string, std::map< std::string, double > > defaultRiskDecompositionWeights_
map of trade ids to the basket consituents with their resp. weights
Data types stored in the scenario class.
Definition: scenario.hpp:48
KeyType keytype
Key type.
Definition: scenario.hpp:89
std::string name
Key name.
Definition: scenario.hpp:94
Class that wraps a sensitivity stream and decomposes equity/commodity and default risk records.
#define LOG(text)
QuantLib::ext::shared_ptr< CurrencyHedgedEquityIndexDecomposition > loadCurrencyHedgedIndexDecomposition(const std::string &name, const QuantLib::ext::shared_ptr< ReferenceDataManager > &refDataMgr, const QuantLib::ext::shared_ptr< CurveConfigurations > &curveConfigs)
bool isCrossGamma() const
True if a SensitivityRecord is a cross gamma, otherwise false.
Structured analytics error.
Class for structured analytics warnings.
vector< string > curveConfigs
string name