31#include <ql/math/comparison.hpp>
32#include <ql/time/daycounters/actualactual.hpp>
40using QuantLib::close_enough;
54 const QuantLib::ext::shared_ptr<ore::data::Market> market,
55 const bool determineWinningRegulations,
const bool enforceIMRegulations,
57 const map<
SimmSide, set<NettingSetDetails>>& hasSEC,
58 const map<
SimmSide, set<NettingSetDetails>>& hasCFTC)
59 : crif_(crif), calculationCcy_(calculationCcy), market_(market), quiet_(quiet),
60 hasSEC_(hasSEC), hasCFTC_(hasCFTC) {
63 "The calculation currency (" <<
calculationCcy_ <<
") must be a valid ISO currency code");
65 QuantLib::Date today = QuantLib::Settings::instance().evaluationDate();
66 QuantLib::DayCounter dayCounter = QuantLib::ActualActual(QuantLib::ActualActual::ISDA);
71 const bool isSchedule = cr.imModel ==
"Schedule";
74 if (determineWinningRegulations) {
76 cr.tradeId, cr.tradeType,
"IM Schedule calculator",
77 "Skipping over CRIF record without im_model=Schedule for portfolio [ " +
87 }
else if (
collectRegsIsEmpty_.at(cr.nettingSetDetails) && !cr.collectRegulations.empty()) {
92 }
else if (
postRegsIsEmpty_.at(cr.nettingSetDetails) && !cr.postRegulations.empty()) {
102 LOG(
"IMScheduleCalculator: Collecting CRIF trade data");
103 for (
const auto& crifRecord :
crif_)
110 for (
auto& nv : sv.second) {
113 for (
auto& rv : nv.second) {
114 const string& regulation = rv.first;
115 auto& tradeDataMap = rv.second;
118 set<string> tradesToRemove;
119 for (
auto& td : tradeDataMap) {
120 if (td.second.incomplete()) {
121 auto subFields = map<string, string>({{
"tradeId", td.first}});
123 if (td.second.missingPVData()) {
124 td.second.presentValue = 0.0;
125 td.second.presentValueUsd = 0.0;
126 td.second.presentValueCcy = td.second.notionalCcy;
129 if (td.second.missingNotionalData()) {
131 "IMSchedule",
"Incomplete CRIF trade data",
132 "Missing Notional data. The trade will not be processed.", subFields)
134 tradesToRemove.insert(td.first);
139 for (
const string& tid : tradesToRemove) {
140 tradeDataMap.erase(tid);
141 tradeIds_.at(side).at(nsd).at(regulation).erase(tid);
145 for (
auto& td : tradeDataMap) {
149 tradeData.
maturity = dayCounter.yearFraction(today, tradeData.
endDate);
160 if (side == SimmSide::Call)
162 if (side == SimmSide::Post)
173 for (
auto& s : sv.second) {
178 const bool hasCFTCGlobal =
hasCFTC_.at(side).find(nettingDetails) !=
hasCFTC_.at(side).end();
179 const bool hasSECGlobal =
hasSEC_.at(side).find(nettingDetails) !=
hasSEC_.at(side).end();
180 const bool hasCFTCLocal = s.second.count(
"CFTC") > 0;
181 const bool hasSECLocal = s.second.count(
"SEC") > 0;
183 if ((hasSECLocal && hasCFTCLocal) || (hasCFTCGlobal && hasSECGlobal)) {
184 if (!hasSECLocal && !hasCFTCLocal)
189 const map<string, IMScheduleTradeData>& tradeDataMapCFTC = s.second.at(
"CFTC");
190 map<string, IMScheduleTradeData>& tradeDataMapSEC = s.second[
"SEC"];
191 for (
const auto& kv : tradeDataMapCFTC) {
194 if (tradeDataMapSEC.find(kv.first) == tradeDataMapSEC.end()) {
195 tradeDataMapSEC[kv.first] = kv.second;
204 if (s.second.count(
"Unspecified") > 0 && s.second.size() > 1)
205 s.second.erase(
"Unspecified");
210 LOG(
"IMScheduleCalculator: Populating higher level results")
214 for (
const auto& nv : sv.second) {
217 for (
const auto& rv : nv.second) {
218 const string& regulation = rv.first;
224 if (determineWinningRegulations) {
225 LOG(
"IMScheduleCalculator: Determining winning regulations");
233 for (
const auto& nv : sv.second) {
235 Real winningMargin = std::numeric_limits<Real>::min();
236 map<string, Real> nettingSetMargins;
237 vector<Real> margins;
239 for (
const auto& rv : nv.second) {
240 const string& regulation = rv.first;
242 const Real& im = schResult.
get(ProductClass::All).
scheduleIM;
244 nettingSetMargins[regulation] = im;
245 if (im > winningMargin)
251 for (
const auto& kv : nettingSetMargins) {
274 QL_REQUIRE(subWinningRegs.find(nettingSetDetails) != subWinningRegs.end(),
275 "IMScheduleCalculator::winningRegulations(): Could not find netting set in the list of "
276 << side <<
" schedule IM winning regulations: " << nettingSetDetails);
277 return subWinningRegs.at(nettingSetDetails);
282 "IMScheduleCalculator::winningRegulations(): Could not find list of"
283 << side <<
" schedule IM winning regulations");
287const map<SimmConfiguration::SimmSide, map<NettingSetDetails, string>>&
294 QL_REQUIRE(subResults.find(nsd) != subResults.end(),
295 "IMScheduleCalculator::imScheduleSummaryResults(): Could not find netting set in the "
296 << side <<
" IM schedule results: " << nsd);
297 return subResults.at(nsd);
302 "IMScheduleCalculator::imScheduleSummaryResults(): Could not find " << side
303 <<
" IM in the IM Schedule results");
307const map<SimmConfiguration::SimmSide, map<NettingSetDetails, map<string, IMScheduleResults>>>&
315 QL_REQUIRE(subResults.find(nsd) != subResults.end(),
316 "IMScheduleCalculator::finalImScheduleSummaryResults(): Could not find netting set in the final IM Schedule "
317 << side <<
" results: " << nsd);
318 return subResults.at(nsd);
323 "IMScheduleCalculator::finalImScheduleSummaryResults(): Could not find "
324 << side <<
" IM in the final IM Schedule results");
330 "IMScheduleCalculator::imScheduleTradeResults(): Could not find results for trade: " << tradeId);
339 if (pc == ProductClass::Credit) {
348 }
else if (pc == ProductClass::Commodity) {
351 }
else if (pc == ProductClass::Equity) {
354 }
else if (pc == ProductClass::FX) {
357 }
else if (pc == ProductClass::Rates) {
366 }
else if (pc == ProductClass::Other) {
369 QL_FAIL(
"IMSchedule::label() Invalid product class " << pc);
374 const map<IMScheduleLabel, string> labelStringMap_ = map<IMScheduleLabel, string>({
387 return labelStringMap_.at(
label);
392 DLOG(
"Processing CRIF record for IMSchedule calculation: trade ID \'"
395 QL_REQUIRE(cr.
riskType == RiskType::PV || cr.
riskType == RiskType::Notional,
396 "Unexpected risk type found in CRIF " << cr.
riskType <<
" for trade ID " << cr.
tradeId);
398 for (
const auto& side : {SimmSide::Call, SimmSide::Post}) {
401 bool collectRegsIsEmpty =
false;
402 bool postRegsIsEmpty =
false;
409 if (enforceIMRegulations)
413 for (
const string& r : regs) {
414 if (r ==
"Unspecified" && enforceIMRegulations && !(collectRegsIsEmpty && postRegsIsEmpty)) {
416 }
else if (r !=
"Excluded") {
421 auto it = tradeDataMap.find(cr.
tradeId);
422 if (it != tradeDataMap.end()) {
429 QL_REQUIRE(incomingDate == tradeData.
endDate,
"End date is not matching for trade ID "
430 << cr.
tradeId <<
": " << incomingDate <<
" and "
433 QL_REQUIRE(tradeData.
missingPVData(),
"Adding PV data for trade that already has PV data, i.e. "
434 "multiple PV records found for the same trade: "
441 "Adding Notional data for trade that already has PV data, i.e. "
442 "multiple Notional records found for the same trade: "
450 const string postRegs = side == SimmSide::Post ? cr.
postRegulations :
"";
464 LOG(
"IMScheduleCalculator: Populating " << side <<
" IM for netting set [" << nettingSetDetails
465 <<
"] under regulation " << regulation);
470 Real grossMarginCalc = 0;
471 Real grossRCCalc = 0;
472 Real presentValueCalc = 0;
475 for (
const auto& td : regTradeData) {
491 Real netRCCalc = side == SimmSide::Call ?
max(0.0, presentValueCalc) :
min(0.0, presentValueCalc);
494 Real netToGrossCalc =
close_enough(grossRCCalc, 0.0) ? 1.0 : netRCCalc / grossRCCalc;
497 Real scheduleMarginCalc = grossMarginCalc * (0.4 + 0.6 * netToGrossCalc);
501 .at(nettingSetDetails)
503 .add(ProductClass::All,
calculationCcy_, grossMarginCalc, grossRCCalc, netRCCalc, netToGrossCalc,
509 LOG(
"IMScheduleCalculator: Populating final winning regulators' IM");
517 for (
const auto& nv : sv.second) {
532 for (
const auto& nv : sv.second) {
533 for (
const auto& rv : nv.second) {
534 const string& regulation = rv.first;
535 for (
const auto& td : rv.second) {
537 const string& tradeId = td.first;
538 IMScheduleTradeData tradeData = td.second;
548 bool foundMatch =
false;
551 if (std::tie(tdd.presentValueCcy, tdd.presentValueUsd, tdd.notionalCcy, tdd.notionalUsd) ==
552 std::tie(tradeData.presentValueCcy, tradeData.presentValueUsd, tradeData.notionalCcy,
553 tradeData.notionalUsd)) {
554 if (side == SimmSide::Call) {
577 const ProductClass& pc,
const string& calcCcy,
const Real& grossIM,
const Real& grossRC,
578 const Real& netRC,
const Real& ngr,
const Real& scheduleIM) {
580 QuantLib::Real netToGrossRatio = ngr != Null<Real>() &&
close_enough(ngr, 0.0) ? 0.0 : ngr;
581 imScheduleResults_[side][nsd][regulation].add(pc, calcCcy, grossIM, grossRC, netRC, netToGrossRatio, scheduleIM);
void addRecord(const CrifRecord &record, bool aggregateDifferentAmountCurrencies=false, bool sortFxVolQualifer=true)
void populateFinalResults()
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, IMScheduleResults > > > imScheduleResults_
Containers, one for call and post, with an IMScheduleResults object for each regulation under each po...
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, IMScheduleResults > > > & imScheduleSummaryResults() const
const std::map< std::string, std::vector< IMScheduleTradeData > > & imScheduleTradeResults() const
std::map< SimmSide, std::set< NettingSetDetails > > hasCFTC_
std::map< SimmSide, std::set< NettingSetDetails > > hasSEC_
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::string > > winningRegulations_
Regulation with highest IM for each given netting set.
std::map< ore::data::NettingSetDetails, bool > collectRegsIsEmpty_
For each netting set, whether all CRIF records' collect regulations are empty.
const std::map< SimmSide, std::map< ore::data::NettingSetDetails, string > > & winningRegulations() const
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::pair< std::string, IMScheduleResults > > > finalImScheduleResults_
Containers, one for call and post, with an IMScheduleResults object for each portfolio ID.
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, std::pair< std::string, IMScheduleResults > > > & finalImScheduleSummaryResults() const
static const IMScheduleLabel label(const ProductClass &productClass, const QuantLib::Real &maturity)
std::map< SimmSide, std::map< ore::data::NettingSetDetails, std::map< std::string, std::map< std::string, IMScheduleTradeData > > > > nettingSetRegTradeData_
Container for trade data, taking into account regulations applicable to each netting set.
QuantLib::ext::shared_ptr< ore::data::Market > market_
Market data for FX rates to use for converting amounts to USD.
std::map< std::string, std::vector< IMScheduleTradeData > > finalTradeData_
static const std::string labelString(const IMScheduleLabel &label)
std::string calculationCcy_
The SIMM calculation currency i.e. the currency of the SIMM results.
IMScheduleCalculator(const Crif &crif, const std::string &calculationCcy="USD", 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 IMScheduleCalculator from a container of netted CRIF records.
ore::analytics::Crif crif_
The net sensitivities used in the calculation.
void collectTradeData(const CrifRecord &cr, const bool enforceIMRegulations)
Collect trade data as defined by the CRIF records.
void add(const SimmSide &side, const ore::data::NettingSetDetails &nsd, const std::string ®ulation, const CrifRecord::ProductClass &pc, const std::string &ccy, const QuantLib::Real &grossIM, const QuantLib::Real &grossRC=QuantLib::Null< QuantLib::Real >(), const QuantLib::Real &netRC=QuantLib::Null< QuantLib::Real >(), const QuantLib::Real &ngr=QuantLib::Null< QuantLib::Real >(), const QuantLib::Real &scheduleIM=QuantLib::Null< QuantLib::Real >())
Add a margin result to either call or post results container depending on the SimmSide parameter.
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.
QuantLib::Real multiplier(const IMScheduleLabel &label)
void populateResults(const ore::data::NettingSetDetails &nsd, const string ®ulation, const SimmSide &side)
SimmConfiguration::SimmSide SimmSide
IMScheduleResult get(const CrifRecord::ProductClass &pc) const
Convert Schedule IM amounts to a different currency.
SimmSide
Enum indicating the relevant side of the SIMM calculation.
Struct for holding a CRIF record.
Date parseDate(const string &s)
bool parseBool(const string &s)
bool checkCurrency(const string &code)
Class for calculating SIMM.
Class for holding IMSchedule results.
RandomVariable max(RandomVariable x, const RandomVariable &y)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable min(RandomVariable x, const RandomVariable &y)
string combineRegulations(const string ®s1, const string ®s2)
SimmConfiguration::Regulation getWinningRegulation(const std::vector< string > &winningRegulations)
From a vector of regulations, determine the winning regulation based on order of priority.
set< string > parseRegulationString(const string ®sString, const set< string > &valueIfEmpty)
Reads a string containing regulations applicable for a given CRIF record.
std::string to_string(const LocationInfo &l)
std::string amountCurrency
std::string collectRegulations
ProductClass productClass
NettingSetDetails nettingSetDetails
std::string postRegulations
std::string presentValueCcy
bool missingNotionalData()
QuantLib::Real presentValue
QuantLib::Real multiplier
QuantLib::Real notionalUsd
QuantLib::Real grossMarginUsd
std::string collectRegulations
QuantLib::Real grossMarginCalc
CrifRecord::ProductClass productClass
QuantLib::Real presentValueUsd
std::string postRegulations
QuantLib::Real notionalCalc
QuantLib::Real presentValueCalc
QuantLib::Real scheduleIM
Class for structured analytics warnings.