Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Public Types | Public Member Functions | Private Member Functions | Private Attributes | List of all members
SimmCalculator Class Reference

A class to calculate SIMM given a set of aggregated CRIF results for one or more portfolios. More...

#include <orea/simm/simmcalculator.hpp>

+ Collaboration diagram for SimmCalculator:

Public Types

typedef SimmConfiguration::SimmSide SimmSide
 

Public Member Functions

 SimmCalculator (const ore::analytics::Crif &crif, const QuantLib::ext::shared_ptr< SimmConfiguration > &simmConfiguration, const std::string &calculationCcyCall="USD", const std::string &calculationCcyPost="USD", const std::string &resultCcy="", const QuantLib::ext::shared_ptr< ore::data::Market > market=nullptr, const bool determineWinningRegulations=true, const bool enforceIMRegulations=false, const bool quiet=false, const std::map< SimmSide, std::set< NettingSetDetails > > &hasSEC=std::map< SimmSide, std::set< NettingSetDetails > >(), const std::map< SimmSide, std::set< NettingSetDetails > > &hasCFTC=std::map< SimmSide, std::set< NettingSetDetails > >())
 
const void calculateRegulationSimm (const ore::analytics::Crif &crif, const ore::data::NettingSetDetails &nsd, const string &regulation, const SimmSide &side)
 Calculates SIMM for a given regulation under a given netting set. More...
 
const std::string & winningRegulations (const SimmSide &side, const ore::data::NettingSetDetails &nettingSetDetails) const
 Return the winning regulation for each netting set. More...
 
const std::map< ore::data::NettingSetDetails, string > & winningRegulations (const SimmSide &side) const
 
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, string > > & winningRegulations () const
 
const SimmResultssimmResults (const SimmSide &side, const ore::data::NettingSetDetails &nettingSetDetails, const std::string &regulation) const
 Give back the SIMM results container for the given portfolioId and SIMM side. More...
 
const std::map< std::string, SimmResults > & simmResults (const SimmSide &side, const ore::data::NettingSetDetails &nettingSetDetails) const
 
const std::map< ore::data::NettingSetDetails, std::map< std::string, SimmResults > > & simmResults (const SimmSide &side) const
 
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, SimmResults > > > & simmResults () const
 
const std::pair< std::string, SimmResults > & finalSimmResults (const SimmSide &side, const ore::data::NettingSetDetails &nettingSetDetails) const
 Give back the SIMM results container for the given netting set and SIMM side. More...
 
const std::map< ore::data::NettingSetDetails, std::pair< std::string, SimmResults > > & finalSimmResults (const SimmSide &side) const
 
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::pair< std::string, SimmResults > > > & finalSimmResults () const
 
const std::map< SimmSide, std::set< std::string > > & finalTradeIds () const
 
const CrifsimmParameters () const
 
const std::string & calculationCurrency (const SimmSide &side) const
 Return the calculator's calculation currency. More...
 
const std::string & resultCurrency () const
 Return the calculator's result currency. More...
 
void populateFinalResults (const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::string > > &winningRegulations)
 

Private Member Functions

std::pair< std::map< std::string, QuantLib::Real >, boolirDeltaMargin (const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const ore::analytics::Crif &netRecords, const SimmSide &side) const
 Calculate the Interest Rate delta margin component for the given portfolio and product class. More...
 
std::pair< std::map< std::string, QuantLib::Real >, boolirVegaMargin (const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const ore::analytics::Crif &netRecords, const SimmSide &side) const
 Calculate the Interest Rate vega margin component for the given portfolio and product class. More...
 
std::pair< std::map< std::string, QuantLib::Real >, boolirCurvatureMargin (const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const SimmSide &side, const ore::analytics::Crif &crif) const
 Calculate the Interest Rate curvature margin component for the given portfolio and product class. More...
 
std::pair< std::map< std::string, QuantLib::Real >, boolmargin (const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const CrifRecord::RiskType &rt, const ore::analytics::Crif &netRecords, const SimmSide &side) const
 
std::pair< std::map< std::string, QuantLib::Real >, boolcurvatureMargin (const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const CrifRecord::RiskType &rt, const SimmSide &side, const ore::analytics::Crif &netRecords, bool rfLabels=true) const
 
void calcAddMargin (const SimmSide &side, const ore::data::NettingSetDetails &nsd, const string &regulation, const ore::analytics::Crif &netRecords)
 Calculate the additional initial margin for the portfolio ID and regulation. More...
 
void populateResults (const SimmSide &side, const ore::data::NettingSetDetails &nsd, const string &regulation)
 
void populateFinalResults ()
 
void add (const ore::data::NettingSetDetails &nettingSetDetails, const string &regulation, const CrifRecord::ProductClass &pc, const SimmConfiguration::RiskClass &rc, const SimmConfiguration::MarginType &mt, const std::string &b, QuantLib::Real margin, SimmSide side, const bool overwrite=true)
 
void add (const ore::data::NettingSetDetails &nettingSetDetails, const string &regulation, const CrifRecord::ProductClass &pc, const SimmConfiguration::RiskClass &rc, const SimmConfiguration::MarginType &mt, const std::map< std::string, QuantLib::Real > &margins, SimmSide side, const bool overwrite=true)
 
void splitCrifByRegulationsAndPortfolios (const Crif &crif, const bool enforceIMRegulations)
 Add CRIF record to the CRIF records container that correspondsd to the given regulation/s and portfolio ID. More...
 
QuantLib::Real lambda (QuantLib::Real theta) const
 Give the \(\lambda\) used in the curvature margin calculation. More...
 
std::set< std::string > getQualifiers (const Crif &crif, const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const std::vector< CrifRecord::RiskType > &riskTypes) const
 

Private Attributes

ore::analytics::Crif crif_
 All the net sensitivities passed in for the calculation. More...
 
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, Crif > > > regSensitivities_
 Net sentivities at the regulation level within each netting set. More...
 
ore::analytics::Crif simmParameters_
 Record of SIMM parameters that were used in the calculation. More...
 
QuantLib::ext::shared_ptr< SimmConfigurationsimmConfiguration_
 The SIMM configuration governing the calculation. More...
 
std::string calculationCcyCall_
 The SIMM exposure calculation currency i.e. the currency for which FX delta risk is ignored. More...
 
std::string calculationCcyPost_
 
std::string resultCcy_
 The SIMM result currency i.e. the currency in which the main SIMM results are denominated. More...
 
QuantLib::ext::shared_ptr< ore::data::Marketmarket_
 Market data for FX rates to use for converting amounts to USD. More...
 
bool quiet_
 If true, no logging is written out. More...
 
std::map< SimmSide, std::set< NettingSetDetails > > hasSEC_
 
std::map< SimmSide, std::set< NettingSetDetails > > hasCFTC_
 
std::map< ore::data::NettingSetDetails, boolcollectRegsIsEmpty_
 For each netting set, whether all CRIF records' collect regulations are empty. More...
 
std::map< ore::data::NettingSetDetails, boolpostRegsIsEmpty_
 For each netting set, whether all CRIF records' post regulations are empty. More...
 
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::string > > winningRegulations_
 Regulation with highest initial margin for each given netting set. More...
 
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, SimmResults > > > simmResults_
 
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::pair< std::string, SimmResults > > > finalSimmResults_
 
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, set< string > > > > tradeIds_
 Container for keeping track of what trade IDs belong to each regulation. More...
 
std::map< SimmSide, set< string > > finalTradeIds_
 

Detailed Description

A class to calculate SIMM given a set of aggregated CRIF results for one or more portfolios.

Definition at line 36 of file simmcalculator.hpp.

Member Typedef Documentation

◆ SimmSide

Definition at line 38 of file simmcalculator.hpp.

Constructor & Destructor Documentation

◆ SimmCalculator()

SimmCalculator ( const ore::analytics::Crif crif,
const QuantLib::ext::shared_ptr< SimmConfiguration > &  simmConfiguration,
const std::string &  calculationCcyCall = "USD",
const std::string &  calculationCcyPost = "USD",
const std::string &  resultCcy = "",
const QuantLib::ext::shared_ptr< ore::data::Market market = nullptr,
const bool  determineWinningRegulations = true,
const bool  enforceIMRegulations = false,
const bool  quiet = false,
const std::map< SimmSide, std::set< NettingSetDetails > > &  hasSEC = std::map<SimmSide, std::set<NettingSetDetails>>(),
const std::map< SimmSide, std::set< NettingSetDetails > > &  hasCFTC = std::map<SimmSide, std::set<NettingSetDetails>>() 
)

Construct the SimmCalculator from a container of netted CRIF records and a SIMM configuration. The SIMM number is initially calculated in USD using the AmountUSD column. It can optionally be converted to a calculation currency other than USD by using the calculationCcy parameter. If the calculationCcy is not USD then the usdSpot parameter must be used to give the FX spot rate between USD and the calculationCcy. This spot rate is interpreted as the number of USD per unit of calculationCcy.

Definition at line 65 of file simmcalculator.cpp.

