Skip to content

Commit f3b9138

Browse files
damienbarkerjenkins
authored andcommitted
QPR-12176 Frtb Crif Generator
1 parent 4a2dcc0 commit f3b9138

32 files changed

Lines changed: 247 additions & 127 deletions

OREAnalytics/orea/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ scenario/simplescenario.cpp
103103
scenario/stressscenariodata.cpp
104104
scenario/stressscenariogenerator.cpp
105105
simm/crif.cpp
106+
simm/crifconfiguration.cpp
106107
simm/crifloader.cpp
107108
simm/crifrecord.cpp
108109
simm/imschedulecalculator.cpp
@@ -263,6 +264,7 @@ scenario/simplescenariofactory.hpp
263264
scenario/stressscenariodata.hpp
264265
scenario/stressscenariogenerator.hpp
265266
simm/crif.hpp
267+
simm/crifconfiguration.hpp
266268
simm/crifloader.hpp
267269
simm/crifrecord.hpp
268270
simm/imschedulecalculator.hpp

OREAnalytics/orea/engine/stresstest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ StressTest::StressTest(const boost::shared_ptr<ore::data::Portfolio>& portfolio,
6666
auto ed = boost::make_shared<EngineData>(*engineData);
6767
ed->globalParameters()["RunType"] = "Stress";
6868
boost::shared_ptr<EngineFactory> factory =
69-
boost::make_shared<EngineFactory>(ed, simMarket, configurations, referenceData, iborFallbackConfig);
69+
boost::make_shared<EngineFactory>(ed, simMarket, configurations, referenceData, iborFallbackConfig);
7070

7171
DLOG("Reset and Build Portfolio");
7272
portfolio->reset();

OREAnalytics/orea/engine/varcalculator.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class VarReport : public MarketRiskReport {
137137
const QuantLib::ext::shared_ptr<MarketRiskGroup>& riskGroup,
138138
const QuantLib::ext::shared_ptr<TradeGroup>& tradeGroup);
139139

140-
virtual std::vector<ore::data::TimePeriod> timePeriods() { return {period_.get()}; }
140+
std::vector<ore::data::TimePeriod> timePeriods() override { return {period_.get()}; }
141141

142142
private:
143143
QuantLib::ext::shared_ptr<Portfolio> portfolio_;

OREAnalytics/orea/orea.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
#include <orea/scenario/stressscenariodata.hpp>
124124
#include <orea/scenario/stressscenariogenerator.hpp>
125125
#include <orea/simm/crif.hpp>
126+
#include <orea/simm/crifconfiguration.hpp>
126127
#include <orea/simm/crifloader.hpp>
127128
#include <orea/simm/crifrecord.hpp>
128129
#include <orea/simm/imschedulecalculator.hpp>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright (C) 2024 AcadiaSoft Inc.
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+
19+
/*! \file orea/simm/crifconfiguration.hpp
20+
\brief CRIF configuration interface
21+
*/
22+
23+
#include <boost/algorithm/string/predicate.hpp>
24+
#include <orea/simm/crifconfiguration.hpp>
25+
#include <qle/indexes/ibor/termrateindex.hpp>
26+
27+
namespace ore {
28+
namespace analytics {
29+
30+
string periodToLabels2(const QuantLib::Period& p) {
31+
if ((p.units() == QuantLib::Months && p.length() == 3) || (p.units() == QuantLib::Weeks && p.length() == 13)) {
32+
return "Libor3m";
33+
} else if ((p.units() == QuantLib::Months && p.length() == 6) || (p.units() == QuantLib::Weeks && p.length() == 26)) {
34+
return "Libor6m";
35+
} else if ((p.units() == QuantLib::Days && p.length() == 1) || p == 1 * QuantLib::Weeks) {
36+
// 7 days here is based on ISDA SIMM FAQ and Implementation Questions, Sep 4, 2019 Section E.9
37+
// Sub curve to be used for CNY seven-day repo rate (closest is OIS).
38+
return "OIS";
39+
} else if ((p.units() == QuantLib::Months && p.length() == 1) || (p.units() == QuantLib::Weeks && p.length() == 2) ||
40+
(p.units() == QuantLib::Weeks && p.length() == 4) || (p.units() == QuantLib::Days && p.length() >= 28 && p.length() <= 31)) {
41+
// 2 weeks here is based on ISDA SIMM Methodology paragraph 14:
42+
// "Any sub curve not given on the above list should be mapped to its closest equivalent."
43+
// A 2 week rate is more like sub-period than OIS.
44+
return "Libor1m";
45+
} else if ((p.units() == QuantLib::Months && p.length() == 12) || (p.units() == QuantLib::Years && p.length() == 1) ||
46+
(p.units() == QuantLib::Weeks && p.length() == 52)) {
47+
return "Libor12m";
48+
} else {
49+
return "";
50+
}
51+
}
52+
53+
std::string CrifConfiguration::label2(const QuantLib::Period& p) const {
54+
std::string label2 = periodToLabels2(p);
55+
QL_REQUIRE(!label2.empty(), "Could not determine SIMM Label2 for period " << p);
56+
return label2;
57+
}
58+
59+
std::string CrifConfiguration::label2(const boost::shared_ptr<QuantLib::InterestRateIndex>& irIndex) const {
60+
std::string label2;
61+
if (boost::algorithm::starts_with(irIndex->name(), "BMA")) {
62+
// There was no municipal until later so override this in
63+
// derived configurations and use 'Prime' in base
64+
label2 = "Prime";
65+
} else if (irIndex->familyName() == "Prime") {
66+
label2 = "Prime";
67+
} else if(boost::dynamic_pointer_cast<QuantExt::TermRateIndex>(irIndex) != nullptr) {
68+
// see ISDA-SIMM-FAQ_Methodology-and-Implementation_20220323_clean.pdf: E.8 Term RFR rate risk should be treated as RFR rate risk
69+
label2 = "OIS";
70+
} else {
71+
label2 = periodToLabels2(irIndex->tenor());
72+
QL_REQUIRE(!label2.empty(), "Could not determine SIMM Label2 for index " << irIndex->name());
73+
}
74+
return label2;
75+
}
76+
}}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright (C) 2024 AcadiaSoft Inc.
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+
19+
/*! \file orea/simm/crifconfiguration.hpp
20+
\brief CRIF configuration interface
21+
*/
22+
23+
#pragma once
24+
25+
#include <orea/scenario/scenario.hpp>
26+
#include <orea/simm/crifrecord.hpp>
27+
#include <orea/simm/simmbucketmapper.hpp>
28+
#include <ql/indexes/interestrateindex.hpp>
29+
#include <ql/time/period.hpp>
30+
#include <string>
31+
32+
namespace ore {
33+
namespace analytics {
34+
35+
class CrifConfiguration {
36+
public:
37+
virtual ~CrifConfiguration() {};
38+
39+
//virtual bool isValidSensitivity(const ore::analytics::RiskFactorKey::KeyType& rfkey) const = 0;
40+
41+
//! Returns the SIMM configuration name
42+
virtual const std::string& name() const = 0;
43+
44+
//! Returns the SIMM configuration version
45+
virtual const std::string& version() const = 0;
46+
47+
/*! Return the CRIF <em>bucket</em> name for the given risk type \p rt
48+
and \p qualifier
49+
50+
\warning Throws an error if there are no buckets for the risk type \p rt
51+
*/
52+
virtual std::string bucket(const ore::analytics::CrifRecord::RiskType& rt, const std::string& qualifier) const = 0;
53+
54+
virtual bool hasBucketMapping(const ore::analytics::CrifRecord::RiskType& rt, const std::string& qualifier) const = 0;
55+
56+
//! Returns the SIMM bucket mapper used by the configuration
57+
virtual const boost::shared_ptr<SimmBucketMapper>& bucketMapper() const = 0;
58+
59+
/*! Return the CRIF <em>Label2</em> value for the given interest rate index
60+
\p irIndex. For interest rate indices, this is the CRIF sub curve name
61+
e.g. 'Libor1m', 'Libor3m' etc.
62+
*/
63+
virtual std::string label2(const boost::shared_ptr<QuantLib::InterestRateIndex>& irIndex) const;
64+
65+
/*! Return the CRIF <em>Label2</em> value for the given Libor tenor
66+
\p p. This is the CRIF sub curve name, e.g. 'Libor1m', 'Libor3m' etc.
67+
*/
68+
virtual std::string label2(const QuantLib::Period& p) const;
69+
};
70+
} // namespace analytics
71+
} // namespace ore

OREAnalytics/orea/simm/crifrecord.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,31 @@ CrifRecord::ProductClass parseProductClass(const string& pc) {
134134
QL_FAIL("Product class string " << pc << " does not correspond to a valid CrifRecord::ProductClass");
135135
}
136136

137+
std::ostream& operator<<(std::ostream& out, const CrifRecord::CurvatureScenario& scenario){
138+
switch(scenario){
139+
case CrifRecord::CurvatureScenario::Down:
140+
out << "CurvatureDown";
141+
break;
142+
case CrifRecord::CurvatureScenario::Up:
143+
out << "CurvatureUp";
144+
break;
145+
default:
146+
out << "";
147+
break;
148+
}
149+
return out;
150+
}
151+
152+
CrifRecord::CurvatureScenario parseFrtbCurvatureScenario(const std::string& scenario) {
153+
if (scenario == "CurvatureDown") {
154+
return CrifRecord::CurvatureScenario::Down;
155+
} else if (scenario == "CurvatureUp") {
156+
return CrifRecord::CurvatureScenario::Up;
157+
} else {
158+
return CrifRecord::CurvatureScenario::Empty;
159+
}
160+
}
161+
137162
std::vector<std::set<std::string>> CrifRecord::additionalHeaders = {};
138163

139164
ostream& operator<<(ostream& out, const CrifRecord& cr) {

OREAnalytics/orea/simm/crifrecord.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,13 @@ std::ostream& operator<<(std::ostream& out, const CrifRecord::RiskType& rt);
366366

367367
std::ostream& operator<<(std::ostream& out, const CrifRecord::ProductClass& pc);
368368

369+
std::ostream& operator<<(std::ostream& out, const CrifRecord::CurvatureScenario& scenario);
370+
369371
CrifRecord::RiskType parseRiskType(const std::string& rt);
370372

371373
CrifRecord::ProductClass parseProductClass(const std::string& pc);
372374

375+
CrifRecord::CurvatureScenario parseFrtbCurvatureScenario(const std::string& scenario);
373376

374377
} // namespace analytics
375378
} // namespace ore

