Skip to content

Commit 86081af

Browse files
NathanielVolfangojenkins
authored andcommitted
QPR-12397 some speed optimizations if there are many qualifiers / buckets
1 parent dbac149 commit 86081af

4 files changed

Lines changed: 104 additions & 81 deletions

File tree

OREAnalytics/orea/simm/simmbucketmapperbase.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ QuantLib::Date BucketMapping::validFromDate() const {
103103

104104
string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qualifier) const {
105105

106+
auto key = std::make_pair(riskType, qualifier);
107+
if (auto b = cache_.find(key); b != cache_.end())
108+
return b->second;
109+
106110
QL_REQUIRE(hasBuckets(riskType), "The risk type " << riskType << " does not have buckets");
107111

108112
// Vol risk type bucket mappings are stored in their non-vol counterparts
@@ -114,7 +118,9 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
114118

115119
// Deal with RiskType::IRCurve
116120
if (lookupRiskType == RiskType::IRCurve || lookupRiskType == RiskType::GIRR_DELTA) {
117-
return irBucket(qualifier);
121+
auto tmp = irBucket(qualifier);
122+
cache_[key] = tmp;
123+
return tmp;
118124
}
119125

120126
string bucket;
@@ -186,13 +192,15 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
186192
for (auto m : bucketMapping_.at(lookupRiskType).at(lookupName)) {
187193
if (m.validToDate() >= today && m.validFromDate() <= today && m.fallback() == !haveMapping) {
188194
bucket = m.bucket();
195+
cache_[key] = bucket;
189196
return bucket;
190197
}
191198
}
192199
TLOG("bucket mapping for risk type " << riskType << " and qualifier " << qualifier << " inactive, return Residual");
193200
bucket = "Residual";
194201
}
195202

203+
cache_[key] = bucket;
196204
return bucket;
197205
}
198206