72 : simmConfiguration_(simmConfiguration), calculationCcyCall_(calculationCcyCall),
73 calculationCcyPost_(calculationCcyPost), resultCcy_(resultCcy.empty() ? calculationCcyCall_ : resultCcy),
74 market_(market), quiet_(quiet), hasSEC_(hasSEC), hasCFTC_(hasCFTC) {
75
76 QL_REQUIRE(checkCurrency(calculationCcyCall_), "SIMM Calculator: The Call side calculation currency ("
77 << calculationCcyCall_ << ") must be a valid ISO currency code");
78 QL_REQUIRE(checkCurrency(calculationCcyPost_), "SIMM Calculator: The Post side calculation currency ("
79 << calculationCcyPost_ << ") must be a valid ISO currency code");
80 QL_REQUIRE(checkCurrency(resultCcy_),
81 "SIMM Calculator: The result currency (" << resultCcy_ << ") must be a valid ISO currency code");
82
83 for (const CrifRecord& cr : crif) {
84 // Remove empty
85 if (cr.riskType == CrifRecord::RiskType::Empty) {
86 continue;
87 }
88 // Remove Schedule-only CRIF records
89 const bool isSchedule = cr.imModel == "Schedule";
90 if (isSchedule) {
91 if (!quiet_ && determineWinningRegulations) {
92 ore::data::StructuredTradeWarningMessage(cr.tradeId, cr.tradeType, "SIMM calculator", "Skipping over Schedule CRIF record").log();
93 }
94 continue;
95 }
96
97
98
99 // Check for each netting set whether post/collect regs are populated at all
100 if (collectRegsIsEmpty_.find(cr.nettingSetDetails) == collectRegsIsEmpty_.end()) {
101 collectRegsIsEmpty_[cr.nettingSetDetails] = cr.collectRegulations.empty();
102 } else if (collectRegsIsEmpty_.at(cr.nettingSetDetails) && !cr.collectRegulations.empty()) {
103 collectRegsIsEmpty_.at(cr.nettingSetDetails) = false;
104 }
105 if (postRegsIsEmpty_.find(cr.nettingSetDetails) == postRegsIsEmpty_.end()) {
106 postRegsIsEmpty_[cr.nettingSetDetails] = cr.postRegulations.empty();
107 } else if (postRegsIsEmpty_.at(cr.nettingSetDetails) && !cr.postRegulations.empty()) {
108 postRegsIsEmpty_.at(cr.nettingSetDetails) = false;
109 }
110
111 // Make sure we have CRIF amount denominated in the result ccy
112 CrifRecord newCrifRecord = cr;
113
114 if (cr.requiresAmountUsd() && resultCcy_ == "USD" && cr.hasAmountUsd()) {
115 newCrifRecord.amountResultCcy = newCrifRecord.amountUsd;
116 } else if(cr.requiresAmountUsd()) {
117 // ProductClassMultiplier and AddOnNotionalFactor don't have a currency and dont need to be converted,
118 // we use the amount
119 const Real fxSpot = market_->fxRate(newCrifRecord.amountCurrency + resultCcy_)->value();
120 newCrifRecord.amountResultCcy = fxSpot * newCrifRecord.amount;
121 }
122 newCrifRecord.resultCurrency = resultCcy_;
123
124 crif_.addRecord(newCrifRecord);
125 }
126
127 // If there are no CRIF records to process
128 if (crif_.empty())
129 return;
130
131 // Add CRIF records to each regulation under each netting set
132 if (!quiet_) {
133 LOG("SimmCalculator: Splitting up original CRIF records into their respective collect/post regulations");
134 }
135
136 splitCrifByRegulationsAndPortfolios(crif_, enforceIMRegulations);
137
138 // Some additional processing depending on the regulations applicable to each netting set
139 for (auto& [side, nettingsSetCrifMap] : regSensitivities_) {
140 for (auto& [nettingDetails, regulationCrifMap] : nettingsSetCrifMap) {
141 // Where there is SEC and CFTC in the portfolio, we add the CFTC trades to SEC,
142 // but still continue with CFTC calculations
143 const bool hasCFTCGlobal = hasCFTC_[side].find(nettingDetails) != hasCFTC_[side].end();
144 const bool hasSECGlobal = hasSEC_[side].find(nettingDetails) != hasSEC_[side].end();
145 const bool hasSECLocal = regulationCrifMap.find("SEC") != regulationCrifMap.end();
146 const bool hasCFTCLocal = regulationCrifMap.find("CFTC") != regulationCrifMap.end();
147
148 if ((hasSECLocal && hasCFTCLocal) || (hasCFTCGlobal && hasSECGlobal)) {
149 if (!hasSECLocal) {
150 if (!hasCFTCLocal) {
151 continue;
152 } else {
153 regulationCrifMap["SEC"] = Crif();
154 }
155 }
156
157 if (hasCFTCLocal) {
158 // At this point, we expect to have both SEC and CFTC sensitivities for the netting set
159 const auto& crifCFTC = regulationCrifMap["CFTC"];
160 const auto& crifSEC = regulationCrifMap["SEC"];
161 for (const auto& cr :crifCFTC) {
162 // Only add CFTC records to SEC if the record was not already in SEC,
163 // i.e. we skip over CRIF records with regulations specified as e.g. "..., CFTC, SEC, ..."
164 if (crifSEC.find(cr) == crifSEC.end()) {
165 if (!quiet_) {
166 DLOG("SimmCalculator: Inserting CRIF record with CFTC "
167 << nettingDetails << " regulation into SEC CRIF records: " << cr);
168 }
169 regulationCrifMap["SEC"].addRecord(cr);
170 }
171 }
172 }
173
174 }
175 // Aggreggate now all Crif Records
176 for (auto& [regulation, crif] : regulationCrifMap) {
177 crif = crif.aggregate();
178 }
179
180 // If netting set has "Unspecified" plus other regulations, the "Unspecified" sensis are to be excluded.
181 // If netting set only has "Unspecified", then no regulations were ever specified, so all trades are
182 // included.
183 if (regulationCrifMap.count("Unspecified") > 0 && regulationCrifMap.size() > 1)
184 regulationCrifMap.erase("Unspecified");
185 }
186 }
187
188 // Calculate SIMM call and post for each regulation under each netting set
189 for (const auto& [side, nettingSetRegulationCrifMap] : regSensitivities_) {
190 for (const auto& [nsd, regulationCrifMap] : nettingSetRegulationCrifMap) {
191 // Calculate SIMM for particular side-nettingSet-regulation combination
192 for (const auto& [regulation, crif] : regulationCrifMap) {
193 bool hasFixedAddOn = false;
194 for (const auto& sp : crif) {
195 if (sp.riskType == RiskType::AddOnFixedAmount) {
196 hasFixedAddOn = true;
197 break;
198 }
199 }
200 if (crif.hasCrifRecords() || hasFixedAddOn)
201 calculateRegulationSimm(crif, nsd, regulation, side);
202 }
203 }
204 }
205
206 // Determine winning call and post regulations
207 if (determineWinningRegulations) {
208 if (!quiet_) {
209 LOG("SimmCalculator: Determining winning regulations");
210 }
211
212 for (auto sv : simmResults_) {
213 const SimmSide side = sv.first;
214
215 // Determine winning (call and post) regulation for each netting set
216 for (const auto& kv : sv.second) {
217
218 // Collect margin amounts and determine the highest margin amount
219 Real winningMargin = std::numeric_limits<Real>::min();
220 map<string, Real> nettingSetMargins;
221 std::vector<Real> margins;
222 for (const auto& regSimmResults : kv.second) {
223 const Real& im = regSimmResults.second.get(ProductClass::All, RiskClass::All, MarginType::All, "All");
224 nettingSetMargins[regSimmResults.first] = im;
225 if (im > winningMargin)
226 winningMargin = im;
227 }
228
229 // Determine winning regulations, i.e. regulations under which we find the highest margin amount
230 std::vector<string> winningRegulations;
231 for (const auto& kv : nettingSetMargins) {
232 if (close_enough(kv.second, winningMargin))
233 winningRegulations.push_back(kv.first);
234 }
235
236 // In the case of multiple winning regulations, pick one based on the priority in the list
237 //const Regulation winningRegulation = oreplus::analytics::getWinningRegulation(winningRegulations);
238 string winningRegulation = winningRegulations.size() > 1
240 : winningRegulations.at(0);
241
242 // Populate internal list of winning regulators
243 winningRegulations_[side][kv.first] = to_string(winningRegulation);
244 }
245 }
246
248 }
249}
bool empty() const
Definition: crif.hpp:55
void addRecord(const CrifRecord &record, bool aggregateDifferentAmountCurrencies=false, bool sortFxVolQualifer=true)
Definition: crif.cpp:41
std::map< SimmSide, std::set< NettingSetDetails > > hasCFTC_
std::map< SimmSide, std::set< NettingSetDetails > > hasSEC_
std::string calculationCcyCall_
The SIMM exposure calculation currency i.e. the currency for which FX delta risk is ignored.
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::string > > winningRegulations_
Regulation with highest initial margin for each given netting set.
std::map< ore::data::NettingSetDetails, bool > collectRegsIsEmpty_
For each netting set, whether all CRIF records' collect regulations are empty.
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, SimmResults > > > simmResults_
std::map< ore::data::NettingSetDetails, bool > postRegsIsEmpty_
For each netting set, whether all CRIF records' post regulations are empty.
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, string > > & winningRegulations() const
QuantLib::ext::shared_ptr< SimmConfiguration > simmConfiguration_
The SIMM configuration governing the calculation.
QuantLib::ext::shared_ptr< ore::data::Market > market_
Market data for FX rates to use for converting amounts to USD.
bool quiet_
If true, no logging is written out.
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, Crif > > > regSensitivities_
Net sentivities at the regulation level within each netting set.
ore::analytics::Crif crif_
All the net sensitivities passed in for the calculation.
void splitCrifByRegulationsAndPortfolios(const Crif &crif, const bool enforceIMRegulations)
Add CRIF record to the CRIF records container that correspondsd to the given regulation/s and portfol...
std::string resultCcy_
The SIMM result currency i.e. the currency in which the main SIMM results are denominated.
const void calculateRegulationSimm(const ore::analytics::Crif &crif, const ore::data::NettingSetDetails &nsd, const string &regulation, const SimmSide &side)
Calculates SIMM for a given regulation under a given netting set.
SimmConfiguration::SimmSide SimmSide
bool checkCurrency(const string &code)
#define LOG(text)
#define DLOG(text)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
SimmConfiguration::Regulation getWinningRegulation(const std::vector< string > &winningRegulations)
From a vector of regulations, determine the winning regulation based on order of priority.
std::string to_string(const LocationInfo &l)
+ Here is the call graph for this function:

Member Function Documentation

◆ calculateRegulationSimm()

const void calculateRegulationSimm ( const ore::analytics::Crif crif,
const ore::data::NettingSetDetails nsd,
const string &  regulation,
const SimmSide side 
)

Calculates SIMM for a given regulation under a given netting set.

Definition at line 251 of file simmcalculator.cpp.

253 {
254
255 if (!quiet_) {
256 LOG("SimmCalculator: Calculating SIMM " << side << " for portfolio [" << nettingSetDetails << "], regulation "
257 << regulation);
258 }
259 // Loop over portfolios and product classes
260 for(const auto productClass : crif.ProductClassesByNettingSetDetails(nettingSetDetails)){
261 if (!quiet_) {
262 LOG("SimmCalculator: Calculating SIMM for product class " << productClass);
263 }
264
265 // Delta margin components
266 RiskClass rc = RiskClass::InterestRate;
267 MarginType mt = MarginType::Delta;
268 auto p = irDeltaMargin(nettingSetDetails, productClass, crif, side);
269 if (p.second)
270 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
271
272 rc = RiskClass::FX;
273 p = margin(nettingSetDetails, productClass, RiskType::FX, crif, side);
274 if (p.second)
275 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
276
277 rc = RiskClass::CreditQualifying;
278 p = margin(nettingSetDetails, productClass, RiskType::CreditQ, crif, side);
279 if (p.second)
280 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
281
282 rc = RiskClass::CreditNonQualifying;
283 p = margin(nettingSetDetails, productClass, RiskType::CreditNonQ, crif, side);
284 if (p.second)
285 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
286
287 rc = RiskClass::Equity;
288 p = margin(nettingSetDetails, productClass, RiskType::Equity, crif, side);
289 if (p.second)
290 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
291
292 rc = RiskClass::Commodity;
293 p = margin(nettingSetDetails, productClass, RiskType::Commodity, crif, side);
294 if (p.second)
295 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
296
297 // Vega margin components
298 mt = MarginType::Vega;
299 rc = RiskClass::InterestRate;
300 p = irVegaMargin(nettingSetDetails, productClass, crif, side);
301 if (p.second)
302 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
303
304 rc = RiskClass::FX;
305 p = margin(nettingSetDetails, productClass, RiskType::FXVol, crif, side);
306 if (p.second)
307 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
308
309 rc = RiskClass::CreditQualifying;
310 p = margin(nettingSetDetails, productClass, RiskType::CreditVol, crif, side);
311 if (p.second)
312 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
313
314 rc = RiskClass::CreditNonQualifying;
315 p = margin(nettingSetDetails, productClass, RiskType::CreditVolNonQ, crif, side);
316 if (p.second)
317 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
318
319 rc = RiskClass::Equity;
320 p = margin(nettingSetDetails, productClass, RiskType::EquityVol, crif, side);
321 if (p.second)
322 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
323
324 rc = RiskClass::Commodity;
325 p = margin(nettingSetDetails, productClass, RiskType::CommodityVol, crif, side);
326 if (p.second)
327 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
328
329 // Curvature margin components for sides call and post
330 mt = MarginType::Curvature;
331 rc = RiskClass::InterestRate;
332
333 p = irCurvatureMargin(nettingSetDetails, productClass, side, crif);
334 if (p.second)
335 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
336
337 rc = RiskClass::FX;
338 p = curvatureMargin(nettingSetDetails, productClass, RiskType::FXVol, side, crif, false);
339 if (p.second)
340 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
341
342 rc = RiskClass::CreditQualifying;
343 p = curvatureMargin(nettingSetDetails, productClass, RiskType::CreditVol, side, crif);
344 if (p.second)
345 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
346
347 rc = RiskClass::CreditNonQualifying;
348 p = curvatureMargin(nettingSetDetails, productClass, RiskType::CreditVolNonQ, side, crif);
349 if (p.second)
350 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
351
352 rc = RiskClass::Equity;
353 p = curvatureMargin(nettingSetDetails, productClass, RiskType::EquityVol, side, crif, false);
354 if (p.second)
355 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
356
357 rc = RiskClass::Commodity;
358 p = curvatureMargin(nettingSetDetails, productClass, RiskType::CommodityVol, side, crif, false);
359 if (p.second)
360 add(nettingSetDetails, regulation, productClass, rc, mt, p.first, side);
361
362 // Base correlation margin components. This risk type came later so need to check
363 // first if it is valid under the configuration
364 if (simmConfiguration_->isValidRiskType(RiskType::BaseCorr)) {
365 p = margin(nettingSetDetails, productClass, RiskType::BaseCorr, crif, side);
366 if (p.second)
367 add(nettingSetDetails, regulation, productClass, RiskClass::CreditQualifying, MarginType::BaseCorr,
368 p.first, side);
369 }
370 }
371
372 // Calculate the higher level margins
373 populateResults(side, nettingSetDetails, regulation);
374
375 // For each portfolio, calculate the additional margin
376 calcAddMargin(side, nettingSetDetails, regulation, crif);
377}
std::set< CrifRecord::ProductClass > ProductClassesByNettingSetDetails(const NettingSetDetails nsd) const
Definition: crif.cpp:324
std::pair< std::map< std::string, QuantLib::Real >, bool > margin(const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const CrifRecord::RiskType &rt, const ore::analytics::Crif &netRecords, const SimmSide &side) const
std::pair< std::map< std::string, QuantLib::Real >, bool > irDeltaMargin(const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const ore::analytics::Crif &netRecords, const SimmSide &side) const
Calculate the Interest Rate delta margin component for the given portfolio and product class.
std::pair< std::map< std::string, QuantLib::Real >, bool > irVegaMargin(const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const ore::analytics::Crif &netRecords, const SimmSide &side) const
Calculate the Interest Rate vega margin component for the given portfolio and product class.
void calcAddMargin(const SimmSide &side, const ore::data::NettingSetDetails &nsd, const string &regulation, const ore::analytics::Crif &netRecords)
Calculate the additional initial margin for the portfolio ID and regulation.
std::pair< std::map< std::string, QuantLib::Real >, bool > irCurvatureMargin(const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const SimmSide &side, const ore::analytics::Crif &crif) const
Calculate the Interest Rate curvature margin component for the given portfolio and product class.
std::pair< std::map< std::string, QuantLib::Real >, bool > curvatureMargin(const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const CrifRecord::RiskType &rt, const SimmSide &side, const ore::analytics::Crif &netRecords, bool rfLabels=true) const
void add(const ore::data::NettingSetDetails &nettingSetDetails, const string &regulation, const CrifRecord::ProductClass &pc, const SimmConfiguration::RiskClass &rc, const SimmConfiguration::MarginType &mt, const std::string &b, QuantLib::Real margin, SimmSide side, const bool overwrite=true)
void populateResults(const SimmSide &side, const ore::data::NettingSetDetails &nsd, const string &regulation)
SimmConfiguration::RiskClass RiskClass
SimmConfiguration::MarginType MarginType
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ winningRegulations() [1/3]