OREAnalytics/orea/simm/simmbucketmapperbase.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,39 @@ namespace analytics {
4242
using RiskType = CrifRecord::RiskType;
4343

4444
// Helper variable for switching risk type before lookup
45-
const map<RiskType, RiskType> nonVolRiskTypeMap = {
46-
{RiskType::IRVol, RiskType::IRCurve}, {RiskType::InflationVol, RiskType::IRCurve},
47-
{RiskType::CreditVol, RiskType::CreditQ}, {RiskType::CreditVolNonQ, RiskType::CreditNonQ},
48-
{RiskType::EquityVol, RiskType::Equity}, {RiskType::CommodityVol, RiskType::Commodity}};
45+
const 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+
{RiskType::CSR_NS_VEGA, RiskType::CSR_NS_DELTA},
52+
{RiskType::CSR_SC_VEGA, RiskType::CSR_SC_DELTA},
53+
{RiskType::CSR_SNC_VEGA, RiskType::CSR_SNC_DELTA},
54+
{RiskType::EQ_VEGA, RiskType::EQ_DELTA},
55+
{RiskType::COMM_VEGA, RiskType::COMM_DELTA},
56+
{RiskType::GIRR_CURV, RiskType::GIRR_DELTA},
57+
{RiskType::CSR_NS_CURV, RiskType::CSR_NS_DELTA},
58+
{RiskType::CSR_SC_CURV, RiskType::CSR_SC_VEGA},
59+
{RiskType::CSR_SNC_CURV, RiskType::CSR_SNC_DELTA},
60+
{RiskType::EQ_CURV, RiskType::EQ_DELTA},
61+
{RiskType::COMM_CURV, RiskType::COMM_DELTA},
62+
{RiskType::FX_CURV, RiskType::FX_DELTA}};
4963

