Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
simmbucketmapperbase.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
23
24#include <ql/errors.hpp>
25
26#include <ostream>
27
28using namespace QuantLib;
29
35using std::map;
36using std::string;
37
38namespace ore {
39namespace analytics {
40
41// Ease syntax
43
44// Helper variable for switching risk type before lookup
45const map<RiskType, RiskType> nonVolRiskTypeMap = {{RiskType::IRVol, RiskType::IRCurve},
46 {RiskType::InflationVol, RiskType::IRCurve},
47 {RiskType::CreditVol, RiskType::CreditQ},
48 {RiskType::CreditVolNonQ, RiskType::CreditNonQ},
49 {RiskType::EquityVol, RiskType::Equity},
50 {RiskType::CommodityVol, RiskType::Commodity}};
51
53 const QuantLib::ext::shared_ptr<ore::data::ReferenceDataManager>& refDataManager,
54 const QuantLib::ext::shared_ptr<SimmBasicNameMapper>& nameMapper) :
55 refDataManager_(refDataManager), nameMapper_(nameMapper){
56
57 // Fill the set of risk types that have buckets
58 rtWithBuckets_ = {RiskType::IRCurve, RiskType::CreditQ, RiskType::CreditNonQ, RiskType::Equity,
59 RiskType::Commodity, RiskType::IRVol, RiskType::InflationVol, RiskType::CreditVol,
60 RiskType::CreditVolNonQ, RiskType::EquityVol, RiskType::CommodityVol};
61}
62
63std::string BucketMapping::name() const {
64 std::ostringstream o;
65 o << bucket_ << "-" << validFrom_ << "-" << validTo_ << "-" << fallback_;
66 return o.str();
67}
68
69bool operator<(const BucketMapping &a, const BucketMapping &b) {
70 return a.name() < b.name();
71}
72
73QuantLib::Date BucketMapping::validToDate() const {
74 if (validTo_.empty())
75 return Date::maxDate();
76 else
78}
79
80QuantLib::Date BucketMapping::validFromDate() const {
81 if (validFrom_.empty())
82 return Date::minDate();
83 else
85}
86
87string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qualifier) const {
88
89 auto key = std::make_pair(riskType, qualifier);
90 if (auto b = cache_.find(key); b != cache_.end())
91 return b->second;
92
93 QL_REQUIRE(hasBuckets(riskType), "The risk type " << riskType << " does not have buckets");
94
95 // Vol risk type bucket mappings are stored in their non-vol counterparts
96 auto lookupRiskType = riskType;
97 auto nv = nonVolRiskTypeMap.find(riskType);
98 if (nv != nonVolRiskTypeMap.end()) {
99 lookupRiskType = nv->second;
100 }
101
102 // Deal with RiskType::IRCurve
103 if (lookupRiskType == RiskType::IRCurve || lookupRiskType == RiskType::GIRR_DELTA) {
104 auto tmp = irBucket(qualifier);
105 cache_[key] = tmp;
106 return tmp;
107 }
108
109 string bucket;
110 string lookupName = qualifier;
111
112 bool haveMapping = has(lookupRiskType, lookupName, false);
113 bool haveFallback = has(lookupRiskType, lookupName, true);
114 bool noBucket = !haveMapping && !haveFallback;
115
116 if (noBucket && nameMapper_ != nullptr &&
117 (lookupRiskType == RiskType::Equity || lookupRiskType == RiskType::EQ_DELTA)) {
118 // if we have a simm name mapping we do a reverse lookup on the name as the CRIF qualifier isn't in reference data
119 lookupName = nameMapper_->externalName(qualifier);
120 haveMapping = has(lookupRiskType, lookupName, false);
121 noBucket = !haveMapping && !haveFallback;
122 }
123
124 // Do some checks and return the lookup
125 if (noBucket) {
126 if (lookupRiskType == RiskType::Commodity) {
127 bool commIndex = false;
128 // first check is there is an entry under equity reference, this may tell us if it is an index
129 if (refDataManager_ != nullptr && refDataManager_->hasData("Equity", lookupName)) {
130 auto refData = QuantLib::ext::dynamic_pointer_cast<ore::data::EquityReferenceDatum>(
131 refDataManager_->getData("Equity", lookupName));
132 if (refData->equityData().isIndex)
133 commIndex = true;
134 }
135
136
137 // Commodity does not allow "Residual", but bucket 16 is "Other", bucket 17 for Indices
138 // FAQ Methodology and implementation 20180124 (H3) says to use "Other"
139 bucket = commIndex ? "17" : "16";
140 TLOG("Don't have any bucket mappings for the "
141 << "combination of risk type " << riskType << " and qualifier " << lookupName
142 << " - assigning to bucket " << bucket);
143
144 } else if (lookupRiskType == RiskType::Equity) {
145 // check if it is an index
146 if (refDataManager_ != nullptr && refDataManager_->hasData("Equity", lookupName)) {
147 auto refData = QuantLib::ext::dynamic_pointer_cast<ore::data::EquityReferenceDatum>(refDataManager_->getData("Equity", lookupName));
148 // if the equity is an index we assign to bucket 11
149 if (refData->equityData().isIndex) {
150 TLOG("Don't have any bucket mappings for the "
151 << "combination of risk type " << riskType << " and qualifier " << lookupName
152 << " - assigning to bucket 11");
153 bucket = "11";
154 }
155 }
156 }
157
158 if (bucket.empty()) {
159 TLOG("Don't have any bucket mappings for the "
160 << "combination of risk type " << riskType << " and qualifier " << lookupName
161 << " - assigning to Residual/Other bucket");
162 bucket = "Residual";
163 }
164
165 FailedMapping fm;
166 fm.name = qualifier;
167 fm.lookupName = lookupName;
168 fm.riskType = riskType;
169 fm.lookupRiskType = lookupRiskType;
170 failedMappings_.insert(fm);
171
172 } else {
173 // We may have several mappings per qualifier, pick the first valid one that matches the fallback flag
174 Date today = Settings::instance().evaluationDate();
175 for (auto m : bucketMapping_.at(lookupRiskType).at(lookupName)) {
176 if (m.validToDate() >= today && m.validFromDate() <= today && m.fallback() == !haveMapping) {
177 bucket = m.bucket();
178 cache_[key] = bucket;
179 return bucket;
180 }
181 }
182 TLOG("bucket mapping for risk type " << riskType << " and qualifier " << qualifier << " inactive, return Residual");
183 bucket = "Residual";
184 }
185
186 cache_[key] = bucket;
187 return bucket;
188}
189
190bool SimmBucketMapperBase::hasBuckets(const RiskType& riskType) const { return rtWithBuckets_.count(riskType) > 0; }
191
192bool SimmBucketMapperBase::has(const RiskType& riskType, const string& qualifier,
193 boost::optional<bool> fallback) const {
194
195 // Vol risk type bucket mappings are stored in their non-vol counterparts
196 auto lookupRiskType = riskType;
197 auto nv = nonVolRiskTypeMap.find(riskType);
198 if (nv != nonVolRiskTypeMap.end()) {
199 lookupRiskType = nv->second;
200 }
201
202 if (lookupRiskType == RiskType::IRCurve || lookupRiskType == RiskType::GIRR_DELTA)
203 return true;
204
205 auto bm = bucketMapping_.find(lookupRiskType);
206 if (bm == bucketMapping_.end())
207 return false;
208
209 auto q = bm->second.find(qualifier);
210 if (q == bm->second.end())
211 return false;
212
213 Date today = Settings::instance().evaluationDate();
214
215 // We may have several mappings (several periods, override and fallback mappings)
216 // So pick the first valid one
217
218 for (const auto& m : q->second) {
219 Date validTo = m.validToDate();
220 Date validFrom = m.validFromDate();
221 if (validTo >= today && validFrom <= today && (fallback ? *fallback == m.fallback() : true))
222 return true;
223 }
224 return false;
225}
226
228 XMLUtils::checkNode(node, "SIMMBucketMappings");
229
230 // Every time a call to fromXML is made, reset the bucket mapper to its initial state
231 reset();
232
233 LOG("Start parsing SIMMBucketMappings");
234
235 for (XMLNode* rtNode = XMLUtils::getChildNode(node); rtNode; rtNode = XMLUtils::getNextSibling(rtNode)) {
236 // Get the risk type that we are dealing with
237 string strRiskType = XMLUtils::getNodeName(rtNode);
238 RiskType riskType = parseRiskType(strRiskType);
239
240 checkRiskType(riskType);
241
242 QL_REQUIRE(bucketMapping_.count(riskType) == 0,
243 "Can only have one node for each risk type. " << strRiskType << " appears more than once.");
244 bucketMapping_[riskType] = {};
245
246 // Loop over and add the bucket mappings for this risk type
247 for (XMLNode* mappingNode = XMLUtils::getChildNode(rtNode, "Mapping"); mappingNode;
248 mappingNode = XMLUtils::getNextSibling(mappingNode, "Mapping")) {
249
250 string validTo = XMLUtils::getChildValue(mappingNode, "ValidTo", false);
251 string validFrom = XMLUtils::getChildValue(mappingNode, "ValidFrom", false);
252 string qualifier = XMLUtils::getChildValue(mappingNode, "Qualifier", false);
253 string bucket = XMLUtils::getChildValue(mappingNode, "Bucket", false);
254 if (bucket == "" || qualifier == "") {
255 ALOG("skip bucket mapping for quaifier '" << qualifier << "' and bucket '" << bucket << "'");
256 continue;
257 }
258 string fallbackString = XMLUtils::getChildValue(mappingNode, "Fallback", false);
259 bool fallback = false;
260 if (fallbackString != "")
261 fallback = ore::data::parseBool(fallbackString);
262
263 if (validTo != "") {
264 // Check whether the provided validTo element is a valid date, ALERT and ignore if it is not
265 try {
266 ore::data::parseDate(validTo);
267 } catch(std::exception&) {
268 ALOG("Cannot parse bucket mapping validTo " << validTo << " for qualifier " << qualifier << ", ignore");
269 validTo = "";
270 }
271 }
272 if (validFrom != "") {
273 // Check whether the provided validFrom element is a valid date, ALERT and ignore if it is not
274 try {
275 ore::data::parseDate(validFrom);
276 } catch(std::exception&) {
277 ALOG("Cannot parse bucket mapping validFrom " << validFrom << " for qualifier " << qualifier << ", ignore");
278 validFrom = "";
279 }
280 }
281
282 // Add mapping
283 BucketMapping mapping(bucket, validFrom, validTo, fallback);
284 if (bucketMapping_[riskType].find(qualifier) == bucketMapping_[riskType].end())
285 bucketMapping_[riskType][qualifier] = std::set<BucketMapping>();
286 bucketMapping_[riskType][qualifier].insert(mapping);
287 TLOG("Added SIMM bucket mapping: {" << riskType << ": {" << qualifier << ", " << bucket << ", " << validFrom << ", " << validTo << ", " << fallback << "}}");
288 if (bucket != "Residual") {
289 int bucketInt = ore::data::parseInteger(bucket);
290 QL_REQUIRE(bucketInt >= 1, "found bucket " << bucket << ", expected >= 1");
291 }
292 }
293 }
294
295 LOG("Finished parsing SIMMBucketMappings");
296}
297
299 XMLNode* node = doc.allocNode("SIMMBucketMappings");
300 for (auto const& b : bucketMapping_) {
301 XMLNode* n = doc.allocNode(ore::data::to_string(b.first));
302 XMLUtils::appendNode(node, n);
303 for (auto const& bms : b.second) {
304 for (auto const& bm : bms.second) {
305 XMLNode* m = doc.allocNode("Mapping");
307 if (!bms.first.empty())
308 XMLUtils::addChild(doc, m, "Qualifier", bms.first);
309 if (!bm.validTo().empty())
310 XMLUtils::addChild(doc, m, "ValidTo", bm.validTo());
311 if (!bm.validFrom().empty())
312 XMLUtils::addChild(doc, m, "ValidFrom", bm.validFrom());
313 if (!bm.bucket().empty())
314 XMLUtils::addChild(doc, m, "Bucket", bm.bucket());
315 if (bm.fallback())
316 XMLUtils::addChild(doc, m, "Fallback", bm.fallback());
317 }
318 }
319 }
320 return node;
321}
322
323void SimmBucketMapperBase::addMapping(const RiskType& riskType, const string& qualifier, const string& bucket,
324 const string& validFrom, const string& validTo, bool fallback) {
325
326 cache_.clear();
327
328 // Possibly map to non-vol counterpart for lookup
329 RiskType rt = riskType;
330 if (nonVolRiskTypeMap.count(riskType) > 0) {
331 rt = nonVolRiskTypeMap.at(riskType);
332 }
333
334 if (rt == RiskType::IRCurve) {
335 // IR has internal mapping so return early - no need for warning
336 // Could catch commodity here but allow extra mappings there
337 return;
338 }
339
340 QL_REQUIRE(hasBuckets(riskType),
341 "Tried to add a bucket mapping for risk type " << riskType << " but it does not have buckets.");
342
343 if (bucketMapping_[rt].find(qualifier) == bucketMapping_[rt].end())
344 bucketMapping_[rt][qualifier] = std::set<BucketMapping>();
345
346 std::string vf = validFrom, vt = validTo;
347 if (vf != "") {
348 try {
350 } catch(std::exception& e) {
351 ALOG("Error parsing validFrom date, ignore: " << e.what());
352 vf = "";
353 }
354 }
355 if (vt != "") {
356 try {
358 } catch(std::exception& e) {
359 ALOG("Error parsing validTo date, ignore: " << e.what());
360 vt = "";
361 }
362 }
363
364 bucketMapping_[rt][qualifier].insert(BucketMapping(bucket, vt, vf, fallback));
365}
366
367string SimmBucketMapperBase::irBucket(const string& qualifier) const {
368 if (qualifier == "USD" || qualifier == "EUR" || qualifier == "GBP" || qualifier == "AUD" || qualifier == "CAD" ||
369 qualifier == "CHF" || qualifier == "DKK" || qualifier == "HKD" || qualifier == "KRW" || qualifier == "NOK" ||
370 qualifier == "NZD" || qualifier == "SEK" || qualifier == "SGD" || qualifier == "TWD")
371 return "1";
372 if (qualifier == "JPY")
373 return "2";
374 return "3";
375}
376
378 QL_REQUIRE(riskType != RiskType::IRCurve, "Risk type " << RiskType::IRCurve << " is mapped to buckets internally.");
379
380 QL_REQUIRE(hasBuckets(riskType), "The risk type " << riskType << " does not have buckets.");
381
382 QL_REQUIRE(nonVolRiskTypeMap.count(riskType) == 0, "The vol risk type "
383 << "mappings are stored in their non-vol counterparts. Use "
384 << nonVolRiskTypeMap.at(riskType) << " instead of "
385 << riskType << ".");
386}
387
389 cache_.clear();
390 // Clear the bucket mapper and add back the commodity mappings
391 bucketMapping_.clear();
392 failedMappings_.clear();
393}
394
395} // namespace analytics
396} // namespace ore
QuantLib::Date validFromDate() const
QuantLib::Date validToDate() const
bool hasBuckets(const CrifRecord::RiskType &riskType) const override
Check if the given SIMM RiskType has a bucket structure.
std::string bucket(const CrifRecord::RiskType &riskType, const std::string &qualifier) const override
ore::data::XMLNode * toXML(ore::data::XMLDocument &) const override
SimmBucketMapperBase(const QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > &refDataManager=nullptr, const QuantLib::ext::shared_ptr< SimmBasicNameMapper > &nameMapper=nullptr)
Default constructor that adds fixed known mappings.
void addMapping(const CrifRecord::RiskType &riskType, const std::string &qualifier, const std::string &bucket, const std::string &validFrom="", const std::string &validTo="", bool fallback=false) override
Add a single bucket mapping for qualifier with risk type riskType.
void fromXML(ore::data::XMLNode *node) override
bool has(const CrifRecord::RiskType &riskType, const std::string &qualifier, boost::optional< bool > fallback=boost::none) const override
Check if the given riskType and qualifier has a valid mapping.
virtual std::string irBucket(const std::string &qualifier) const
void checkRiskType(const CrifRecord::RiskType &riskType) const
Check the risk type before adding a mapping entry.
std::map< CrifRecord::RiskType, std::map< std::string, std::set< BucketMapping > > > bucketMapping_
QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > refDataManager_
Reference data manager.
void reset()
Reset the SIMM bucket mapper i.e. clears all mappings and adds the initial hard-coded commodity mappi...
std::set< CrifRecord::RiskType > rtWithBuckets_
Set of SIMM risk types that have buckets.
QuantLib::ext::shared_ptr< SimmBasicNameMapper > nameMapper_
Simm Name Mapper.
std::map< std::pair< CrifRecord::RiskType, std::string >, std::string > cache_
XMLNode * allocNode(const string &nodeName)
static void checkNode(XMLNode *n, const string &expectedName)
static string getNodeName(XMLNode *n)
static string getChildValue(XMLNode *node, const string &name, bool mandatory=false, const string &defaultValue=string())
static XMLNode * getChildNode(XMLNode *n, const string &name="")
static XMLNode * getNextSibling(XMLNode *node, const string &name="")
static XMLNode * addChild(XMLDocument &doc, XMLNode *n, const string &name)
static void appendNode(XMLNode *parent, XMLNode *child)
Date parseDate(const string &s)
bool parseBool(const string &s)
bool checkCurrency(const string &code)
Integer parseInteger(const string &s)
#define LOG(text)
#define ALOG(text)
#define TLOG(text)
bool operator<(const Dividend &d1, const Dividend &d2)
CrifRecord::RiskType RiskType
Definition: crifloader.cpp:92
boost::bimap< T, boost::bimaps::set_of< string, string_cmp > > bm
Definition: riskfilter.cpp:42
const map< RiskType, RiskType > nonVolRiskTypeMap
CrifRecord::RiskType parseRiskType(const string &rt)
Definition: crifrecord.cpp:117
std::string to_string(const LocationInfo &l)
Base SIMM class for mapping qualifiers to buckets.