@@ -332,6 +340,8 @@ XMLNode* SimmBucketMapperBase::toXML(ore::data::XMLDocument& doc) const {
332340
void SimmBucketMapperBase::addMapping(const RiskType& riskType, const string& qualifier, const string& bucket,
333341
const string& validFrom, const string& validTo, bool fallback) {
334342

343+
cache_.clear();
344+
335345
// Possibly map to non-vol counterpart for lookup
336346
RiskType rt = riskType;
337347
if (nonVolRiskTypeMap.count(riskType) > 0) {
@@ -393,6 +403,7 @@ void SimmBucketMapperBase::checkRiskType(const RiskType& riskType) const {
393403
}
394404

395405
void SimmBucketMapperBase::reset() {
406+
cache_.clear();
396407
// Clear the bucket mapper and add back the commodity mappings
397408
bucketMapping_.clear();
398409
failedMappings_.clear();

OREAnalytics/orea/simm/simmbucketmapperbase.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class SimmBucketMapperBase : public SimmBucketMapper, public ore::data::XMLSeria
115115
std::set<CrifRecord::RiskType> rtWithBuckets_;
116116

117117
private:
118+
mutable std::map<std::pair<CrifRecord::RiskType, std::string>, std::string> cache_;
119+
118120
//! Reset the SIMM bucket mapper i.e. clears all mappings and adds the initial hard-coded commodity mappings
119121
void reset();
120122

OREAnalytics/orea/simm/simmcalculator.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -897,10 +897,16 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
897897

898898
bool riskClassIsFX = rt == RiskType::FX || rt == RiskType::FXVol;
899899

900+
// precomputed
901+
map<std::pair<std::string,std::string>, std::vector<CrifRecord>> crifByQualifierAndBucket;
902+
map<std::string, std::vector<CrifRecord>> crifByBucket;
903+
900904
// Find the set of buckets and associated qualifiers for the netting set details, product class and risk type
901905
map<string, set<string>> buckets;
902906
for(const auto& it : crif.filterBy(nettingSetDetails, pc, rt)) {
903907
buckets[it.bucket].insert(it.qualifier);
908+
crifByQualifierAndBucket[std::make_pair(it.qualifier,it.bucket)].push_back(it);
909+
crifByBucket[it.bucket].push_back(it);
904910
}
905911

906912
// If there are no buckets, return early and set bool to false to indicate margin does not apply
@@ -925,7 +931,8 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
925931

926932
// Get the concentration risk for each qualifier in current bucket i.e. $CR_k$ from SIMM docs
927933
map<string, Real> concentrationRisk;
928-
for (const auto& qualifier : buckets.at(bucket)) {
934+
935+
for (const auto& qualifier : kv.second) {
929936

930937
// Do not include Risk_FX components in the calculation currency in the SIMM calculation
931938
if (rt == RiskType::FX && qualifier == calcCcy) {
@@ -938,7 +945,7 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
938945
}
939946

940947
// Pair of iterators to start and end of sensitivities with current qualifier
941-
auto pQualifier = crif.filterByQualifierAndBucket(nettingSetDetails, pc, rt, qualifier, bucket);
948+
auto pQualifier = crifByQualifierAndBucket[std::make_pair(qualifier,bucket)];
942949

943950
// One pass to get the concentration risk for this qualifier
944951
for (auto it = pQualifier.begin(); it != pQualifier.end(); ++it) {
@@ -955,9 +962,10 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
955962
concentrationRisk[qualifier] = max(1.0, sqrt(std::abs(concentrationRisk[qualifier])));
956963
}
957964

965+
958966
// Calculate the margin component for the current bucket
959967
// Pair of iterators to start and end of sensitivities within current bucket
960-
auto pBucket = crif.filterByBucket(nettingSetDetails, pc, rt, bucket);
968+
auto pBucket = crifByBucket[bucket];
961969
for (auto itOuter = pBucket.begin(); itOuter != pBucket.end(); ++itOuter) {
962970
// Do not include Risk_FX components in the calculation currency in the SIMM calculation
963971
if (rt == RiskType::FX && itOuter->qualifier == calcCcy) {
@@ -975,6 +983,8 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
975983
// Weighted sensitivity i.e. $WS_{k}$ from SIMM docs
976984
Real wsOuter =
977985
rwOuter * (itOuter->amountResultCcy * sigmaOuter * hvr) * concentrationRisk[itOuter->qualifier];
986+
// Get concentration risk for outer qualifier
987+
Real outerConcentrationRisk = concentrationRisk.at(itOuter->qualifier);
978988
// Update weighted sensitivity sum
979989
sumWeightedSensis[bucket] += wsOuter;
980990
// Add diagonal element to bucket margin
@@ -991,12 +1001,12 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
9911001
continue;
9921002
}
9931003
// Correlation, $\rho_{k,l}$ in the SIMM docs
994-
Real corr = simmConfiguration_->correlation(rt, itOuter->qualifier, itOuter->label1, itOuter->label2,
995-
rt, itInner->qualifier, itInner->label1, itInner->label2,
996-
calcCcy);
1004+
Real corr =
1005+
simmConfiguration_->correlation(rt, itOuter->qualifier, itOuter->label1, itOuter->label2, rt,
1006+
itInner->qualifier, itInner->label1, itInner->label2, calcCcy);
9971007
// $f_{k,l}$ from the SIMM docs
998-
Real f = min(concentrationRisk.at(itOuter->qualifier), concentrationRisk.at(itInner->qualifier)) /
999-
max(concentrationRisk.at(itOuter->qualifier), concentrationRisk.at(itInner->qualifier));
1008+
Real f = min(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier)) /
1009+
max(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier));
10001010
// Add cross element to delta margin
10011011
Real sigmaInner = simmConfiguration_->sigma(rt, itInner->qualifier, itInner->label1, calcCcy);
10021012
Real rwInner = simmConfiguration_->weight(rt, itInner->qualifier, itInner->label1, calcCcy);

OREAnalytics/orea/simm/simmconfigurationbase.cpp

Lines changed: 72 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -231,59 +231,32 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
231231
return 1.0;
232232
}
233233

234-
// Deal with case of different risk types
235-
if ((firstRt != secondRt) && (firstQualifier == secondQualifier)) {
236-
if (((firstRt == RiskType::IRCurve || firstRt == RiskType::Inflation) && secondRt == RiskType::XCcyBasis) ||
237-
(firstRt == RiskType::XCcyBasis && (secondRt == RiskType::IRCurve || secondRt == RiskType::Inflation))) {
238-
// Between xccy basis and any yield or inflation in same currency
239-
return xccyCorr_;
240-
}
241-
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::Inflation) ||
242-
(firstRt == RiskType::Inflation && secondRt == RiskType::IRCurve)) {
243-
// Between any yield and inflation in same currency
244-
return infCorr_;
245-
}
246-
if ((firstRt == RiskType::IRVol && secondRt == RiskType::InflationVol) ||
247-
(firstRt == RiskType::InflationVol && secondRt == RiskType::IRVol)) {
248-
// Between any yield volatility and inflation volatility in same currency
249-
return infVolCorr_;
250-
}
251-
}
234+
// Deal with Equity correlations
235+
if ((firstRt == RiskType::Equity && secondRt == RiskType::Equity) ||
236+
(firstRt == RiskType::EquityVol && secondRt == RiskType::EquityVol)) {
252237

253-
// Deal with IRCurve and IRVol correlations
254-
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::IRCurve) ||
255-
(firstRt == RiskType::IRVol && secondRt == RiskType::IRVol)) {
238+
// Get the bucket of each qualifier
239+
string bucket_1 = simmBucketMapper_->bucket(firstRt, firstQualifier);
240+
string bucket_2 = simmBucketMapper_->bucket(secondRt, secondQualifier);
256241

257-
// If the qualifiers, i.e. currencies, are the same
258-
if (firstQualifier == secondQualifier) {
259-
// Label2 level, i.e. sub-curve, correlations
260-
if (firstLabel_2 != secondLabel_2) {
261-
QL_REQUIRE(
262-
firstLabel_1 == "" && secondLabel_1 == "",
263-
"When asking for Label2 level correlations, "
264-
<< "the Label1 level values should both contain the default parameter i.e. empty string");
265-
QL_REQUIRE(firstRt != RiskType::IRVol, "There is no correlation at the Label2 level for Risk_IRVol");
266-
return irSubCurveCorr_;
267-
}
242+
// Residual is special, 0 correlation inter and intra except if same qualifier
243+
if (bucket_1 == "Residual" || bucket_2 == "Residual") {
244+
return firstQualifier == secondQualifier ? 1.0 : 0.0;
245+
}
268246

269-
// Label1 level, i.e. tenor, correlations
270-
RiskType rt = RiskType::IRCurve;
271-
auto label12Key = makeKey("", firstLabel_1, secondLabel_1);
272-
if (intraBucketCorrelation_.at(rt).find(label12Key) != intraBucketCorrelation_.at(rt).end())
273-
return intraBucketCorrelation_.at(rt).at(label12Key);
274-
else
275-
QL_FAIL("Could not find correlation for risk type " << rt << " and key " << label12Key);
247+
// Non-residual
248+
// Get the bucket index of each qualifier
249+
if (bucket_1 == bucket_2) {
250+
auto bucketKey = makeKey(bucket_1, "", "");
251+
// If same bucket, return the intra-bucket correlation
252+
return firstQualifier == secondQualifier ? 1.0 : intraBucketCorrelation_.at(RiskType::Equity).at(bucketKey);
276253
} else {
277-
// If the qualifiers, i.e. currencies, are not the same
278-
return irInterCurrencyCorr_;
254+
// If different buckets, return the inter-bucket correlation
255+
auto label12Key = makeKey("", bucket_1, bucket_2);
256+
return interBucketCorrelation_.at(RiskType::Equity).at(label12Key);
279257
}
280258
}
281259

282-
// Deal with inflation volatility correlations
283-
if (firstRt == RiskType::InflationVol && secondRt == RiskType::InflationVol) {
284-
return 1.0;
285-
}
286-
287260
// Deal with CreditQ correlations
288261
if ((firstRt == RiskType::CreditQ && secondRt == RiskType::CreditQ) ||
289262
(firstRt == RiskType::CreditVol && secondRt == RiskType::CreditVol)) {
@@ -364,32 +337,6 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
364337
}
365338
}
366339

367-
// Deal with Equity correlations
368-
if ((firstRt == RiskType::Equity && secondRt == RiskType::Equity) ||
369-
(firstRt == RiskType::EquityVol && secondRt == RiskType::EquityVol)) {
370-
371-
// Get the bucket of each qualifier
372-
string bucket_1 = simmBucketMapper_->bucket(firstRt, firstQualifier);
373-
string bucket_2 = simmBucketMapper_->bucket(secondRt, secondQualifier);
374-
375-
// Residual is special, 0 correlation inter and intra except if same qualifier
376-
if (bucket_1 == "Residual" || bucket_2 == "Residual") {
377-
return firstQualifier == secondQualifier ? 1.0 : 0.0;
378-
}
379-
380-
// Non-residual
381-
// Get the bucket index of each qualifier
382-
if (bucket_1 == bucket_2) {
383-
auto bucketKey = makeKey(bucket_1, "", "");
384-
// If same bucket, return the intra-bucket correlation
385-
return firstQualifier == secondQualifier ? 1.0 : intraBucketCorrelation_.at(RiskType::Equity).at(bucketKey);
386-
} else {
387-
// If different buckets, return the inter-bucket correlation
388-
auto label12Key = makeKey("", bucket_1, bucket_2);
389-
return interBucketCorrelation_.at(RiskType::Equity).at(label12Key);
390-
}
391-
}
392-
393340
// Deal with Commodity correlations
394341
if ((firstRt == RiskType::Commodity && secondRt == RiskType::Commodity) ||
395342
(firstRt == RiskType::CommodityVol && secondRt == RiskType::CommodityVol)) {
@@ -409,6 +356,59 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
409356
}
410357
}
411358