5064
SimmBucketMapperBase::SimmBucketMapperBase(
5165
const boost::shared_ptr<ore::data::ReferenceDataManager>& refDataManager,
5266
const boost::shared_ptr<SimmBasicNameMapper>& nameMapper) :
5367
refDataManager_(refDataManager), nameMapper_(nameMapper){
5468

5569
// Fill the set of risk types that have buckets
56-
rtWithBuckets_ = {RiskType::IRCurve, RiskType::CreditQ, RiskType::CreditNonQ, RiskType::Equity,
57-
RiskType::Commodity, RiskType::IRVol, RiskType::InflationVol, RiskType::CreditVol,
58-
RiskType::CreditVolNonQ, RiskType::EquityVol, RiskType::CommodityVol};
70+
rtWithBuckets_ = {RiskType::IRCurve, RiskType::CreditQ, RiskType::CreditNonQ, RiskType::Equity,
71+
RiskType::Commodity, RiskType::IRVol, RiskType::InflationVol, RiskType::CreditVol,
72+
RiskType::CreditVolNonQ, RiskType::EquityVol, RiskType::CommodityVol, RiskType::GIRR_DELTA,
73+
RiskType::GIRR_CURV, RiskType::CSR_NS_DELTA, RiskType::CSR_NS_VEGA, RiskType::CSR_NS_CURV,
74+
RiskType::COMM_DELTA, RiskType::COMM_VEGA, RiskType::COMM_CURV, RiskType::CSR_SC_CURV,
75+
RiskType::CSR_SC_DELTA, RiskType::CSR_SC_VEGA, RiskType::CSR_SNC_CURV, RiskType::CSR_SNC_DELTA,
76+
RiskType::CSR_SNC_VEGA, RiskType::CSR_SNC_CURV, RiskType::EQ_CURV, RiskType::EQ_DELTA,
77+
RiskType::EQ_VEGA, RiskType::FX_CURV, RiskType::FX_DELTA};
5978
}
6079