const string & winningRegulations ( const SimmSide side,
const ore::data::NettingSetDetails nettingSetDetails 
) const

Return the winning regulation for each netting set.

Definition at line 379 of file simmcalculator.cpp.

379 {
380 const auto& subWinningRegs = winningRegulations(side);
381 QL_REQUIRE(subWinningRegs.find(nettingSetDetails) != subWinningRegs.end(),
382 "SimmCalculator::winningRegulations(): Could not find netting set in the list of "
383 << side << " IM winning regulations: " << nettingSetDetails);
384 return subWinningRegs.at(nettingSetDetails);
385}
+ Here is the call graph for this function:

◆ winningRegulations() [2/3]

const map< NettingSetDetails, string > & winningRegulations ( const SimmSide side) const

Definition at line 387 of file simmcalculator.cpp.

387 {
388 QL_REQUIRE(winningRegulations_.find(side) != winningRegulations_.end(),
389 "SimmCalculator::winningRegulations(): Could not find list of" << side << " IM winning regulations");
390 return winningRegulations_.at(side);
391}

◆ winningRegulations() [3/3]

const map< SimmSide, map< NettingSetDetails, string > > & winningRegulations ( ) const

Definition at line 393 of file simmcalculator.cpp.

393 {
394 return winningRegulations_;
395}
+ Here is the caller graph for this function:

◆ simmResults() [1/4]

const SimmResults & simmResults ( const SimmSide side,
const ore::data::NettingSetDetails nettingSetDetails,
const std::string &  regulation 
) const

Give back the SIMM results container for the given portfolioId and SIMM side.

Definition at line 397 of file simmcalculator.cpp.

398 {
399 const auto& subResults = simmResults(side, nettingSetDetails);
400 QL_REQUIRE(subResults.find(regulation) != subResults.end(),
401 "SimmCalculator::simmResults(): Could not find regulation in the SIMM "
402 << side << " results for netting set [" << nettingSetDetails << "]: " << regulation);
403 return subResults.at(regulation);
404}
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, SimmResults > > > & simmResults() const
+ Here is the call graph for this function:

◆ simmResults() [2/4]

const map< string, SimmResults > & simmResults ( const SimmSide side,
const ore::data::NettingSetDetails nettingSetDetails 
) const

Definition at line 406 of file simmcalculator.cpp.

407 {
408 const auto& subResults = simmResults(side);
409 QL_REQUIRE(subResults.find(nettingSetDetails) != subResults.end(),
410 "SimmCalculator::simmResults(): Could not find netting set in the SIMM "
411 << side << " results: " << nettingSetDetails);
412 return subResults.at(nettingSetDetails);
413}
+ Here is the call graph for this function:

◆ simmResults() [3/4]

const map< NettingSetDetails, map< string, SimmResults > > & simmResults ( const SimmSide side) const

Give back a map containing the SIMM results containers for every portfolio for the given SIMM side. The key is the portfolio ID and the value is the SIMM results container for that portfolio.

Definition at line 415 of file simmcalculator.cpp.

415 {
416 QL_REQUIRE(simmResults_.find(side) != simmResults_.end(),
417 "SimmCalculator::simmResults(): Could not find " << side << " IM in the SIMM results");
418 return simmResults_.at(side);
419}

◆ simmResults() [4/4]

const map< SimmSide, map< NettingSetDetails, map< string, SimmResults > > > & simmResults ( ) const

Definition at line 421 of file simmcalculator.cpp.

421 {
422 return simmResults_;
423}
+ Here is the caller graph for this function:

◆ finalSimmResults() [1/3]

const pair< string, SimmResults > & finalSimmResults ( const SimmSide side,
const ore::data::NettingSetDetails nettingSetDetails 
) const

Give back the SIMM results container for the given netting set and SIMM side.

Definition at line 425 of file simmcalculator.cpp.

426 {
427 const auto& subResults = finalSimmResults(side);
428 QL_REQUIRE(subResults.find(nettingSetDetails) != subResults.end(),
429 "SimmCalculator::finalSimmResults(): Could not find netting set in the final SIMM "
430 << side << " results: " << nettingSetDetails);
431 return subResults.at(nettingSetDetails);
432}
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::pair< std::string, SimmResults > > > & finalSimmResults() const
+ Here is the call graph for this function:

◆ finalSimmResults() [2/3]

const map< NettingSetDetails, pair< string, SimmResults > > & finalSimmResults ( const SimmSide side) const

Give back a map containing the SIMM results containers for every portfolio for the given SIMM side. The key is the portfolio ID and the value is a map, with regulation as the key, and the value is the SIMM results container for that portfolioId-regulation combination.

Definition at line 434 of file simmcalculator.cpp.

434 {
435 QL_REQUIRE(finalSimmResults_.find(side) != finalSimmResults_.end(),
436 "SimmCalculator::finalSimmResults(): Could not find " << side << " IM in the final SIMM results");
437 return finalSimmResults_.at(side);
438}
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::pair< std::string, SimmResults > > > finalSimmResults_

◆ finalSimmResults() [3/3]

const map< SimmSide, map< NettingSetDetails, pair< string, SimmResults > > > & finalSimmResults ( ) const

Definition at line 440 of file simmcalculator.cpp.

440 {
441 return finalSimmResults_;
442}
+ Here is the caller graph for this function:

◆ finalTradeIds()

const std::map< SimmSide, std::set< std::string > > & finalTradeIds ( ) const

Definition at line 93 of file simmcalculator.hpp.

93{ return finalTradeIds_; }
std::map< SimmSide, set< string > > finalTradeIds_

◆ simmParameters()

const Crif & simmParameters ( ) const

Definition at line 95 of file simmcalculator.hpp.

95{ return simmParameters_; }
ore::analytics::Crif simmParameters_
Record of SIMM parameters that were used in the calculation.

◆ calculationCurrency()

const std::string & calculationCurrency ( const SimmSide side) const

Return the calculator's calculation currency.

Definition at line 98 of file simmcalculator.hpp.

98 {
99 return side == SimmSide::Call ? calculationCcyCall_ : calculationCcyPost_;
100 }

◆ resultCurrency()

const std::string & resultCurrency ( ) const

Return the calculator's result currency.

Definition at line 103 of file simmcalculator.hpp.

103{ return resultCcy_; }

◆ populateFinalResults() [1/2]

void populateFinalResults ( const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::string > > &  winningRegulations)

Populate the finalSimmResults_ and finalAddMargins_ containers using the provided map of winning call/post regulations.

◆ irDeltaMargin()

pair< map< string, Real >, bool > irDeltaMargin ( const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const ore::analytics::Crif netRecords,
const SimmSide side 
) const
private

Calculate the Interest Rate delta margin component for the given portfolio and product class.

Definition at line 444 of file simmcalculator.cpp.