359+
// Deal with case of different risk types
360+
if ((firstRt != secondRt) && (firstQualifier == secondQualifier)) {
361+
if (((firstRt == RiskType::IRCurve || firstRt == RiskType::Inflation) && secondRt == RiskType::XCcyBasis) ||
362+
(firstRt == RiskType::XCcyBasis && (secondRt == RiskType::IRCurve || secondRt == RiskType::Inflation))) {
363+
// Between xccy basis and any yield or inflation in same currency
364+
return xccyCorr_;
365+
}
366+
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::Inflation) ||
367+
(firstRt == RiskType::Inflation && secondRt == RiskType::IRCurve)) {
368+
// Between any yield and inflation in same currency
369+
return infCorr_;
370+
}
371+
if ((firstRt == RiskType::IRVol && secondRt == RiskType::InflationVol) ||
372+
(firstRt == RiskType::InflationVol && secondRt == RiskType::IRVol)) {
373+
// Between any yield volatility and inflation volatility in same currency
374+
return infVolCorr_;
375+
}
376+
}
377+
378+
// Deal with IRCurve and IRVol correlations
379+
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::IRCurve) ||
380+
(firstRt == RiskType::IRVol && secondRt == RiskType::IRVol)) {
381+
382+
// If the qualifiers, i.e. currencies, are the same
383+
if (firstQualifier == secondQualifier) {
384+
// Label2 level, i.e. sub-curve, correlations
385+
if (firstLabel_2 != secondLabel_2) {
386+
QL_REQUIRE(
387+
firstLabel_1 == "" && secondLabel_1 == "",
388+
"When asking for Label2 level correlations, "
389+
<< "the Label1 level values should both contain the default parameter i.e. empty string");
390+
QL_REQUIRE(firstRt != RiskType::IRVol, "There is no correlation at the Label2 level for Risk_IRVol");
391+
return irSubCurveCorr_;
392+
}
393+
394+
// Label1 level, i.e. tenor, correlations
395+
RiskType rt = RiskType::IRCurve;
396+
auto label12Key = makeKey("", firstLabel_1, secondLabel_1);
397+
if (intraBucketCorrelation_.at(rt).find(label12Key) != intraBucketCorrelation_.at(rt).end())
398+
return intraBucketCorrelation_.at(rt).at(label12Key);
399+
else
400+
QL_FAIL("Could not find correlation for risk type " << rt << " and key " << label12Key);
401+
} else {
402+
// If the qualifiers, i.e. currencies, are not the same
403+
return irInterCurrencyCorr_;
404+
}
405+
}
406+
407+
// Deal with inflation volatility correlations
408+
if (firstRt == RiskType::InflationVol && secondRt == RiskType::InflationVol) {
409+
return 1.0;
410+
}
411+
412412
// Deal with FX correlations
413413
// TODO:
414414
// For FXVol, qualifier is a currency pair. Is it possible to get here

0 commit comments

Comments
 (0)