Skip to content

Commit 7f9735c

Browse files
mgronckijenkins
authored andcommitted
QPR-12173 bugfix decomposedsensitivitystream
1 parent 8cd91c1 commit 7f9735c

4 files changed

Lines changed: 94 additions & 72 deletions

File tree

OREAnalytics/orea/engine/decomposedsensitivitystream.cpp

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,16 @@ DecomposedSensitivityStream::decomposeSurvivalProbability(const SensitivityRecor
143143
}
144144

145145
std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeEquityRisk(const SensitivityRecord& sr) const {
146-
auto eqRefData = refDataManager_->getData("EquityIndex", sr.key_1.name);
147-
auto decomposeEquityIndexData = boost::dynamic_pointer_cast<ore::data::EquityIndexReferenceDatum>(eqRefData);
148-
if (decomposeEquityIndexData != nullptr) {
149-
auto decompResults =
150-
decomposeEqComIndexRisk(sr.delta, decomposeEquityIndexData, ore::data::CurveSpec::CurveType::Equity);
151-
scaleFxRisk(decompResults.fxRisk, decomposeEquityIndexData->id());
152-
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency,
153-
sr);
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+
convertFxRiskToBaseCurrency(decompResults.fxRisk, indexCurrency);
154+
return createDecompositionRecords(
155+
decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency, sr);
154156
} else {
155157
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
156158
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
@@ -164,53 +166,54 @@ std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeEquityRisk(
164166

165167
std::vector<SensitivityRecord>
166168
DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityRecord& sr) const {
169+
170+
auto indexName = sr.key_1.name;
171+
auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Equity);
167172

168173
boost::shared_ptr<ore::data::CurrencyHedgedEquityIndexDecomposition> decomposeCurrencyHedgedIndexHelper;
169174
decomposeCurrencyHedgedIndexHelper =
170-
loadCurrencyHedgedIndexDecomposition(sr.key_1.name, refDataManager_, curveConfigs_);
175+
loadCurrencyHedgedIndexDecomposition(indexName, refDataManager_, curveConfigs_);
171176
if (decomposeCurrencyHedgedIndexHelper != nullptr) {
172177
QL_REQUIRE(currencyHedgedIndexQuantities_.count(sr.tradeId) > 0,
173178
"CurrencyHedgedIndexDecomposition failed, there is no index quantity for trade "
174-
<< sr.tradeId << " and equity index EQ-" << sr.key_1.name);
175-
QL_REQUIRE(currencyHedgedIndexQuantities_.at(sr.tradeId).count("EQ-" + sr.key_1.name) > 0,
179+
<< sr.tradeId << " and equity index EQ-" << indexName);
180+
QL_REQUIRE(currencyHedgedIndexQuantities_.at(sr.tradeId).count("EQ-" + indexName) > 0,
176181
"CurrencyHedgedIndexDecomposition failed, there is no index quantity for trade "
177-
<< sr.tradeId << " and equity index EQ-" << sr.key_1.name);
182+
<< sr.tradeId << " and equity index EQ-" << indexName);
178183
QL_REQUIRE(todaysMarket_ != nullptr,
179184
"CurrencyHedgedIndexDecomposition failed, there is no market given quantity for trade "
180185
<< sr.tradeId);
181186

182187
Date today = QuantLib::Settings::instance().evaluationDate();
183188

184-
auto quantity = currencyHedgedIndexQuantities_.at(sr.tradeId).find("EQ-" + sr.key_1.name)->second;
189+
auto quantity = currencyHedgedIndexQuantities_.at(sr.tradeId).find("EQ-" + indexName)->second;
185190

186191
QL_REQUIRE(quantity != QuantLib::Null<double>(),
187192
"CurrencyHedgedIndexDecomposition failed, index quantity cannot be NULL.");
188193

189-
double hedgedExposure = sr.delta / eqRiskShiftSize(sr.key_1.name, ssd_);
194+
double hedgedExposure = sr.delta / eqRiskShiftSize(indexName, ssd_);
190195

191196
double unhedgedExposure =
192197
decomposeCurrencyHedgedIndexHelper->unhedgedSpotExposure(hedgedExposure, quantity, today, todaysMarket_);
193198

194-
double unhedgedDelta = unhedgedExposure * eqRiskShiftSize(sr.key_1.name, ssd_);
199+
double unhedgedDelta = unhedgedExposure * eqRiskShiftSize(indexName, ssd_);
195200

196-
auto decompResults =
197-
decomposeEqComIndexRisk(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingRefData(),
198-
ore::data::CurveSpec::CurveType::Equity);
201+
auto decompResults = decomposeIndex(unhedgedDelta, decomposeCurrencyHedgedIndexHelper->underlyingRefData(),
202+
ore::data::CurveSpec::CurveType::Equity);
199203
scaleFxRisk(decompResults.fxRisk, decomposeCurrencyHedgedIndexHelper->indexName());
200204
// Correct FX Delta from FxForwards
201205
for (const auto& [ccy, fxRisk] :
202206
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();
207+
decompResults.fxRisk[ccy] = decompResults.fxRisk[ccy] - fxRisk * fxRiskShiftSize(ccy, baseCurrency_, ssd_);
207208
}
209+
// Convert into the correct currency
210+
convertFxRiskToBaseCurrency(decompResults.fxRisk, indexCurrency);
208211
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk,
209-
decomposeCurrencyHedgedIndexHelper->indexCurrency(), sr);
212+
indexCurrency, sr);
210213
} else {
211214
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
212215
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
213-
"Cannot decompose equity index delta (" + sr.key_1.name +
216+
"Cannot decompose equity index delta (" + indexName +
214217
") for trade: no reference data found. Continuing without decomposition.",
215218
subFields)
216219
.log();
@@ -219,14 +222,16 @@ DecomposedSensitivityStream::decomposeCurrencyHedgedIndexRisk(const SensitivityR
219222
}
220223