446 {
447 const string& calcCcy = side == SimmSide::Call ? calculationCcyCall_ : calculationCcyPost_;
448
449 // "Bucket" here referse to exposures under the CRIF qualifiers
450 map<string, Real> bucketMargins;
451
452 // Get alls IR qualifiers
453 set<string> qualifiers =
454 getQualifiers(crif, nettingSetDetails, pc, {RiskType::IRCurve, RiskType::XCcyBasis, RiskType::Inflation});
455
456 // If there are no qualifiers, return early and set bool to false to indicate margin does not apply
457 if (qualifiers.empty()) {
458 bucketMargins["All"] = 0.0;
459 return make_pair(bucketMargins, false);
460 }
461
462 // Hold the concentration risk for each qualifier i.e. $CR_b$ from SIMM docs
463 map<string, Real> concentrationRisk;
464 // The delta margin for each currency i.e. $K_b$ from SIMM docs
465 map<string, Real> deltaMargin;
466 // The sum of the weighted sensitivities for each currency i.e. $\sum_{i,k} WS_{k,i}$ from SIMM docs
467 map<string, Real> sumWeightedSensis;
468
469 // Loop over the qualifiers i.e. currencies
470 // Loop over the qualifiers i.e. currencies
471 for (const auto& qualifier : qualifiers) {
472 // Pair of iterators to start and end of IRCurve sensitivities with current qualifier
473 auto pIrQualifier =
474 crif.filterByQualifier(nettingSetDetails, pc, RiskType::IRCurve, qualifier);
475
476 // Iterator to Xccy basis element with current qualifier (expect zero or one element)
477 auto XccyCount = crif.countMatching(nettingSetDetails, pc, RiskType::XCcyBasis, qualifier);
478 QL_REQUIRE(XccyCount < 2, "SIMM Calcuator: Expected either 0 or 1 elements for risk type "
479 << RiskType::XCcyBasis << " and qualifier " << qualifier << " but got "
480 << XccyCount);
481 auto itXccy = crif.findBy(nettingSetDetails, pc, RiskType::XCcyBasis, qualifier);
482
483 // Iterator to inflation element with current qualifier (expect zero or one element)
484 auto inflationCount = crif.countMatching(nettingSetDetails, pc, RiskType::Inflation, qualifier);
485 QL_REQUIRE(inflationCount < 2, "SIMM Calculator: Expected either 0 or 1 elements for risk type "
486 << RiskType::Inflation << " and qualifier " << qualifier << " but got "
487 << inflationCount);
488 auto itInflation = crif.findBy(nettingSetDetails, pc, RiskType::Inflation, qualifier);
489
490 // One pass to get the concentration risk for this qualifier
491 // Note: XccyBasis is not included in the calculation of concentration risk and the XccyBasis sensitivity
492 // is not scaled by it
493 for (const auto& it : pIrQualifier) {
494 concentrationRisk[qualifier] += it.amountResultCcy;
495 }
496 // Add inflation sensitivity to the concentration risk
497 if (itInflation != crif.end()){
498 concentrationRisk[qualifier] += itInflation->amountResultCcy;
499 }
500 // Divide by the concentration risk threshold
501 Real concThreshold = simmConfiguration_->concentrationThreshold(RiskType::IRCurve, qualifier);
502 if (resultCcy_ != "USD")
503 concThreshold *= market_->fxRate("USD" + resultCcy_)->value();
504 concentrationRisk[qualifier] /= concThreshold;
505 // Final concentration risk amount
506 concentrationRisk[qualifier] = max(1.0, sqrt(std::abs(concentrationRisk[qualifier])));
507
508 // Calculate the delta margin piece for this qualifier i.e. $K_b$ from SIMM docs
509 for (auto itOuter = pIrQualifier.begin(); itOuter != pIrQualifier.end(); ++itOuter) {
510 // Risk weight i.e. $RW_k$ from SIMM docs
511 Real rwOuter = simmConfiguration_->weight(RiskType::IRCurve, qualifier, itOuter->label1);
512 // Weighted sensitivity i.e. $WS_{k,i}$ from SIMM docs
513 Real wsOuter = rwOuter * itOuter->amountResultCcy * concentrationRisk[qualifier];
514 // Update weighted sensitivity sum
515 sumWeightedSensis[qualifier] += wsOuter;
516 // Add diagonal element to delta margin
517 deltaMargin[qualifier] += wsOuter * wsOuter;
518 // Add the cross elements to the delta margin
519 for (auto itInner = pIrQualifier.begin(); itInner != itOuter; ++itInner) {
520 // Label2 level correlation i.e. $\phi_{i,j}$ from SIMM docs
521 Real subCurveCorr = simmConfiguration_->correlation(RiskType::IRCurve, qualifier, "", itOuter->label2,
522 RiskType::IRCurve, qualifier, "", itInner->label2);
523 // Label1 level correlation i.e. $\rho_{k,l}$ from SIMM docs
524 Real tenorCorr = simmConfiguration_->correlation(RiskType::IRCurve, qualifier, itOuter->label1, "",
525 RiskType::IRCurve, qualifier, itInner->label1, "");
526 // Add cross element to delta margin
527 Real rwInner = simmConfiguration_->weight(RiskType::IRCurve, qualifier, itInner->label1);
528 Real wsInner = rwInner * itInner->amountResultCcy * concentrationRisk[qualifier];
529 deltaMargin[qualifier] += 2 * subCurveCorr * tenorCorr * wsOuter * wsInner;
530 }
531 }
532
533 // Add the Inflation component, if any
534 Real wsInflation = 0.0;
535 if (itInflation != crif.end()) {
536 // Risk weight
537 Real rwInflation = simmConfiguration_->weight(RiskType::Inflation, qualifier, itInflation->label1);
538 // Weighted sensitivity
539 wsInflation = rwInflation * itInflation->amountResultCcy * concentrationRisk[qualifier];
540 // Update weighted sensitivity sum
541 sumWeightedSensis[qualifier] += wsInflation;
542 // Add diagonal element to delta margin
543 deltaMargin[qualifier] += wsInflation * wsInflation;
544 // Add the cross elements (Inflation with IRCurve tenors) to the delta margin
545 // Correlation (know that Label1 and Label2 do not matter)
546 Real corr = simmConfiguration_->correlation(RiskType::IRCurve, qualifier, "", "", RiskType::Inflation,
547 qualifier, "", "");
548 for (auto it = pIrQualifier.begin(); it != pIrQualifier.end(); ++it) {
549 // Add cross element to delta margin
550 Real rw = simmConfiguration_->weight(RiskType::IRCurve, qualifier, it->label1);
551 Real ws = rw * it->amountResultCcy * concentrationRisk[qualifier];
552 deltaMargin[qualifier] += 2 * corr * ws * wsInflation;
553 }
554 }
555
556 // Add the XccyBasis component, if any
557 if (itXccy != crif.end()) {
558 // Risk weight
559 Real rwXccy = simmConfiguration_->weight(RiskType::XCcyBasis, qualifier, itXccy->label1);
560 // Weighted sensitivity (no concentration risk here)
561 Real wsXccy = rwXccy * itXccy->amountResultCcy;
562 // Update weighted sensitivity sum
563 sumWeightedSensis[qualifier] += wsXccy;
564 // Add diagonal element to delta margin
565 deltaMargin[qualifier] += wsXccy * wsXccy;
566 // Add the cross elements (XccyBasis with IRCurve tenors) to the delta margin
567 // Correlation (know that Label1 and Label2 do not matter)
568 Real corr = simmConfiguration_->correlation(RiskType::IRCurve, qualifier, "", "", RiskType::XCcyBasis,
569 qualifier, "", "");
570 for (auto it = pIrQualifier.begin(); it != pIrQualifier.end(); ++it) {
571 // Add cross element to delta margin
572 Real rw = simmConfiguration_->weight(RiskType::IRCurve, qualifier, it->label1, calcCcy);
573 Real ws = rw * it->amountResultCcy * concentrationRisk[qualifier];
574 deltaMargin[qualifier] += 2 * corr * ws * wsXccy;
575 }
576
577 // Inflation vs. XccyBasis cross component if any
578 if (itInflation != crif.end()) {
579 // Correlation (know that Label1 and Label2 do not matter)
580 Real corr = simmConfiguration_->correlation(RiskType::Inflation, qualifier, "", "", RiskType::XCcyBasis,
581 qualifier, "", "");
582 deltaMargin[qualifier] += 2 * corr * wsInflation * wsXccy;
583 }
584 }
585
586 // Finally have the value of $K_b$
587 deltaMargin[qualifier] = sqrt(max(deltaMargin[qualifier], 0.0));
588 }
589
590 // Now calculate final IR delta margin by aggregating across currencies
591 Real margin = 0.0;
592 for (auto itOuter = qualifiers.begin(); itOuter != qualifiers.end(); ++itOuter) {
593 // Diagonal term
594 margin += deltaMargin.at(*itOuter) * deltaMargin.at(*itOuter);
595 // Cross terms
596 Real sOuter = max(min(sumWeightedSensis.at(*itOuter), deltaMargin.at(*itOuter)), -deltaMargin.at(*itOuter));
597 for (auto itInner = qualifiers.begin(); itInner != itOuter; ++itInner) {
598 Real sInner = max(min(sumWeightedSensis.at(*itInner), deltaMargin.at(*itInner)), -deltaMargin.at(*itInner));
599 Real g = min(concentrationRisk.at(*itOuter), concentrationRisk.at(*itInner)) /
600 max(concentrationRisk.at(*itOuter), concentrationRisk.at(*itInner));
601 Real corr = simmConfiguration_->correlation(RiskType::IRCurve, *itOuter, "", "", RiskType::IRCurve,
602 *itInner, "", "");
603 margin += 2.0 * sOuter * sInner * corr * g;
604 }
605 }
606 margin = sqrt(max(margin, 0.0));
607
608 for (const auto& m : deltaMargin)
609 bucketMargins[m.first] = m.second;
610 bucketMargins["All"] = margin;
611
612 return make_pair(bucketMargins, true);
613}
std::set< std::string > getQualifiers(const Crif &crif, const ore::data::NettingSetDetails &nettingSetDetails, const CrifRecord::ProductClass &pc, const std::vector< CrifRecord::RiskType > &riskTypes) const
RandomVariable max(RandomVariable x, const RandomVariable &y)
RandomVariable sqrt(RandomVariable x)
RandomVariable min(RandomVariable x, const RandomVariable &y)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ irVegaMargin()

pair< map< string, Real >, bool > irVegaMargin ( const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const ore::analytics::Crif netRecords,
const SimmSide side 
) const
private

Calculate the Interest Rate vega margin component for the given portfolio and product class.

Definition at line 615 of file simmcalculator.cpp.

617 {
618
619 const string& calcCcy = side == SimmSide::Call ? calculationCcyCall_ : calculationCcyPost_;
620
621 // "Bucket" here refers to exposures under the CRIF qualifiers
622 map<string, Real> bucketMargins;
623
624 // Find the set of qualifiers, i.e. currencies, in the Simm sensitivities
625 set<string> qualifiers = getQualifiers(crif, nettingSetDetails, pc, {RiskType::IRVol, RiskType::InflationVol});
626
627 // If there are no qualifiers, return early and set bool to false to indicate margin does not apply
628 if (qualifiers.empty()) {
629 bucketMargins["All"] = 0.0;
630 return make_pair(bucketMargins, false);
631 }
632
633 // Hold the concentration risk for each qualifier i.e. $VCR_b$ from SIMM docs
634 map<string, Real> concentrationRisk;
635 // The vega margin for each currency i.e. $K_b$ from SIMM docs
636 map<string, Real> vegaMargin;
637 // The sum of the weighted sensitivities for each currency i.e. $\sum_{k=1}^K VR_{k}$ from SIMM docs
638 map<string, Real> sumWeightedSensis;
639
640 // Loop over the qualifiers i.e. currencies
641 for (const auto& qualifier : qualifiers) {
642 // Pair of iterators to start and end of IRVol sensitivities with current qualifier
643 auto pIrQualifier = crif.filterByQualifier(nettingSetDetails, pc, RiskType::IRVol, qualifier);
644
645 // Pair of iterators to start and end of InflationVol sensitivities with current qualifier
646 auto pInfQualifier = crif.filterByQualifier(nettingSetDetails, pc, RiskType::InflationVol, qualifier);
647
648 // One pass to get the concentration risk for this qualifier
649 for (const auto& it : pIrQualifier) {
650 concentrationRisk[qualifier] += it.amountResultCcy;
651 }
652 for (const auto& it : pInfQualifier) {
653 concentrationRisk[qualifier] += it.amountResultCcy;
654 }
655 // Divide by the concentration risk threshold
656 Real concThreshold = simmConfiguration_->concentrationThreshold(RiskType::IRVol, qualifier);
657 if (resultCcy_ != "USD")
658 concThreshold *= market_->fxRate("USD" + resultCcy_)->value();
659 concentrationRisk[qualifier] /= concThreshold;
660
661 // Final concentration risk amount
662 concentrationRisk[qualifier] = max(1.0, sqrt(std::abs(concentrationRisk[qualifier])));
663
664 // Calculate the margin piece for this qualifier i.e. $K_b$ from SIMM docs
665 // Start with IRVol vs. IRVol components
666 for (auto itOuter = pIrQualifier.begin(); itOuter != pIrQualifier.end(); ++itOuter) {
667 // Risk weight i.e. $RW_k$ from SIMM docs
668 Real rwOuter = simmConfiguration_->weight(RiskType::IRVol, qualifier, itOuter->label1);
669 // Weighted sensitivity i.e. $WS_{k,i}$ from SIMM docs
670 Real wsOuter = rwOuter * itOuter->amountResultCcy * concentrationRisk[qualifier];
671 // Update weighted sensitivity sum
672 sumWeightedSensis[qualifier] += wsOuter;
673 // Add diagonal element to vega margin
674 vegaMargin[qualifier] += wsOuter * wsOuter;
675 // Add the cross elements to the vega margin
676 for (auto itInner = pIrQualifier.begin(); itInner != itOuter; ++itInner) {
677 // Label1 level correlation i.e. $\rho_{k,l}$ from SIMM docs
678 Real corr = simmConfiguration_->correlation(RiskType::IRVol, qualifier, itOuter->label1, "",
679 RiskType::IRVol, qualifier, itInner->label1, "");
680 // Add cross element to vega margin
681 Real rwInner = simmConfiguration_->weight(RiskType::IRVol, qualifier, itInner->label1);
682 Real wsInner = rwInner * itInner->amountResultCcy * concentrationRisk[qualifier];
683 vegaMargin[qualifier] += 2 * corr * wsOuter * wsInner;
684 }
685 }
686
687 // Now deal with inflation component
688 // To be generic/future-proof, assume that we don't know correlation structure. The way SIMM is
689 // currently, we could just sum over the InflationVol numbers within qualifier and use this.
690 for (auto itOuter = pInfQualifier.begin(); itOuter != pInfQualifier.end(); ++itOuter) {
691 // Risk weight i.e. $RW_k$ from SIMM docs
692 Real rwOuter = simmConfiguration_->weight(RiskType::InflationVol, qualifier, itOuter->label1);
693 // Weighted sensitivity i.e. $WS_{k,i}$ from SIMM docs
694 Real wsOuter = rwOuter * itOuter->amountResultCcy * concentrationRisk[qualifier];
695 // Update weighted sensitivity sum
696 sumWeightedSensis[qualifier] += wsOuter;
697 // Add diagonal element to vega margin
698 vegaMargin[qualifier] += wsOuter * wsOuter;
699 // Add the cross elements to the vega margin
700 // Firstly, against all IRVol components
701 for (auto itInner = pIrQualifier.begin(); itInner != pIrQualifier.end(); ++itInner) {
702 // Correlation i.e. $\rho_{k,l}$ from SIMM docs
703 Real corr = simmConfiguration_->correlation(RiskType::InflationVol, qualifier, itOuter->label1, "",
704 RiskType::IRVol, qualifier, itInner->label1, "");
705 // Add cross element to vega margin
706 Real rwInner = simmConfiguration_->weight(RiskType::IRVol, qualifier, itInner->label1);
707 Real wsInner = rwInner * itInner->amountResultCcy * concentrationRisk[qualifier];
708 vegaMargin[qualifier] += 2 * corr * wsOuter * wsInner;
709 }
710 // Secondly, against all previous InflationVol components
711 for (auto itInner = pInfQualifier.begin(); itInner != itOuter; ++itInner) {
712 // Correlation i.e. $\rho_{k,l}$ from SIMM docs
713 Real corr = simmConfiguration_->correlation(RiskType::InflationVol, qualifier, itOuter->label1, "",
714 RiskType::InflationVol, qualifier, itInner->label1, "");
715 // Add cross element to vega margin
716 Real rwInner = simmConfiguration_->weight(RiskType::InflationVol, qualifier, itInner->label1);
717 Real wsInner = rwInner * itInner->amountResultCcy * concentrationRisk[qualifier];
718 vegaMargin[qualifier] += 2 * corr * wsOuter * wsInner;
719 }
720 }
721
722 // Finally have the value of $K_b$
723 vegaMargin[qualifier] = sqrt(max(vegaMargin[qualifier], 0.0));
724 }
725
726 // Now calculate final vega margin by aggregating across currencies
727 Real margin = 0.0;
728 for (auto itOuter = qualifiers.begin(); itOuter != qualifiers.end(); ++itOuter) {
729 // Diagonal term
730 margin += vegaMargin.at(*itOuter) * vegaMargin.at(*itOuter);
731 // Cross terms
732 Real sOuter = max(min(sumWeightedSensis.at(*itOuter), vegaMargin.at(*itOuter)), -vegaMargin.at(*itOuter));
733 for (auto itInner = qualifiers.begin(); itInner != itOuter; ++itInner) {
734 Real sInner = max(min(sumWeightedSensis.at(*itInner), vegaMargin.at(*itInner)), -vegaMargin.at(*itInner));
735 Real g = min(concentrationRisk.at(*itOuter), concentrationRisk.at(*itInner)) /
736 max(concentrationRisk.at(*itOuter), concentrationRisk.at(*itInner));
737 Real corr = simmConfiguration_->correlation(RiskType::IRVol, *itOuter, "", "", RiskType::IRVol, *itInner,
738 "", "", calcCcy);
739 margin += 2.0 * sOuter * sInner * corr * g;
740 }
741 }
742 margin = sqrt(max(margin, 0.0));
743
744 for (const auto& m : vegaMargin)
745 bucketMargins[m.first] = m.second;
746 bucketMargins["All"] = margin;
747
748 return make_pair(bucketMargins, true);
749}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ irCurvatureMargin()

