Skip to content

Commit 7554c66

Browse files
committed
QPR-12397 some speed optimizations if there are many qualifiers / buckets
1 parent 5e8985e commit 7554c66

File tree

4 files changed

+101
-78
lines changed

4 files changed

+101
-78
lines changed

OREAnalytics/orea/simm/simmbucketmapperbase.cpp

+12-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ QuantLib::Date BucketMapping::validFromDate() const {
8484

8585
string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qualifier) const {
8686

87+
auto key = std::make_pair(riskType, qualifier);
88+
if (auto b = cache_.find(key); b != cache_.end())
89+
return b->second;
90+
8791
QL_REQUIRE(hasBuckets(riskType), "The risk type " << riskType << " does not have buckets");
8892

8993
// Vol risk type bucket mappings are stored in their non-vol counterparts
@@ -95,7 +99,9 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
9599

96100
// Deal with RiskType::IRCurve
97101
if (lookupRiskType == RiskType::IRCurve) {
98-
return irBucket(qualifier);
102+
auto tmp = irBucket(qualifier);
103+
cache_[key] = tmp;
104+
return tmp;
99105
}
100106

101107
string bucket;
@@ -167,13 +173,15 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
167173
for (auto m : bucketMapping_.at(lookupRiskType).at(lookupName)) {
168174
if (m.validToDate() >= today && m.validFromDate() <= today && m.fallback() == !haveMapping) {
169175
bucket = m.bucket();
176+
cache_[key] = bucket;
170177
return bucket;
171178
}
172179
}
173180
TLOG("bucket mapping for risk type " << riskType << " and qualifier " << qualifier << " inactive, return Residual");
174181
bucket = "Residual";
175182
}
176183

184+
cache_[key] = bucket;
177185
return bucket;
178186
}
179187