221224
std::vector<SensitivityRecord> DecomposedSensitivityStream::decomposeCommodityRisk(const SensitivityRecord& sr) const {
222-
if (refDataManager_->hasData("CommodityIndex", sr.key_1.name)) {
223-
auto refDatum = refDataManager_->getData("CommodityIndex", sr.key_1.name);
225+
std::string indexName = sr.key_1.name;
226+
if (refDataManager_->hasData("CommodityIndex", indexName)) {
227+
auto refDatum = refDataManager_->getData("CommodityIndex", indexName);
224228
auto indexRefDatum = boost::dynamic_pointer_cast<ore::data::IndexReferenceDatum>(refDatum);
225-
auto decompResults =
226-
decomposeEqComIndexRisk(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Commodity);
227-
scaleFxRisk(decompResults.fxRisk, indexRefDatum->id());
228-
return createDecompositionRecords(decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency,
229-
sr);
229+
auto decompResults = decomposeIndex(sr.delta, indexRefDatum, ore::data::CurveSpec::CurveType::Commodity);
230+
scaleFxRisk(decompResults.fxRisk, indexName);
231+
auto indexCurrency = curveCurrency(indexName, ore::data::CurveSpec::CurveType::Commodity);
232+
convertFxRiskToBaseCurrency(decompResults.fxRisk, indexCurrency);
233+
return createDecompositionRecords(
234+
decompResults.equityDelta, decompResults.fxRisk, decompResults.indexCurrency, sr);
230235
} else {
231236
auto subFields = std::map<std::string, std::string>({{"tradeId", sr.tradeId}});
232237
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition failed",
@@ -244,48 +249,34 @@ void DecomposedSensitivityStream::reset() {
244249
itCurrent_ = decomposedRecords_.begin();
245250
}
246251

247-
DecomposedSensitivityStream::EqComIndexDecompositionResults
248-
DecomposedSensitivityStream::decomposeEqComIndexRisk(double delta,
249-
const boost::shared_ptr<ore::data::IndexReferenceDatum>& ird,
250-
ore::data::CurveSpec::CurveType curveType) const {
252+
DecomposedSensitivityStream::DecompositionResults
253+
DecomposedSensitivityStream::decomposeIndex(double delta, const boost::shared_ptr<ore::data::IndexReferenceDatum>& ird,
254+
ore::data::CurveSpec::CurveType curveType) const {
251255
QL_REQUIRE(ird, "Can not decompose equity risk, no EquityIndexReferenceData giving");
252256
QL_REQUIRE(curveConfigs_, "Can not decompose equity risk, no CurveConfig giving");
253257
QL_REQUIRE(curveType == ore::data::CurveSpec::CurveType::Equity ||
254258
curveType == ore::data::CurveSpec::CurveType::Commodity,
255259
"internal error decomposeEquityRisk supports only Equity and Commodity curves");
256260

257-
EqComIndexDecompositionResults results;
258-
259-
if (curveConfigs_->has(curveType, ird->id())) {
260-
results.indexCurrency = curveType == ore::data::CurveSpec::CurveType::Equity
261-
? curveConfigs_->equityCurveConfig(ird->id())->currency()
262-
: curveConfigs_->commodityCurveConfig(ird->id())->currency();
263-
} else if (curveConfigs_->hasEquityCurveConfig(ird->id())) {
264-
// if we use a equity curve as proxy fall back to lookup the currency from the proxy config
265-
results.indexCurrency = curveConfigs_->equityCurveConfig(ird->id())->currency();
266-
} else {
267-
QL_FAIL("Cannot perform equity risk decomposition find currency index " + ird->id() + " from curve configs.");
268-
}
261+
DecompositionResults results;
262+
results.indexCurrency = curveCurrency(ird->id(), curveType);
263+
QL_REQUIRE(!results.indexCurrency.empty(),
264+
"Cannot perform equity risk decomposition find currency index " + ird->id() + " from curve configs.");
269265

270-
for (auto c : ird->underlyings()) {
271-
results.equityDelta[c.first] += delta * c.second;
266+
for (const auto& [constituent, weight] : ird->underlyings()) {
267+
results.equityDelta[constituent] += delta * weight;
272268
// try look up currency in reference data and add if FX delta risk if necessary
273-
std::string constituentCcy = results.indexCurrency;
274-
if (curveConfigs_->has(curveType, c.first)) {
275-
auto constituentCcy = curveType == ore::data::CurveSpec::CurveType::Equity
276-
? curveConfigs_->equityCurveConfig(c.first)->currency()
277-
: curveConfigs_->commodityCurveConfig(c.first)->currency();
278-
279-
} else {
269+
std::string constituentCcy = curveCurrency(constituent, curveType);
270+
if (constituentCcy.empty()) {
271+
constituentCcy = results.indexCurrency;
280272
StructuredAnalyticsErrorMessage("CRIF Generation", "Equity index decomposition",
281-
"Cannot find currency for equity " + c.first +
273+
"Cannot find currency for equity " + constituent +
282274
" from curve configs, fallback to use index currency (" +
283275
results.indexCurrency + ")")
284276
.log();
285277
}
286278
if (constituentCcy != baseCurrency_) {
287-
results.fxRisk[constituentCcy] +=
288-
delta * c.second; //* todaysMarket_->fxSpot(constituentCcy+baseCurrency_)->value();
279+
results.fxRisk[constituentCcy] += delta * weight;
289280
}
290281
}
291282
return results;
@@ -320,5 +311,27 @@ void DecomposedSensitivityStream::scaleFxRisk(std::map<std::string, double>& fxR
320311
}
321312
}
322313

314+
void DecomposedSensitivityStream::convertFxRiskToBaseCurrency(std::map<std::string, double>& fxRisk,
315+
const std::string& indexCurrency) const {
316+
// Eq/Comm Shift to FX Shift Conversion
317+
auto fxSpot = todaysMarket_->fxSpot(indexCurrency + baseCurrency_)->value();
318+
for (auto& [ccy, fxdelta] : fxRisk) {
319+
fxdelta *= fxSpot;
320+
}
321+
}
322+
323+
std::string DecomposedSensitivityStream::curveCurrency(const std::string& name,
324+
ore::data::CurveSpec::CurveType curveType) const {
325+
std::string curveCurrency;
326+
if (curveConfigs_->has(curveType, name)) {
327+
curveCurrency = curveType == ore::data::CurveSpec::CurveType::Equity
328+
? curveConfigs_->equityCurveConfig(name)->currency()
329+
: curveConfigs_->commodityCurveConfig(name)->currency();
330+
} else if (curveConfigs_->hasEquityCurveConfig(name)) {
331+
// if we use a equity curve as proxy fall back to lookup the currency from the proxy config
332+
curveCurrency = curveConfigs_->equityCurveConfig(name)->currency();
333+
}
334+
return curveCurrency;
335+
}
323336
} // namespace analytics
324337
} // namespace ore

OREAnalytics/orea/engine/decomposedsensitivitystream.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ class DecomposedSensitivityStream : public SensitivityStream {
5555
void reset() override;
5656

5757
private:
58-
struct EqComIndexDecompositionResults {
58+
struct DecompositionResults {
5959
std::map<std::string, double> equityDelta;
60+
//! Fx Delta in IndexCurrency
6061
std::map<std::string, double> fxRisk;
6162
std::string indexCurrency;
6263
};
@@ -68,7 +69,7 @@ class DecomposedSensitivityStream : public SensitivityStream {
6869
std::vector<SensitivityRecord> decomposeCurrencyHedgedIndexRisk(const SensitivityRecord& record) const;
6970
std::vector<SensitivityRecord> decomposeCommodityRisk(const SensitivityRecord& record) const;
7071

71-
EqComIndexDecompositionResults decomposeEqComIndexRisk(double delta,
72+
DecompositionResults decomposeIndex(double delta,
7273
const boost::shared_ptr<ore::data::IndexReferenceDatum>& ird,
7374
ore::data::CurveSpec::CurveType curveType) const;
7475

@@ -77,8 +78,11 @@ class DecomposedSensitivityStream : public SensitivityStream {
7778
const std::string indexCurrency,
7879
const SensitivityRecord& sr) const;
7980

81+
// get the curve currency for name, fallback to check equity curves
82+
std::string curveCurrency(const std::string& name, ore::data::CurveSpec::CurveType curveType) const;
8083
// Scale the fx risk entries from the index decomposition
8184
void scaleFxRisk(std::map<std::string, double>& fxRisk, const std::string& equityName) const;
85+
void convertFxRiskToBaseCurrency(std::map<std::string, double>& fxRisk, const std::string& indexCurrency) const;
8286

8387
std::vector<SensitivityRecord> decomposedRecords_;
8488
std::vector<SensitivityRecord>::iterator itCurrent_;

OREData/ored/utilities/currencyhedgedequityindexdecomposition.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ QuantLib::Date CurrencyHedgedEquityIndexDecomposition::rebalancingDate(const Qua
163163
}
164164

165165
std::map<std::string, double> CurrencyHedgedEquityIndexDecomposition::fxSpotRiskFromForwards(
166-
const double quantity, const QuantLib::Date& asof, const boost::shared_ptr<ore::data::Market>& todaysMarket) const {
166+
const double quantity, const QuantLib::Date& asof, const boost::shared_ptr<ore::data::Market>& todaysMarket, const double shiftsize) const {
167167

168168
std::map<std::string, double> fxRisks;
169169
auto indexCurve = todaysMarket->equityCurve(indexName());
@@ -181,26 +181,31 @@ std::map<std::string, double> CurrencyHedgedEquityIndexDecomposition::fxSpotRisk
181181
auto fxIndex = todaysMarket->fxIndex("FX-" + fxIndexFamily + "-" + underlyingIndexCurrency_ + "-" + indexCurrency_);
182182
double forwardNotional =
183183
quantity * adjustmentFactor * weight * indexCurve->fixing(refDate) / fxIndex->fixing(refDate);
184-
fxRisks[ccy] = 0.01 * forwardNotional * fxIndex->fixing(asof);
184+
fxRisks[ccy] = shiftsize * forwardNotional * fxIndex->fixing(asof);
185185
}
186186
return fxRisks;
187187
}
188188

189189
double
190-
CurrencyHedgedEquityIndexDecomposition::unhedgedDelta(double hedgedDelta, const double quantity,
190+
CurrencyHedgedEquityIndexDecomposition::unhedgedSpotExposure(double hedgedExposure, const double quantity,
191191
const QuantLib::Date& asof,
192192
const boost::shared_ptr<ore::data::Market>& todaysMarket) const {
193193
auto indexCurve = todaysMarket->equityCurve(indexName());
194194
auto underlyingCurve = todaysMarket->equityCurve(underlyingIndexName());
195195
QuantLib::Date rebalanceDt = rebalancingDate(asof);
196196
auto fxIndexFamily = parseFxIndex(fxIndexName())->familyName();
197197
auto fxIndex = todaysMarket->fxIndex("FX-" + fxIndexFamily + "-" + underlyingIndexCurrency_ + "-" + indexCurrency_);
198-
// In case we have a option unit delta isnt one
199-
double scaling = (hedgedDelta * 100 / quantity) / indexCurve->fixing(asof);
198+
double hedgedUnitPrice = (hedgedExposure / quantity);
199+
// In case we have a option and the unit delta isnt one
200+
double scaling = hedgedUnitPrice / indexCurve->fixing(asof);
201+
// Change in the fx since the last rebalacing
200202
double fxReturn = fxIndex->fixing(asof) / fxIndex->fixing(rebalanceDt);
203+
// Return of the underlying since last rebalacning
201204
double underlyingIndexReturn = underlyingCurve->equitySpot()->value() / underlyingCurve->fixing(rebalanceDt);
202-
double unhedgedDelta= indexCurve->fixing(rebalanceDt) * underlyingIndexReturn * fxReturn;
203-
return scaling * 0.01 * quantity * unhedgedDelta;
205+
// Unhedged price of the index
206+
double unhedgedUnitPrice= indexCurve->fixing(rebalanceDt) * underlyingIndexReturn * fxReturn;
207+
// Unhedged exposure
208+
return scaling * quantity * unhedgedUnitPrice;
204209
}
205210

206211
void CurrencyHedgedEquityIndexDecomposition::addAdditionalFixingsForEquityIndexDecomposition(

OREData/ored/utilities/currencyhedgedequityindexdecomposition.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ class CurrencyHedgedEquityIndexDecomposition {
6363
return currencyWeightsAndFxIndexNames_;
6464
}
6565

66-
std::map<std::string, double>
67-
fxSpotRiskFromForwards(const double quantity, const QuantLib::Date& asof,
68-
const boost::shared_ptr<ore::data::Market>& todaysMarket) const;
66+
std::map<std::string, double> fxSpotRiskFromForwards(
67+
const double quantity, const QuantLib::Date& asof,
68+
const boost::shared_ptr<ore::data::Market>& todaysMarket, const double shiftsize) const;
6969

70-
double unhedgedDelta(double hedgedDelta, const double quantity, const QuantLib::Date& asof,
70+
double unhedgedSpotExposure(double hedgedExposure, const double quantity, const QuantLib::Date& asof,
7171
const boost::shared_ptr<ore::data::Market>& todaysMarket) const;
7272

7373
boost::shared_ptr<ore::data::EquityIndexReferenceDatum> underlyingRefData() const { return underlyingRefData_; }

0 commit comments

Comments
 (0)