pair< map< string, Real >, bool > irCurvatureMargin ( const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const SimmSide side,
const ore::analytics::Crif crif 
) const
private

Calculate the Interest Rate curvature margin component for the given portfolio and product class.

Definition at line 751 of file simmcalculator.cpp.

753 {
754
755 // "Bucket" here refers to exposures under the CRIF qualifiers
756 map<string, Real> bucketMargins;
757
758 // Multiplier for sensitivities, -1 if SIMM side is Post
759 Real multiplier = side == SimmSide::Call ? 1.0 : -1.0;
760
761 // Find the set of qualifiers, i.e. currencies, in the Simm sensitivities
762 set<string> qualifiers = getQualifiers(crif, nettingSetDetails, pc, {RiskType::IRVol, RiskType::InflationVol});
763
764 // If there are no qualifiers, return early and set bool to false to indicate margin does not apply
765 if (qualifiers.empty()) {
766 bucketMargins["All"] = 0.0;
767 return make_pair(bucketMargins, false);
768 }
769
770 // The curvature margin for each currency i.e. $K_b$ from SIMM docs
771 map<string, Real> curvatureMargin;
772 // The sum of the weighted sensitivities for each currency i.e. $\sum_{k}^K CVR_{b,k}$ from SIMM docs
773 map<string, Real> sumWeightedSensis;
774 // The sum of all weighted sensitivities across currencies and risk factors
775 Real sumWs = 0.0;
776 // The sum of the absolute value of weighted sensitivities across currencies and risk factors
777 Real sumAbsWs = 0.0;
778
779 // Loop over the qualifiers i.e. currencies
780 for (const auto& qualifier : qualifiers) {
781 // Pair of iterators to start and end of IRVol sensitivities with current qualifier
782 auto pIrQualifier = crif.filterByQualifier(nettingSetDetails, pc, RiskType::IRVol, qualifier);
783
784 // Pair of iterators to start and end of InflationVol sensitivities with current qualifier
785 auto pInfQualifier =
786 crif.filterByQualifier(nettingSetDetails, pc, RiskType::InflationVol, qualifier);
787
788 // Calculate the margin piece for this qualifier i.e. $K_b$ from SIMM docs
789 // Start with IRVol vs. IRVol components
790 for (auto itOuter = pIrQualifier.begin(); itOuter != pIrQualifier.end(); ++itOuter) {
791 // Curvature weight i.e. $SF(t_{kj})$ from SIMM docs
792 Real sfOuter = simmConfiguration_->curvatureWeight(RiskType::IRVol, itOuter->label1);
793 // Curvature sensitivity i.e. $CVR_{ik}$ from SIMM docs
794 Real wsOuter = sfOuter * (itOuter->amountResultCcy * multiplier);
795 // Update weighted sensitivity sums
796 sumWeightedSensis[qualifier] += wsOuter;
797 sumWs += wsOuter;
798 sumAbsWs += std::abs(wsOuter);
799 // Add diagonal element to curvature margin
800 curvatureMargin[qualifier] += wsOuter * wsOuter;
801 // Add the cross elements to the curvature margin
802 for (auto itInner = pIrQualifier.begin(); itInner != itOuter; ++itInner) {
803 // Label1 level correlation i.e. $\rho_{k,l}$ from SIMM docs
804 Real corr = simmConfiguration_->correlation(RiskType::IRVol, qualifier, itOuter->label1, "",
805 RiskType::IRVol, qualifier, itInner->label1, "");
806 // Add cross element to curvature margin
807 Real sfInner = simmConfiguration_->curvatureWeight(RiskType::IRVol, itInner->label1);
808 Real wsInner = sfInner * (itInner->amountResultCcy * multiplier);
809 curvatureMargin[qualifier] += 2 * corr * corr * wsOuter * wsInner;
810 }
811 }
812
813 // Now deal with inflation component
814 const string simmVersion = simmConfiguration_->version();
815 SimmVersion thresholdVersion = SimmVersion::V1_0;
816 if (simmConfiguration_->isSimmConfigCalibration() || parseSimmVersion(simmVersion) > thresholdVersion) {
817 // Weighted sensitivity i.e. $WS_{k,i}$ from SIMM docs
818 Real infWs = 0.0;
819 for (auto infIt = pInfQualifier.begin(); infIt != pInfQualifier.end(); ++infIt) {
820 // Curvature weight i.e. $SF(t_{kj})$ from SIMM docs
821 Real infSf = simmConfiguration_->curvatureWeight(RiskType::InflationVol, infIt->label1);
822 infWs += infSf * (infIt->amountResultCcy * multiplier);
823 }
824 // Update weighted sensitivity sums
825 sumWeightedSensis[qualifier] += infWs;
826 sumWs += infWs;
827 sumAbsWs += std::abs(infWs);
828
829 // Add diagonal element to curvature margin - there is only one element for inflationVol
830 curvatureMargin[qualifier] += infWs * infWs;
831
832 // Add the cross elements to the curvature margin against IRVol components.
833 // There are no cross elements against InflationVol since we only have one element.
834 for (auto irIt = pIrQualifier.begin(); irIt != pIrQualifier.end(); ++irIt) {
835 // Correlation i.e. $\rho_{k,l}$ from SIMM docs
836 Real corr = simmConfiguration_->correlation(RiskType::InflationVol, qualifier, "", "", RiskType::IRVol,
837 qualifier, irIt->label1, "");
838 // Add cross element to curvature margin
839 Real irSf = simmConfiguration_->curvatureWeight(RiskType::IRVol, irIt->label1);
840 Real irWs = irSf * (irIt->amountResultCcy * multiplier);
841 curvatureMargin[qualifier] += 2 * corr * corr * infWs * irWs;
842 }
843 }
844
845 // Finally have the value of $K_b$
846 curvatureMargin[qualifier] = sqrt(max(curvatureMargin[qualifier], 0.0));
847 }
848
849 // If sum of absolute value of all individual curvature risks is zero, we can return 0.0
850 if (close_enough(sumAbsWs, 0.0)) {
851 bucketMargins["All"] = 0.0;
852 return make_pair(bucketMargins, true);
853 }
854
855 // Now calculate final curvature margin by aggregating across currencies
856 Real theta = min(sumWs / sumAbsWs, 0.0);
857
858 Real margin = 0.0;
859 for (auto itOuter = qualifiers.begin(); itOuter != qualifiers.end(); ++itOuter) {
860 // Diagonal term
861 margin += curvatureMargin.at(*itOuter) * curvatureMargin.at(*itOuter);
862 // Cross terms
863 Real sOuter =
864 max(min(sumWeightedSensis.at(*itOuter), curvatureMargin.at(*itOuter)), -curvatureMargin.at(*itOuter));
865 for (auto itInner = qualifiers.begin(); itInner != itOuter; ++itInner) {
866 Real sInner =
867 max(min(sumWeightedSensis.at(*itInner), curvatureMargin.at(*itInner)), -curvatureMargin.at(*itInner));
868 Real corr =
869 simmConfiguration_->correlation(RiskType::IRVol, *itOuter, "", "", RiskType::IRVol, *itInner, "", "");
870 margin += 2.0 * sOuter * sInner * corr * corr;
871 }
872 }
873 margin = sumWs + lambda(theta) * sqrt(max(margin, 0.0));
874
875 for (const auto& m : curvatureMargin)
876 bucketMargins[m.first] = m.second;
877
878 Real scaling = simmConfiguration_->curvatureMarginScaling();
879 Real totalCurvatureMargin = scaling * max(margin, 0.0);
880 // TODO: Review, should we return the pre-scaled value instead?
881 bucketMargins["All"] = totalCurvatureMargin;
882
883 return make_pair(bucketMargins, true);
884}
std::vector< CrifRecord > filterByQualifier(const NettingSetDetails &nsd, const CrifRecord::ProductClass pc, const CrifRecord::RiskType rt, const std::string &qualifier) const
Definition: crif.cpp:247
QuantLib::Real lambda(QuantLib::Real theta) const
Give the used in the curvature margin calculation.
SimmVersion
Ordered SIMM versions.
Definition: utilities.hpp:43
SimmVersion parseSimmVersion(const string &version)
Definition: utilities.cpp:162
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ margin()

pair< map< string, Real >, bool > margin ( const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const CrifRecord::RiskType rt,
const ore::analytics::Crif netRecords,
const SimmSide side 
) const
private

Calculate the (delta or vega) margin component for the given portfolio, product class and risk type Used to calculate delta or vega or base correlation margin for all risk types except IR, IRVol (and by association, Inflation, XccyBasis and InflationVol)

Definition at line 886 of file simmcalculator.cpp.