@@ -313,6 +321,8 @@ XMLNode* SimmBucketMapperBase::toXML(ore::data::XMLDocument& doc) {
313321
void SimmBucketMapperBase::addMapping(const RiskType& riskType, const string& qualifier, const string& bucket,
314322
const string& validFrom, const string& validTo, bool fallback) {
315323

324+
cache_.clear();
325+
316326
// Possibly map to non-vol counterpart for lookup
317327
RiskType rt = riskType;
318328
if (nonVolRiskTypeMap.count(riskType) > 0) {
@@ -374,6 +384,7 @@ void SimmBucketMapperBase::checkRiskType(const RiskType& riskType) const {
374384
}
375385

376386
void SimmBucketMapperBase::reset() {
387+
cache_.clear();
377388
// Clear the bucket mapper and add back the commodity mappings
378389
bucketMapping_.clear();
379390
failedMappings_.clear();

OREAnalytics/orea/simm/simmbucketmapperbase.hpp

+2
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

+15-5
Original file line numberDiff line numberDiff line change
@@ -888,10 +888,16 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
888888

889889
bool riskClassIsFX = rt == RiskType::FX || rt == RiskType::FXVol;
890890

891+
// precomputed
892+
map<std::pair<std::string,std::string>, std::vector<CrifRecord>> crifByQualifierAndBucket;
893+
map<std::string, std::vector<CrifRecord>> crifByBucket;
894+
891895
// Find the set of buckets and associated qualifiers for the netting set details, product class and risk type
892896
map<string, set<string>> buckets;
893897
for(const auto& it : crif.filterBy(nettingSetDetails, pc, rt)) {
894898
buckets[it.bucket].insert(it.qualifier);
899+
crifByQualifierAndBucket[std::make_pair(it.qualifier,it.bucket)].push_back(it);
900+
crifByBucket[it.bucket].push_back(it);
895901
}
896902

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

917923
// Get the concentration risk for each qualifier in current bucket i.e. $CR_k$ from SIMM docs
918924
map<string, Real> concentrationRisk;
919-
for (const auto& qualifier : buckets.at(bucket)) {
925+
926+
for (const auto& qualifier : kv.second) {
920927

921928
// Do not include Risk_FX components in the calculation currency in the SIMM calculation
922929
if (rt == RiskType::FX && qualifier == calculationCcy_) {
@@ -929,7 +936,7 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
929936
}
930937

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

934941
// One pass to get the concentration risk for this qualifier
935942
for (auto it = pQualifier.begin(); it != pQualifier.end(); ++it) {
@@ -946,9 +953,10 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
946953
concentrationRisk[qualifier] = max(1.0, sqrt(std::abs(concentrationRisk[qualifier])));
947954
}
948955

956+
949957
// Calculate the margin component for the current bucket
950958
// Pair of iterators to start and end of sensitivities within current bucket
951-
auto pBucket = crif.filterByBucket(nettingSetDetails, pc, rt, bucket);
959+
auto pBucket = crifByBucket[bucket];
952960
for (auto itOuter = pBucket.begin(); itOuter != pBucket.end(); ++itOuter) {
953961
// Do not include Risk_FX components in the calculation currency in the SIMM calculation
954962
if (rt == RiskType::FX && itOuter->qualifier == calculationCcy_) {
@@ -966,6 +974,8 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
966974
// Weighted sensitivity i.e. $WS_{k}$ from SIMM docs
967975
Real wsOuter =
968976
rwOuter * (itOuter->amountResultCcy * sigmaOuter * hvr) * concentrationRisk[itOuter->qualifier];
977+
// Get concentration risk for outer qualifier
978+
Real outerConcentrationRisk = concentrationRisk.at(itOuter->qualifier);
969979
// Update weighted sensitivity sum
970980
sumWeightedSensis[bucket] += wsOuter;
971981
// Add diagonal element to bucket margin
@@ -986,8 +996,8 @@ pair<map<string, Real>, bool> SimmCalculator::margin(const NettingSetDetails& ne
986996
rt, itInner->qualifier, itInner->label1, itInner->label2,
987997
calculationCcy_);
988998
// $f_{k,l}$ from the SIMM docs
989-
Real f = min(concentrationRisk.at(itOuter->qualifier), concentrationRisk.at(itInner->qualifier)) /
990-
max(concentrationRisk.at(itOuter->qualifier), concentrationRisk.at(itInner->qualifier));
999+
Real f = min(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier)) /
1000+
max(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier));
9911001
// Add cross element to delta margin
9921002
Real sigmaInner = simmConfiguration_->sigma(rt, itInner->qualifier, itInner->label1, calculationCcy_);
9931003
Real rwInner = simmConfiguration_->weight(rt, itInner->qualifier, itInner->label1, calculationCcy_);

OREAnalytics/orea/simm/simmconfigurationbase.cpp

+72-72
Original file line numberDiff line numberDiff line change
@@ -280,59 +280,32 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
280280
return 1.0;
281281
}
282282

283-
// Deal with case of different risk types
284-
if ((firstRt != secondRt) && (firstQualifier == secondQualifier)) {
285-
if (((firstRt == RiskType::IRCurve || firstRt == RiskType::Inflation) && secondRt == RiskType::XCcyBasis) ||
286-
(firstRt == RiskType::XCcyBasis && (secondRt == RiskType::IRCurve || secondRt == RiskType::Inflation))) {
287-
// Between xccy basis and any yield or inflation in same currency
288-
return xccyCorr_;
289-
}
290-
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::Inflation) ||
291-
(firstRt == RiskType::Inflation && secondRt == RiskType::IRCurve)) {
292-
// Between any yield and inflation in same currency
293-
return infCorr_;
294-
}
295-
if ((firstRt == RiskType::IRVol && secondRt == RiskType::InflationVol) ||
296-
(firstRt == RiskType::InflationVol && secondRt == RiskType::IRVol)) {
297-
// Between any yield volatility and inflation volatility in same currency
298-
return infVolCorr_;
299-
}
300-
}
283+
// Deal with Equity correlations
284+
if ((firstRt == RiskType::Equity && secondRt == RiskType::Equity) ||
285+
(firstRt == RiskType::EquityVol && secondRt == RiskType::EquityVol)) {
301286

302-
// Deal with IRCurve and IRVol correlations
303-
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::IRCurve) ||
304-
(firstRt == RiskType::IRVol && secondRt == RiskType::IRVol)) {
287+
// Get the bucket of each qualifier
288+
string bucket_1 = simmBucketMapper_->bucket(firstRt, firstQualifier);
289+
string bucket_2 = simmBucketMapper_->bucket(secondRt, secondQualifier);
305290

306-
// If the qualifiers, i.e. currencies, are the same
307-
if (firstQualifier == secondQualifier) {
308-
// Label2 level, i.e. sub-curve, correlations
309-
if (firstLabel_2 != secondLabel_2) {
310-
QL_REQUIRE(
311-
firstLabel_1 == "" && secondLabel_1 == "",
312-
"When asking for Label2 level correlations, "
313-
<< "the Label1 level values should both contain the default parameter i.e. empty string");
314-
QL_REQUIRE(firstRt != RiskType::IRVol, "There is no correlation at the Label2 level for Risk_IRVol");
315-
return irSubCurveCorr_;
316-
}
291+
// Residual is special, 0 correlation inter and intra except if same qualifier
292+
if (bucket_1 == "Residual" || bucket_2 == "Residual") {
293+
return firstQualifier == secondQualifier ? 1.0 : 0.0;
294+
}
317295

318-
// Label1 level, i.e. tenor, correlations
319-
RiskType rt = RiskType::IRCurve;
320-
auto label12Key = makeKey("", firstLabel_1, secondLabel_1);
321-
if (intraBucketCorrelation_.at(rt).find(label12Key) != intraBucketCorrelation_.at(rt).end())
322-
return intraBucketCorrelation_.at(rt).at(label12Key);
323-
else
324-
QL_FAIL("Could not find correlation for risk type " << rt << " and key " << label12Key);
296+
// Non-residual
297+
// Get the bucket index of each qualifier
298+
if (bucket_1 == bucket_2) {
299+
auto bucketKey = makeKey(bucket_1, "", "");
300+
// If same bucket, return the intra-bucket correlation
301+
return firstQualifier == secondQualifier ? 1.0 : intraBucketCorrelation_.at(RiskType::Equity).at(bucketKey);
325302
} else {
326-
// If the qualifiers, i.e. currencies, are not the same
327-
return irInterCurrencyCorr_;
303+
// If different buckets, return the inter-bucket correlation
304+
auto label12Key = makeKey("", bucket_1, bucket_2);
305+
return interBucketCorrelation_.at(RiskType::Equity).at(label12Key);
328306
}
329307
}
330308

331-
// Deal with inflation volatility correlations
332-
if (firstRt == RiskType::InflationVol && secondRt == RiskType::InflationVol) {
333-
return 1.0;
334-
}
335-
336309
// Deal with CreditQ correlations
337310
if ((firstRt == RiskType::CreditQ && secondRt == RiskType::CreditQ) ||
338311
(firstRt == RiskType::CreditVol && secondRt == RiskType::CreditVol)) {
@@ -413,32 +386,6 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
413386
}
414387
}
415388

416-
// Deal with Equity correlations
417-
if ((firstRt == RiskType::Equity && secondRt == RiskType::Equity) ||
418-
(firstRt == RiskType::EquityVol && secondRt == RiskType::EquityVol)) {
419-
420-
// Get the bucket of each qualifier
421-
string bucket_1 = simmBucketMapper_->bucket(firstRt, firstQualifier);
422-
string bucket_2 = simmBucketMapper_->bucket(secondRt, secondQualifier);
423-
424-
// Residual is special, 0 correlation inter and intra except if same qualifier
425-
if (bucket_1 == "Residual" || bucket_2 == "Residual") {
426-
return firstQualifier == secondQualifier ? 1.0 : 0.0;
427-
}
428-
429-
// Non-residual
430-
// Get the bucket index of each qualifier
431-
if (bucket_1 == bucket_2) {
432-
auto bucketKey = makeKey(bucket_1, "", "");
433-
// If same bucket, return the intra-bucket correlation
434-
return firstQualifier == secondQualifier ? 1.0 : intraBucketCorrelation_.at(RiskType::Equity).at(bucketKey);
435-
} else {
436-
// If different buckets, return the inter-bucket correlation
437-
auto label12Key = makeKey("", bucket_1, bucket_2);
438-
return interBucketCorrelation_.at(RiskType::Equity).at(label12Key);
439-
}
440-
}
441-
442389
// Deal with Commodity correlations
443390
if ((firstRt == RiskType::Commodity && secondRt == RiskType::Commodity) ||
444391
(firstRt == RiskType::CommodityVol && secondRt == RiskType::CommodityVol)) {
@@ -458,6 +405,59 @@ QuantLib::Real SimmConfigurationBase::correlation(const RiskType& firstRt, const
458405
}
459406
}
460407

408+
// Deal with case of different risk types
409+
if ((firstRt != secondRt) && (firstQualifier == secondQualifier)) {
410+
if (((firstRt == RiskType::IRCurve || firstRt == RiskType::Inflation) && secondRt == RiskType::XCcyBasis) ||
411+
(firstRt == RiskType::XCcyBasis && (secondRt == RiskType::IRCurve || secondRt == RiskType::Inflation))) {
412+
// Between xccy basis and any yield or inflation in same currency
413+
return xccyCorr_;
414+
}
415+
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::Inflation) ||
416+
(firstRt == RiskType::Inflation && secondRt == RiskType::IRCurve)) {
417+
// Between any yield and inflation in same currency
418+
return infCorr_;
419+
}
420+
if ((firstRt == RiskType::IRVol && secondRt == RiskType::InflationVol) ||
421+
(firstRt == RiskType::InflationVol && secondRt == RiskType::IRVol)) {
422+
// Between any yield volatility and inflation volatility in same currency
423+
return infVolCorr_;
424+
}
425+
}
426+
427+
// Deal with IRCurve and IRVol correlations
428+
if ((firstRt == RiskType::IRCurve && secondRt == RiskType::IRCurve) ||
429+
(firstRt == RiskType::IRVol && secondRt == RiskType::IRVol)) {
430+
431+
// If the qualifiers, i.e. currencies, are the same
432+
if (firstQualifier == secondQualifier) {
433+
// Label2 level, i.e. sub-curve, correlations
434+
if (firstLabel_2 != secondLabel_2) {
435+
QL_REQUIRE(
436+
firstLabel_1 == "" && secondLabel_1 == "",
437+
"When asking for Label2 level correlations, "
438+
<< "the Label1 level values should both contain the default parameter i.e. empty string");
439+
QL_REQUIRE(firstRt != RiskType::IRVol, "There is no correlation at the Label2 level for Risk_IRVol");
440+
return irSubCurveCorr_;
441+
}
442+
443+
// Label1 level, i.e. tenor, correlations
444+
RiskType rt = RiskType::IRCurve;
445+
auto label12Key = makeKey("", firstLabel_1, secondLabel_1);
446+
if (intraBucketCorrelation_.at(rt).find(label12Key) != intraBucketCorrelation_.at(rt).end())
447+
return intraBucketCorrelation_.at(rt).at(label12Key);
448+
else
449+
QL_FAIL("Could not find correlation for risk type " << rt << " and key " << label12Key);
450+
} else {
451+
// If the qualifiers, i.e. currencies, are not the same
452+
return irInterCurrencyCorr_;
453+
}
454+
}
455+
456+
// Deal with inflation volatility correlations
457+
if (firstRt == RiskType::InflationVol && secondRt == RiskType::InflationVol) {
458+
return 1.0;
459+
}
460+
461461
// Deal with FX correlations
462462
// TODO:
463463
// For FXVol, qualifier is a currency pair. Is it possible to get here

0 commit comments

Comments
 (0)