6180
std::string BucketMapping::name() const {
@@ -94,7 +113,7 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
94113
}
95114

96115
// Deal with RiskType::IRCurve
97-
if (lookupRiskType == RiskType::IRCurve) {
116+
if (lookupRiskType == RiskType::IRCurve || lookupRiskType == RiskType::GIRR_DELTA) {
98117
return irBucket(qualifier);
99118
}
100119

@@ -105,12 +124,12 @@ string SimmBucketMapperBase::bucket(const RiskType& riskType, const string& qual
105124
bool haveFallback = has(lookupRiskType, lookupName, true);
106125
bool noBucket = !haveMapping && !haveFallback;
107126

108-
if (noBucket && nameMapper_ != nullptr && lookupRiskType == RiskType::Equity) {
127+
if (noBucket && nameMapper_ != nullptr &&
128+
(lookupRiskType == RiskType::Equity || lookupRiskType == RiskType::EQ_DELTA)) {
109129
// if we have a simm name mapping we do a reverse lookup on the name as the CRIF qualifier isn't in reference data
110130
lookupName = nameMapper_->externalName(qualifier);
111131
haveMapping = has(lookupRiskType, lookupName, false);
112-
haveFallback = has(lookupRiskType, lookupName, true);
113-
noBucket = !haveMapping && ! haveFallback;
132+
noBucket = !haveMapping && !haveFallback;
114133
}
115134

116135
// Do some checks and return the lookup

OREAnalytics/orea/simm/simmconfiguration.hpp

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,16 @@
2727
#include <vector>
2828

2929
#include <boost/optional.hpp>
30-
30+
#include <orea/simm/crifconfiguration.hpp>
3131
#include <orea/simm/crifrecord.hpp>
32-
#include <orea/simm/simmbucketmapper.hpp>
3332
#include <ql/indexes/interestrateindex.hpp>
3433
#include <ql/types.hpp>
3534

3635
namespace ore {
3736
namespace analytics {
3837

3938
//! Abstract base class defining the interface for a SIMM configuration
40-
class SimmConfiguration {
39+
class SimmConfiguration : public CrifConfiguration {
4140
public:
4241
virtual ~SimmConfiguration() {}
4342

@@ -127,28 +126,18 @@ class SimmConfiguration {
127126
return (less_than(pc1, pc2) ? pc2 : pc1);
128127
}
129128

130-
//! Returns the SIMM configuration name
131-
virtual const std::string& name() const = 0;
132-
133-
//! Returns the SIMM configuration version
134-
virtual const std::string& version() const = 0;
135-
136-
//! Returns the SIMM bucket mapper used by the configuration
137-
virtual const boost::shared_ptr<SimmBucketMapper>& bucketMapper() const = 0;
138-
139129
//! Return the SIMM <em>bucket</em> names for the given risk type \p rt
140130
//! An empty vector is returned if the risk type has no buckets
141131
virtual std::vector<std::string> buckets(const CrifRecord::RiskType& rt) const = 0;
142132

143133
//! Return true if the SIMM risk type \p rt has buckets
144134
virtual bool hasBuckets(const CrifRecord::RiskType& rt) const = 0;
145135

146-
/*! Return the SIMM <em>bucket</em> name for the given risk type \p rt
147-
and \p qualifier
136+
//! Return true if the SIMM risk type \p rt has buckets
137+
bool hasBucketMapping(const CrifRecord::RiskType& rt, const std::string& qualifier) const override {
138+
return bucketMapper()->has(rt, qualifier);
139+
}
148140

149-
\warning Throws an error if there are no buckets for the risk type \p rt
150-
*/
151-
virtual std::string bucket(const CrifRecord::RiskType& rt, const std::string& qualifier) const = 0;
152141

153142
//! Return the list of SIMM <em>Label1</em> values for risk type \p rt
154143
//! An empty vector is returned if the risk type does not use <em>Label1</em>
@@ -158,17 +147,6 @@ class SimmConfiguration {
158147
//! An empty vector is returned if the risk type does not use <em>Label2</em>
159148
virtual std::vector<std::string> labels2(const CrifRecord::RiskType& rt) const = 0;
160149

161-
/*! Return the SIMM <em>Label2</em> value for the given interest rate index
162-
\p irIndex. For interest rate indices, this is the SIMM sub curve name
163-
e.g. 'Libor1m', 'Libor3m' etc.
164-
*/
165-
virtual std::string labels2(const boost::shared_ptr<QuantLib::InterestRateIndex>& irIndex) const = 0;
166-
167-
/*! Return the SIMM <em>Label2</em> value for the given Libor tenor
168-
\p p. This is the SIMM sub curve name, e.g. 'Libor1m', 'Libor3m' etc.
169-
*/
170-
virtual std::string labels2(const QuantLib::Period& p) const = 0;
171-
172150
/*! Add SIMM <em>Label2</em> values under certain circumstances.
173151
174152
For example, in v338 and later, CreditQ label2 should have the payment

0 commit comments

Comments
 (0)