887 {
888
889 const string& calcCcy = side == SimmSide::Call ? calculationCcyCall_ : calculationCcyPost_;
890
891 // "Bucket" here refers to exposures under the CRIF qualifiers for FX (and IR) risk class, and CRIF buckets for
892 // every other risk class.
893 // For FX Delta margin, this refers to WS_k in Section B. "Structure of the methodology", 8.(b).
894 // For FX Vega margin, this refers to VR_k in Section B., 10.(d).
895 // For other risk type, the bucket margin is K_b in the corresponding subsections.
896 map<string, Real> bucketMargins;
897
898 bool riskClassIsFX = rt == RiskType::FX || rt == RiskType::FXVol;
899
900 // precomputed
901 map<std::pair<std::string,std::string>, std::vector<CrifRecord>> crifByQualifierAndBucket;
902 map<std::string, std::vector<CrifRecord>> crifByBucket;
903
904 // Find the set of buckets and associated qualifiers for the netting set details, product class and risk type
905 map<string, set<string>> buckets;
906 for(const auto& it : crif.filterBy(nettingSetDetails, pc, rt)) {
907 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);
910 }
911
912 // If there are no buckets, return early and set bool to false to indicate margin does not apply
913 if (buckets.empty()) {
914 bucketMargins["All"] = 0.0;
915 return make_pair(bucketMargins, false);
916 }
917
918 // The margin for each bucket i.e. $K_b$ from SIMM docs
919 map<string, Real> bucketMargin;
920 // The sum of the weighted sensitivities for each bucket i.e. $\sum_{k=1}^{K} WS_{k}$ from SIMM docs
921 map<string, Real> sumWeightedSensis;
922 // The historical volatility ratio for the risk type - will be 1.0 if not applicable
923 Real hvr = simmConfiguration_->historicalVolatilityRatio(rt);
924
925 // Loop over the buckets
926 for (const auto& kv : buckets) {
927 string bucket = kv.first;
928
929 // Initialise sumWeightedSensis here to ensure it is not empty in the later calculations
930 sumWeightedSensis[bucket] = 0.0;
931
932 // Get the concentration risk for each qualifier in current bucket i.e. $CR_k$ from SIMM docs
933 map<string, Real> concentrationRisk;
934
935 for (const auto& qualifier : kv.second) {
936
937 // Do not include Risk_FX components in the calculation currency in the SIMM calculation
938 if (rt == RiskType::FX && qualifier == calcCcy) {
939 if (!quiet_) {
940 DLOG("Not calculating concentration risk for qualifier "
941 << qualifier << " of risk type " << rt
942 << " since the qualifier equals the SIMM calculation currency " << calcCcy);
943 }
944 continue;
945 }
946
947 // Pair of iterators to start and end of sensitivities with current qualifier
948 auto pQualifier = crifByQualifierAndBucket[std::make_pair(qualifier,bucket)];
949
950 // One pass to get the concentration risk for this qualifier
951 for (auto it = pQualifier.begin(); it != pQualifier.end(); ++it) {
952 // Get the sigma value if applicable - returns 1.0 if not applicable
953 Real sigma = simmConfiguration_->sigma(rt, it->qualifier, it->label1, calcCcy);
954 concentrationRisk[qualifier] += it->amountResultCcy * sigma * hvr;
955 }
956 // Divide by the concentration risk threshold
957 Real concThreshold = simmConfiguration_->concentrationThreshold(rt, qualifier);
958 if (resultCcy_ != "USD")
959 concThreshold *= market_->fxRate("USD" + resultCcy_)->value();
960 concentrationRisk[qualifier] /= concThreshold;
961 // Final concentration risk amount
962 concentrationRisk[qualifier] = max(1.0, sqrt(std::abs(concentrationRisk[qualifier])));
963 }
964
965
966 // Calculate the margin component for the current bucket
967 // Pair of iterators to start and end of sensitivities within current bucket
968 auto pBucket = crifByBucket[bucket];
969 for (auto itOuter = pBucket.begin(); itOuter != pBucket.end(); ++itOuter) {
970 // Do not include Risk_FX components in the calculation currency in the SIMM calculation
971 if (rt == RiskType::FX && itOuter->qualifier == calcCcy) {
972 if (!quiet_) {
973 DLOG("Skipping qualifier " << itOuter->qualifier << " of risk type " << rt
974 << " since the qualifier equals the SIMM calculation currency "
975 << calcCcy);
976 }
977 continue;
978 }
979 // Risk weight i.e. $RW_k$ from SIMM docs
980 Real rwOuter = simmConfiguration_->weight(rt, itOuter->qualifier, itOuter->label1, calcCcy);
981 // Get the sigma value if applicable - returns 1.0 if not applicable
982 Real sigmaOuter = simmConfiguration_->sigma(rt, itOuter->qualifier, itOuter->label1, calcCcy);
983 // Weighted sensitivity i.e. $WS_{k}$ from SIMM docs
984 Real wsOuter =
985 rwOuter * (itOuter->amountResultCcy * sigmaOuter * hvr) * concentrationRisk[itOuter->qualifier];
986 // Get concentration risk for outer qualifier
987 Real outerConcentrationRisk = concentrationRisk.at(itOuter->qualifier);
988 // Update weighted sensitivity sum
989 sumWeightedSensis[bucket] += wsOuter;
990 // Add diagonal element to bucket margin
991 bucketMargin[bucket] += wsOuter * wsOuter;
992 // Add the cross elements to the bucket margin
993 for (auto itInner = pBucket.begin(); itInner != itOuter; ++itInner) {
994 // Do not include Risk_FX components in the calculation currency in the SIMM calculation
995 if (rt == RiskType::FX && itInner->qualifier == calcCcy) {
996 if (!quiet_) {
997 DLOG("Skipping qualifier " << itInner->qualifier << " of risk type " << rt
998 << " since the qualifier equals the SIMM calculation currency "
999 << calcCcy);
1000 }
1001 continue;
1002 }
1003 // Correlation, $\rho_{k,l}$ in the SIMM docs
1004 Real corr =
1005 simmConfiguration_->correlation(rt, itOuter->qualifier, itOuter->label1, itOuter->label2, rt,
1006 itInner->qualifier, itInner->label1, itInner->label2, calcCcy);
1007 // $f_{k,l}$ from the SIMM docs
1008 Real f = min(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier)) /
1009 max(outerConcentrationRisk, concentrationRisk.at(itInner->qualifier));
1010 // Add cross element to delta margin
1011 Real sigmaInner = simmConfiguration_->sigma(rt, itInner->qualifier, itInner->label1, calcCcy);
1012 Real rwInner = simmConfiguration_->weight(rt, itInner->qualifier, itInner->label1, calcCcy);
1013 Real wsInner =
1014 rwInner * (itInner->amountResultCcy * sigmaInner * hvr) * concentrationRisk[itInner->qualifier];
1015 bucketMargin[bucket] += 2 * corr * f * wsOuter * wsInner;
1016 }
1017 // For FX risk class, results are broken down by qualifier, i.e. currency, instead of bucket, which is not used for Risk_FX
1018 if (riskClassIsFX)
1019 bucketMargins[itOuter->qualifier] += wsOuter;
1020 }
1021
1022 // Finally have the value of $K_b$
1023 bucketMargin[bucket] = sqrt(max(bucketMargin[bucket], 0.0));
1024 }
1025
1026 // If there is a "Residual" bucket entry store it separately
1027 // This is $K_{residual}$ from SIMM docs
1028 Real residualMargin = 0.0;
1029 if (bucketMargin.count("Residual") > 0) {
1030 residualMargin = bucketMargin.at("Residual");
1031 bucketMargin.erase("Residual");
1032 }
1033
1034 // Now calculate final margin by aggregating across non-residual buckets
1035 Real margin = 0.0;
1036 for (auto itOuter = bucketMargin.begin(); itOuter != bucketMargin.end(); ++itOuter) {
1037 string outerBucket = itOuter->first;
1038 // Diagonal term, $K_b^2$ from SIMM docs
1039 margin += itOuter->second * itOuter->second;
1040 // Cross terms
1041 // $S_b$ from SIMM docs
1042 Real sOuter = max(min(sumWeightedSensis.at(outerBucket), itOuter->second), -itOuter->second);
1043 for (auto itInner = bucketMargin.begin(); itInner != itOuter; ++itInner) {
1044 string innerBucket = itInner->first;
1045 // $S_c$ from SIMM docs
1046 Real sInner = max(min(sumWeightedSensis.at(innerBucket), itInner->second), -itInner->second);
1047 // $\gamma_{b,c}$ from SIMM docs
1048 // Interface to SimmConfiguration is on qualifiers => take any qualifier from each
1049 // of the respective (different) buckets to get the inter-bucket correlation
1050 string innerQualifier = *buckets.at(innerBucket).begin();
1051 string outerQualifier = *buckets.at(outerBucket).begin();
1052 Real corr = simmConfiguration_->correlation(rt, outerQualifier, "", "", rt, innerQualifier, "", "", calcCcy);
1053 margin += 2.0 * sOuter * sInner * corr;
1054 }
1055 }
1056 margin = sqrt(max(margin, 0.0));
1057
1058 // Now add the residual component back in
1059 margin += residualMargin;
1060 if (!close_enough(residualMargin, 0.0))
1061 bucketMargins["Residual"] = residualMargin;
1062
1063 // For non-FX risk class, results are broken down by buckets
1064 if (!riskClassIsFX)
1065 for (const auto& m : bucketMargin)
1066 bucketMargins[m.first] = m.second;
1067 else
1068 for (auto& m : bucketMargins)
1069 m.second = std::abs(m.second);
1070
1071 bucketMargins["All"] = margin;
1072 return make_pair(bucketMargins, true);
1073}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ curvatureMargin()

pair< map< string, Real >, bool > curvatureMargin ( const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const CrifRecord::RiskType rt,
const SimmSide side,
const ore::analytics::Crif netRecords,
bool  rfLabels = true 
) const
private

Calculate the curvature margin component for the given portfolio, product class and risk type Used to calculate curvature margin for all risk types except IR

Definition at line 1076 of file simmcalculator.cpp.

1077 {
1078
1079 const string& calcCcy = side == SimmSide::Call ? calculationCcyCall_ : calculationCcyPost_;
1080
1081 // "Bucket" here refers to exposures under the CRIF qualifiers for FX (and IR) risk class, and CRIF buckets for
1082 // every other risk class
1083 // For FX Curvature marrgin, this refers to CVR_{b,k} in Section B. "Structure of the methodology", 11.(c).
1084 // For other risk types, the bucket margin is K_b in the corresponding subsection.
1085 map<string, Real> bucketMargins;
1086
1087 bool riskClassIsFX = rt == RiskType::FX || rt == RiskType::FXVol;
1088
1089 // Multiplier for sensitivities, -1 if SIMM side is Post
1090 Real multiplier = side == SimmSide::Call ? 1.0 : -1.0;
1091
1092 // Find the set of buckets and associated qualifiers for the netting set details, product class and risk type
1093 map<string, set<string>> buckets;
1094 for(const auto& it : crif.filterBy(nettingSetDetails, pc, rt)) {
1095 buckets[it.bucket].insert(it.qualifier);
1096 }
1097
1098 // If there are no buckets, return early and set bool to false to indicate margin does not apply
1099 if (buckets.empty()) {
1100 bucketMargins["All"] = 0.0;
1101 return make_pair(bucketMargins, false);
1102 }
1103
1104 // The curvature margin for each bucket i.e. $K_b$ from SIMM docs
1105 map<string, Real> curvatureMargin;
1106 // The sum of the weighted (and absolute weighted) sensitivities for each bucket
1107 // i.e. $\sum_{k}^K CVR_{b,k}$ from SIMM docs
1108 map<string, Real> sumWeightedSensis;
1109 map<string, map<string, Real>> sumAbsTemp;
1110 map<string, Real> sumAbsWeightedSensis;
1111
1112 // Loop over the buckets
1113 for (const auto& kv : buckets) {
1114 string bucket = kv.first;
1115 sumAbsTemp[bucket] = {};
1116
1117 // Calculate the margin component for the current bucket
1118 // Pair of iterators to start and end of sensitivities within current bucket
1119 auto pBucket = crif.filterByBucket(nettingSetDetails, pc, rt, bucket);
1120 for (auto itOuter = pBucket.begin(); itOuter != pBucket.end(); ++itOuter) {
1121 // Curvature weight i.e. $SF(t_{kj})$ from SIMM docs
1122 Real sfOuter = simmConfiguration_->curvatureWeight(rt, itOuter->label1);
1123 // Get the sigma value if applicable - returns 1.0 if not applicable
1124 Real sigmaOuter = simmConfiguration_->sigma(rt, itOuter->qualifier, itOuter->label1, calcCcy);
1125 // Weighted curvature i.e. $CVR_{ik}$ from SIMM docs
1126 // WARNING: The order of multiplication here is important because unit tests fail if for
1127 // example you use sfOuter * (itOuter->amountResultCcy * multiplier) * sigmaOuter;
1128 Real wsOuter = sfOuter * ((itOuter->amountResultCcy * multiplier) * sigmaOuter);
1129 // for ISDA SIMM 2.2 or higher, this $CVR_{ik}$ for EQ bucket 12 is zero
1130 const string simmVersion = simmConfiguration_->version();
1131 SimmVersion thresholdVersion = SimmVersion::V2_2;
1132 if ((simmConfiguration_->isSimmConfigCalibration() || parseSimmVersion(simmVersion) >= thresholdVersion) &&
1133 bucket == "12" && rt == RiskType::EquityVol) {
1134 wsOuter = 0.0;
1135 }
1136 // Update weighted sensitivity sum
1137 sumWeightedSensis[bucket] += wsOuter;
1138 sumAbsTemp[bucket][itOuter->qualifier] += rfLabels ? std::abs(wsOuter) : wsOuter;
1139 // Add diagonal element to curvature margin
1140 curvatureMargin[bucket] += wsOuter * wsOuter;
1141 // Add the cross elements to the curvature margin
1142 for (auto itInner = pBucket.begin(); itInner != itOuter; ++itInner) {
1143 // Correlation, $\rho_{k,l}$ in the SIMM docs
1144 Real corr = simmConfiguration_->correlation(rt, itOuter->qualifier, itOuter->label1, itOuter->label2,
1145 rt, itInner->qualifier, itInner->label1, itInner->label2,
1146 calcCcy);
1147 // Add cross element to delta margin
1148 Real sfInner = simmConfiguration_->curvatureWeight(rt, itInner->label1);
1149 Real sigmaInner = simmConfiguration_->sigma(rt, itInner->qualifier, itInner->label1, calcCcy);
1150 Real wsInner = sfInner * ((itInner->amountResultCcy * multiplier) * sigmaInner);
1151 curvatureMargin[bucket] += 2 * corr * corr * wsOuter * wsInner;
1152 }
1153 // For FX risk class, results are broken down by qualifier, i.e. currency, instead of bucket, which is not
1154 // used for Risk_FX
1155 if (riskClassIsFX)
1156 bucketMargins[itOuter->qualifier] += wsOuter;
1157 }
1158
1159 // Finally have the value of $K_b$
1160 Real bucketCurvatureMargin = sqrt(max(curvatureMargin[bucket], 0.0));
1161 curvatureMargin[bucket] = bucketCurvatureMargin;
1162
1163 // Bucket level absolute sensitivity
1164 for (const auto& kv : sumAbsTemp[bucket]) {
1165 sumAbsWeightedSensis[bucket] += std::abs(kv.second);
1166 }
1167 }
1168
1169 // If there is a "Residual" bucket entry store it separately
1170 // This is $K_{residual}$ from SIMM docs
1171 Real residualMargin = 0.0;
1172 Real residualSum = 0.0;
1173 Real residualAbsSum = 0.0;
1174 if (curvatureMargin.count("Residual") > 0) {
1175 residualMargin = curvatureMargin.at("Residual");
1176 residualSum = sumWeightedSensis.at("Residual");
1177 residualAbsSum = sumAbsWeightedSensis.at("Residual");
1178 // Remove the entries for "Residual" bucket
1179 curvatureMargin.erase("Residual");
1180 sumWeightedSensis.erase("Residual");
1181 sumAbsWeightedSensis.erase("Residual");
1182 }
1183
1184 // Now calculate final margin
1185 Real margin = 0.0;
1186
1187 // First, aggregating across non-residual buckets
1188 auto acc = [](const Real p, const pair<const string, Real>& kv) { return p + kv.second; };
1189 Real sumSensis = accumulate(sumWeightedSensis.begin(), sumWeightedSensis.end(), 0.0, acc);
1190 Real sumAbsSensis = accumulate(sumAbsWeightedSensis.begin(), sumAbsWeightedSensis.end(), 0.0, acc);
1191
1192 if (!close_enough(sumAbsSensis, 0.0)) {
1193 Real theta = min(sumSensis / sumAbsSensis, 0.0);
1194 for (auto itOuter = curvatureMargin.begin(); itOuter != curvatureMargin.end(); ++itOuter) {
1195 string outerBucket = itOuter->first;
1196 // Diagonal term
1197 margin += itOuter->second * itOuter->second;
1198 // Cross terms
1199 // $S_b$ from SIMM docs
1200 Real sOuter = max(min(sumWeightedSensis.at(outerBucket), itOuter->second), -itOuter->second);
1201 for (auto itInner = curvatureMargin.begin(); itInner != itOuter; ++itInner) {
1202 string innerBucket = itInner->first;
1203 // $S_c$ from SIMM docs
1204 Real sInner = max(min(sumWeightedSensis.at(innerBucket), itInner->second), -itInner->second);
1205 // $\gamma_{b,c}$ from SIMM docs
1206 // Interface to SimmConfiguration is on qualifiers => take any qualifier from each
1207 // of the respective (different) buckets to get the inter-bucket correlation
1208 string innerQualifier = *buckets.at(innerBucket).begin();
1209 string outerQualifier = *buckets.at(outerBucket).begin();
1210 Real corr = simmConfiguration_->correlation(rt, outerQualifier, "", "", rt, innerQualifier, "", "", calcCcy);
1211 margin += 2.0 * sOuter * sInner * corr * corr;
1212 }
1213 }
1214 margin = max(sumSensis + lambda(theta) * sqrt(max(margin, 0.0)), 0.0);
1215 }
1216
1217 // Second, the residual bucket if necessary, and add "Residual" bucket back in to be added to the SIMM results
1218 if (!close_enough(residualAbsSum, 0.0)) {
1219 Real theta = min(residualSum / residualAbsSum, 0.0);
1220 curvatureMargin["Residual"] = max(residualSum + lambda(theta) * residualMargin, 0.0);
1221 margin += curvatureMargin["Residual"];
1222 }
1223
1224 // For non-FX risk class, results are broken down by buckets
1225 if (!riskClassIsFX)
1226 for (const auto& m : curvatureMargin)
1227 bucketMargins[m.first] = m.second;
1228 else
1229 for (auto& m : bucketMargins)
1230 m.second = std::abs(m.second);
1231
1232 bucketMargins["All"] = margin;
1233 return make_pair(bucketMargins, true);
1234}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ calcAddMargin()

void calcAddMargin ( const SimmSide side,
const ore::data::NettingSetDetails nsd,
const string &  regulation,
const ore::analytics::Crif netRecords 
)
private

Calculate the additional initial margin for the portfolio ID and regulation.

Definition at line 1236 of file simmcalculator.cpp.

1237 {
1238
1239 // Reference to SIMM results for this portfolio
1240 auto& results = simmResults_[side][nettingSetDetails][regulation];
1241
1242 const bool overwrite = false;
1243
1244 if (!quiet_) {
1245 DLOG("Calculating additional margin for portfolio [" << nettingSetDetails << "], regulation " << regulation
1246 << " and SIMM side " << side);
1247 }
1248
1249 // First, add scaled additional margin, using "ProductClassMultiplier"
1250 // risk type, for the portfolio
1251 auto pc = ProductClass::Empty;
1252 auto rt = RiskType::ProductClassMultiplier;
1253 auto pIt = crif.filterBy(nettingSetDetails, pc, rt);
1254
1255 for(const auto& it : pIt) {
1256 // Qualifier should be a product class string
1257 auto qpc = parseProductClass(it.qualifier);
1258 if (results.has(qpc, RiskClass::All, MarginType::All, "All")) {
1259 Real im = results.get(qpc, RiskClass::All, MarginType::All, "All");
1260 Real factor = it.amount;
1261 QL_REQUIRE(factor >= 0.0, "SIMM Calculator: Amount for risk type "
1262 << rt << " must be greater than or equal to 0 but we got " << factor);
1263 Real pcmMargin = (factor - 1.0) * im;
1264 add(nettingSetDetails, regulation, qpc, RiskClass::All, MarginType::AdditionalIM, "All", pcmMargin, side,
1265 overwrite);
1266
1267 // Add to aggregation at margin type level
1268 add(nettingSetDetails, regulation, qpc, RiskClass::All, MarginType::All, "All", pcmMargin, side, overwrite);
1269 // Add to aggregation at product class level
1270 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::AdditionalIM, "All", pcmMargin,
1271 side, overwrite);
1272 // Add to aggregation at portfolio level
1273 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::All, "All", pcmMargin, side,
1274 overwrite);
1275 CrifRecord spRecord = it;
1276 if (side == SimmSide::Call)
1277 spRecord.collectRegulations = regulation;
1278 else
1279 spRecord.postRegulations = regulation;
1280 simmParameters_.addRecord(spRecord);
1281 }
1282 }
1283
1284 // Second, add fixed amounts IM, using "AddOnFixedAmount" risk type, for the portfolio
1285 pIt = crif.filterBy(nettingSetDetails, pc, RiskType::AddOnFixedAmount);
1286 for(const auto& it : pIt){
1287 Real fixedMargin = it.amountResultCcy;
1288 add(nettingSetDetails, regulation, ProductClass::AddOnFixedAmount, RiskClass::All, MarginType::AdditionalIM,
1289 "All", fixedMargin, side, overwrite);
1290
1291 // Add to aggregation at margin type level
1292 add(nettingSetDetails, regulation, ProductClass::AddOnFixedAmount, RiskClass::All, MarginType::All, "All",
1293 fixedMargin,
1294 side, overwrite);
1295 // Add to aggregation at product class level
1296 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::AdditionalIM, "All",
1297 fixedMargin, side, overwrite);
1298 // Add to aggregation at portfolio level
1299 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::All, "All", fixedMargin, side,
1300 overwrite);
1301 CrifRecord spRecord = it;
1302 if (side == SimmSide::Call)
1303 spRecord.collectRegulations = regulation;
1304 else
1305 spRecord.postRegulations = regulation;
1306 simmParameters_.addRecord(spRecord);
1307 }
1308
1309 // Third, add percentage of notional amounts IM, using "AddOnNotionalFactor"
1310 // and "Notional" risk types, for the portfolio.
1311 pIt = crif.filterBy(nettingSetDetails, pc, RiskType::AddOnNotionalFactor);
1312 for(const auto& it : pIt){
1313 // We should have a single corresponding CrifRecord with risk type
1314 // "Notional" and the same qualifier. Search for it.
1315 auto pQualifierIt = crif.filterByQualifier(nettingSetDetails, pc, RiskType::Notional, it.qualifier);
1316 const auto count = pQualifierIt.size();
1317 QL_REQUIRE(count < 2, "Expected either 0 or 1 elements for risk type "
1318 << RiskType::Notional << " and qualifier " << it.qualifier
1319 << " but got " << count);
1320
1321 // If we have found a corresponding notional, update the additional margin
1322 if (count == 1) {
1323 Real notional = pQualifierIt.front().amountResultCcy;
1324 Real factor = it.amount;
1325 Real notionalFactorMargin = notional * factor / 100.0;
1326
1327 add(nettingSetDetails, regulation, ProductClass::AddOnNotionalFactor, RiskClass::All,
1328 MarginType::AdditionalIM, "All", notionalFactorMargin, side, overwrite);
1329
1330 // Add to aggregation at margin type level
1331 add(nettingSetDetails, regulation, ProductClass::AddOnNotionalFactor, RiskClass::All, MarginType::All,
1332 "All",
1333 notionalFactorMargin, side, overwrite);
1334 // Add to aggregation at product class level
1335 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::AdditionalIM, "All",
1336 notionalFactorMargin, side, overwrite);
1337 // Add to aggregation at portfolio level
1338 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::All, "All",
1339 notionalFactorMargin,
1340 side, overwrite);
1341 CrifRecord spRecord = it;
1342 if (side == SimmSide::Call)
1343 spRecord.collectRegulations = regulation;
1344 else
1345 spRecord.postRegulations = regulation;
1346 simmParameters_.addRecord(spRecord);
1347 }
1348 }
1349}
CrifRecord::ProductClass parseProductClass(const string &pc)
Definition: crifrecord.cpp:127
std::size_t count
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ populateResults()

void populateResults ( const SimmSide side,
const ore::data::NettingSetDetails nsd,
const string &  regulation 
)
private

Populate the results structure with the higher level results after the IMs have been calculated at the (product class, risk class, margin type) level for the given regulation under the given portfolio

Definition at line 1351 of file simmcalculator.cpp.

1352 {
1353
1354 if (!quiet_) {
1355 LOG("SimmCalculator: Populating higher level results")
1356 }
1357
1358 // Sets of classes (excluding 'All')
1359 auto pcs = simmConfiguration_->productClasses(false);
1360 auto rcs = simmConfiguration_->riskClasses(false);
1361 auto mts = simmConfiguration_->marginTypes(false);
1362
1363 // Populate netting set level results for each portfolio
1364
1365 // Reference to SIMM results for this portfolio
1366 auto& results = simmResults_[side][nettingSetDetails][regulation];
1367
1368 // Fill in the margin within each (product class, risk class) combination
1369 for (const auto& pc : pcs) {
1370 for (const auto& rc : rcs) {
1371 // Margin for a risk class is just sum over margin for each margin type
1372 // within that risk class
1373 Real riskClassMargin = 0.0;
1374 bool hasRiskClass = false;
1375 for (const auto& mt : mts) {
1376 if (results.has(pc, rc, mt, "All")) {
1377 riskClassMargin += results.get(pc, rc, mt, "All");
1378 if (!hasRiskClass)
1379 hasRiskClass = true;
1380 }
1381 }
1382
1383 // Add the margin to the results if it was calculated
1384 if (hasRiskClass) {
1385 add(nettingSetDetails, regulation, pc, rc, MarginType::All, "All", riskClassMargin, side);
1386 }
1387 }
1388 }
1389
1390 // Fill in the margin within each product class by aggregating across risk classes
1391 for (const auto& pc : pcs) {
1392 Real productClassMargin = 0.0;
1393 bool hasProductClass = false;
1394
1395 // IM within product class across risk classes requires correlation
1396 // o suffix => outer and i suffix => inner here
1397 for (auto ito = rcs.begin(); ito != rcs.end(); ++ito) {
1398 // Skip to next if no results for current risk class
1399 if (!results.has(pc, *ito, MarginType::All, "All"))
1400 continue;
1401 // If we get to here, we have the current product class
1402 if (!hasProductClass)
1403 hasProductClass = true;
1404
1405 Real imo = results.get(pc, *ito, MarginType::All, "All");
1406 // Diagonal term
1407 productClassMargin += imo * imo;
1408
1409 // Now, cross terms
1410 for (auto iti = rcs.begin(); iti != ito; ++iti) {
1411 if (!results.has(pc, *iti, MarginType::All, "All"))
1412 continue;
1413 Real imi = results.get(pc, *iti, MarginType::All, "All");
1414 // Get the correlation between risk classes
1415 Real corr = simmConfiguration_->correlationRiskClasses(*ito, *iti);
1416 productClassMargin += 2.0 * corr * imo * imi;
1417 }
1418 }
1419
1420 // Add the margin to the results if it was calculated
1421 if (hasProductClass) {
1422 productClassMargin = sqrt(max(productClassMargin, 0.0));
1423 add(nettingSetDetails, regulation, pc, RiskClass::All, MarginType::All, "All", productClassMargin, side);
1424 }
1425 }
1426
1427 // Overall initial margin for the portfolio is the sum of the initial margin in
1428 // each of the product classes. Could have done it in the last loop but cleaner here.
1429 Real im = 0.0;
1430 for (const auto& pc : pcs) {
1431 if (results.has(pc, RiskClass::All, MarginType::All, "All")) {
1432 im += results.get(pc, RiskClass::All, MarginType::All, "All");
1433 }
1434 }
1435 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, MarginType::All, "All", im, side);
1436
1437 // Combinations outside of the natural SIMM hierarchy
1438
1439 // Across risk class, for each product class and margin type
1440 for (const auto& pc : pcs) {
1441 for (const auto& mt : mts) {
1442 Real margin = 0.0;
1443 bool hasPcMt = false;
1444
1445 // IM within product class and margin type across risk classes
1446 // requires correlation. o suffix => outer and i suffix => inner here
1447 for (auto ito = rcs.begin(); ito != rcs.end(); ++ito) {
1448 // Skip to next if no results for current risk class
1449 if (!results.has(pc, *ito, mt, "All"))
1450 continue;
1451 // If we get to here, we have the current product class & margin type
1452 if (!hasPcMt)
1453 hasPcMt = true;
1454
1455 Real imo = results.get(pc, *ito, mt, "All");
1456 // Diagonal term
1457 margin += imo * imo;
1458
1459 // Now, cross terms
1460 for (auto iti = rcs.begin(); iti != ito; ++iti) {
1461 if (!results.has(pc, *iti, mt, "All"))
1462 continue;
1463 Real imi = results.get(pc, *iti, mt, "All");
1464 // Get the correlation between risk classes
1465 Real corr = simmConfiguration_->correlationRiskClasses(*ito, *iti);
1466 margin += 2.0 * corr * imo * imi;
1467 }
1468 }
1469
1470 // Add the margin to the results if it was calculated
1471 if (hasPcMt) {
1472 margin = sqrt(max(margin, 0.0));
1473 add(nettingSetDetails, regulation, pc, RiskClass::All, mt, "All", margin, side);
1474 }
1475 }
1476 }
1477
1478 // Across product class, for each risk class and margin type
1479 for (const auto& rc : rcs) {
1480 for (const auto& mt : mts) {
1481 Real margin = 0.0;
1482 bool hasRcMt = false;
1483
1484 // Can just sum across product class
1485 for (const auto& pc : pcs) {
1486 // Skip to next if no results for current product class
1487 if (!results.has(pc, rc, mt, "All"))
1488 continue;
1489 // If we get to here, we have the current risk class & margin type
1490 if (!hasRcMt)
1491 hasRcMt = true;
1492
1493 margin += results.get(pc, rc, mt, "All");
1494 }
1495
1496 // Add the margin to the results if it was calculated
1497 if (hasRcMt) {
1498 add(nettingSetDetails, regulation, ProductClass::All, rc, mt, "All", margin, side);
1499 }
1500 }
1501 }
1502
1503 // Across product class and margin type for each risk class
1504 // Have already computed MarginType::All results above so just need
1505 // to sum over product class for each risk class here
1506 for (const auto& rc : rcs) {
1507 Real margin = 0.0;
1508 bool hasRc = false;
1509
1510 for (const auto& pc : pcs) {
1511 // Skip to next if no results for current product class
1512 if (!results.has(pc, rc, MarginType::All, "All"))
1513 continue;
1514 // If we get to here, we have the current risk class
1515 if (!hasRc)
1516 hasRc = true;
1517
1518 margin += results.get(pc, rc, MarginType::All, "All");
1519 }
1520
1521 // Add the margin to the results if it was calculated
1522 if (hasRc) {
1523 add(nettingSetDetails, regulation, ProductClass::All, rc, MarginType::All, "All", margin, side);
1524 }
1525 }
1526
1527 // Across product class and risk class for each margin type
1528 // Have already computed RiskClass::All results above so just need
1529 // to sum over product class for each margin type here
1530 for (const auto& mt : mts) {
1531 Real margin = 0.0;
1532 bool hasMt = false;
1533
1534 for (const auto& pc : pcs) {
1535 // Skip to next if no results for current product class
1536 if (!results.has(pc, RiskClass::All, mt, "All"))
1537 continue;
1538 // If we get to here, we have the current risk class
1539 if (!hasMt)
1540 hasMt = true;
1541
1542 margin += results.get(pc, RiskClass::All, mt, "All");
1543 }
1544
1545 // Add the margin to the results if it was calculated
1546 if (hasMt) {
1547 add(nettingSetDetails, regulation, ProductClass::All, RiskClass::All, mt, "All", margin, side);
1548 }
1549 }
1550}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ populateFinalResults() [2/2]

void populateFinalResults ( )
private

Populate final (i.e. winning regulators') using own list of winning regulators, which were determined solely by the SIMM results (i.e. not including any external IMSchedule results)

Definition at line 1595 of file simmcalculator.cpp.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ add() [1/2]

void add ( const ore::data::NettingSetDetails nettingSetDetails,
const string &  regulation,
const CrifRecord::ProductClass pc,
const SimmConfiguration::RiskClass rc,
const SimmConfiguration::MarginType mt,
const std::string &  b,
QuantLib::Real  margin,
SimmSide  side,
const bool  overwrite = true 
)
private

Add a margin result to either call or post results container depending on the side parameter.

Remarks
all additions to the results containers should happen in this method
+ Here is the caller graph for this function:

◆ add() [2/2]

void add ( const ore::data::NettingSetDetails nettingSetDetails,
const string &  regulation,
const CrifRecord::ProductClass pc,
const SimmConfiguration::RiskClass rc,
const SimmConfiguration::MarginType mt,
const std::map< std::string, QuantLib::Real > &  margins,
SimmSide  side,
const bool  overwrite = true 
)
private

◆ splitCrifByRegulationsAndPortfolios()

void splitCrifByRegulationsAndPortfolios ( const Crif crif,
const bool  enforceIMRegulations 
)
private

Add CRIF record to the CRIF records container that correspondsd to the given regulation/s and portfolio ID.

Definition at line 1620 of file simmcalculator.cpp.

1620 {
1621 for (const auto& crifRecord : crif) {
1622 for (const auto& side : {SimmSide::Call, SimmSide::Post}) {
1623 const NettingSetDetails& nettingSetDetails = crifRecord.nettingSetDetails;
1624
1625 bool collectRegsIsEmpty = false;
1626 bool postRegsIsEmpty = false;
1627 if (collectRegsIsEmpty_.find(crifRecord.nettingSetDetails) != collectRegsIsEmpty_.end())
1628 collectRegsIsEmpty = collectRegsIsEmpty_.at(crifRecord.nettingSetDetails);
1629 if (postRegsIsEmpty_.find(crifRecord.nettingSetDetails) != postRegsIsEmpty_.end())
1630 postRegsIsEmpty = postRegsIsEmpty_.at(crifRecord.nettingSetDetails);
1631
1632 string regsString;
1633 if (enforceIMRegulations)
1634 regsString = side == SimmSide::Call ? crifRecord.collectRegulations : crifRecord.postRegulations;
1635 set<string> regs = parseRegulationString(regsString);
1636
1637 auto newCrifRecord = crifRecord;
1638 newCrifRecord.collectRegulations.clear();
1639 newCrifRecord.postRegulations.clear();
1640 for (const string& r : regs) {
1641 if (r == "Excluded" ||
1642 (r == "Unspecified" && enforceIMRegulations && !(collectRegsIsEmpty && postRegsIsEmpty))) {
1643 continue;
1644 } else if (r != "Excluded") {
1645 // Keep a record of trade IDs for each regulation
1646 if (!newCrifRecord.isSimmParameter())
1647 tradeIds_[side][nettingSetDetails][r].insert(newCrifRecord.tradeId);
1648 // We make sure to ignore amountCcy when aggregating the records, since we will only be using
1649 // amountResultCcy, and we may have CRIF records that are equal everywhere except for the amountCcy,
1650 // and this will fail in the case of Risk_XCcyBasis and Risk_Inflation.
1651 const bool onDiffAmountCcy = true;
1652 regSensitivities_[side][nettingSetDetails][r].addRecord(newCrifRecord, onDiffAmountCcy);
1653 }
1654 }
1655 }
1656 }
1657}
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, set< string > > > > tradeIds_
Container for keeping track of what trade IDs belong to each regulation.
set< string > parseRegulationString(const string &regsString, const set< string > &valueIfEmpty)
Reads a string containing regulations applicable for a given CRIF record.
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ lambda()

Real lambda ( QuantLib::Real  theta) const
private

Give the \(\lambda\) used in the curvature margin calculation.

Definition at line 1659 of file simmcalculator.cpp.

1659 {
1660 // Use boost inverse normal here as opposed to QL. Using QL inverse normal
1661 // will cause the ISDA SIMM unit tests to fail
1662 static Real q = boost::math::quantile(boost::math::normal(), 0.995);
1663
1664 return (q * q - 1.0) * (1.0 + theta) - theta;
1665}
+ Here is the caller graph for this function:

◆ getQualifiers()

std::set< std::string > getQualifiers ( const Crif crif,
const ore::data::NettingSetDetails nettingSetDetails,
const CrifRecord::ProductClass pc,
const std::vector< CrifRecord::RiskType > &  riskTypes 
) const
private

Definition at line 1667 of file simmcalculator.cpp.

1670 {
1671 std::set<std::string> qualifiers;
1672 for (auto& rt : riskTypes) {
1673 auto qualis = crif.qualifiersBy(nettingSetDetails, pc, rt);
1674 qualifiers.insert(qualis.begin(), qualis.end());
1675 }
1676 return qualifiers;
1677}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Member Data Documentation

◆ crif_

ore::analytics::Crif crif_
private

All the net sensitivities passed in for the calculation.

Definition at line 112 of file simmcalculator.hpp.

◆ regSensitivities_

std::map<SimmSide, std::map<ore::data::NettingSetDetails, std::map<std::string, Crif> > > regSensitivities_
private

Net sentivities at the regulation level within each netting set.

Definition at line 115 of file simmcalculator.hpp.

◆ simmParameters_

ore::analytics::Crif simmParameters_
private

Record of SIMM parameters that were used in the calculation.

Definition at line 118 of file simmcalculator.hpp.

◆ simmConfiguration_

QuantLib::ext::shared_ptr<SimmConfiguration> simmConfiguration_
private

The SIMM configuration governing the calculation.

Definition at line 121 of file simmcalculator.hpp.

◆ calculationCcyCall_

std::string calculationCcyCall_
private

The SIMM exposure calculation currency i.e. the currency for which FX delta risk is ignored.

Definition at line 124 of file simmcalculator.hpp.

◆ calculationCcyPost_

std::string calculationCcyPost_
private

Definition at line 124 of file simmcalculator.hpp.

◆ resultCcy_

std::string resultCcy_
private

The SIMM result currency i.e. the currency in which the main SIMM results are denominated.

Definition at line 127 of file simmcalculator.hpp.

◆ market_

QuantLib::ext::shared_ptr<ore::data::Market> market_
private

Market data for FX rates to use for converting amounts to USD.

Definition at line 130 of file simmcalculator.hpp.

◆ quiet_

bool quiet_
private

If true, no logging is written out.

Definition at line 133 of file simmcalculator.hpp.

◆ hasSEC_

std::map<SimmSide, std::set<NettingSetDetails> > hasSEC_
private

Definition at line 135 of file simmcalculator.hpp.

◆ hasCFTC_

std::map<SimmSide, std::set<NettingSetDetails> > hasCFTC_
private

Definition at line 135 of file simmcalculator.hpp.

◆ collectRegsIsEmpty_

std::map<ore::data::NettingSetDetails, bool> collectRegsIsEmpty_
private

For each netting set, whether all CRIF records' collect regulations are empty.

Definition at line 138 of file simmcalculator.hpp.

◆ postRegsIsEmpty_

std::map<ore::data::NettingSetDetails, bool> postRegsIsEmpty_
private

For each netting set, whether all CRIF records' post regulations are empty.

Definition at line 141 of file simmcalculator.hpp.

◆ winningRegulations_

std::map<SimmSide, std::map<ore::data::NettingSetDetails, std::string> > winningRegulations_
private

Regulation with highest initial margin for each given netting set.

Definition at line 145 of file simmcalculator.hpp.

◆ simmResults_

std::map<SimmSide, std::map<ore::data::NettingSetDetails, std::map<std::string, SimmResults> > > simmResults_
private

Containers, one for call and post, with a map containing a SimmResults object for each regulation under each portfolio ID

Definition at line 151 of file simmcalculator.hpp.

◆ finalSimmResults_

std::map<SimmSide, std::map<ore::data::NettingSetDetails, std::pair<std::string, SimmResults> > > finalSimmResults_
private

Containers, one for call and post, with a SimmResults object for each portfolio ID, and each margin amount is that of the winning regulation applicable to the portfolio ID

Definition at line 157 of file simmcalculator.hpp.

◆ tradeIds_

std::map<SimmSide, std::map<ore::data::NettingSetDetails, std::map<std::string, set<string> > > > tradeIds_
private

Container for keeping track of what trade IDs belong to each regulation.

Definition at line 161 of file simmcalculator.hpp.

◆ finalTradeIds_

std::map<SimmSide, set<string> > finalTradeIds_
private

Definition at line 163 of file simmcalculator.hpp.