Skip to content

Commit e1d4ecf

Browse files
committed
Merge branch 'feature/QPR-13609' into 'master'
QPR-13609 HW n-factor historical calibration Closes QPR-13609 See merge request qs/oreplus!3037
2 parents 8e20c85 + 6770add commit e1d4ecf

22 files changed

Lines changed: 1934 additions & 272 deletions

OREAnalytics/orea/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ app/analytics/xvastressanalytic.cpp
5151
app/analytics/zerotoparshiftanalytic.cpp
5252
app/analyticsmanager.cpp
5353
app/cleanupsingletons.cpp
54+
app/hwhistoricalcalibrationdataloader.cpp
5455
app/initbuilders.cpp
5556
app/inputparameters.cpp
5657
app/marketcalibrationreport.cpp
@@ -264,6 +265,7 @@ app/analytics/zerotoparshiftanalytic.hpp
264265
app/analyticsmanager.hpp
265266
app/cleanupsingletons.hpp
266267
app/dummymarketdataloader.hpp
268+
app/hwhistoricalcalibrationdataloader.hpp
267269
app/initbuilders.hpp
268270
app/inputparameters.hpp
269271
app/marketcalibrationreport.hpp

OREAnalytics/orea/app/analytics/calibrationanalytic.cpp

Lines changed: 364 additions & 244 deletions
Large diffs are not rendered by default.

OREAnalytics/orea/app/analytics/calibrationanalytic.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
#include <orea/app/analytic.hpp>
2626

27+
#include <ored/model/hwhistoricalcalibrationmodelbuilder.hpp>
28+
2729
namespace ore {
2830
namespace analytics {
2931

@@ -42,10 +44,14 @@ class CalibrationAnalyticImpl : public Analytic::Impl {
4244
protected:
4345
QuantLib::ext::shared_ptr<ore::data::EngineFactory> engineFactory() override;
4446
void buildCrossAssetModel(bool continueOnError, bool allowModelFallbacks);
47+
//void buildHwHistoricalCalibrationModel(bool continueOnError);
48+
void buildHwHistoricalCalibrationModelData();
4549

4650
QuantLib::ext::shared_ptr<EngineFactory> engineFactory_;
4751
QuantLib::ext::shared_ptr<CrossAssetModel> model_;
4852
QuantLib::ext::shared_ptr<CrossAssetModelBuilder> builder_;
53+
QuantLib::ext::shared_ptr<HwHistoricalCalibrationModelData> hwHistoricalModelData_;
54+
QuantLib::ext::shared_ptr<HwHistoricalCalibrationModelBuilder> hwHistoricalModelBuilder_;
4955
};
5056

5157
static const std::set<std::string> calibrationAnalyticSubAnalytics{};
@@ -56,6 +62,7 @@ class CalibrationAnalytic : public Analytic {
5662
const QuantLib::ext::weak_ptr<ore::analytics::AnalyticsManager>& analyticsManager)
5763
: Analytic(std::make_unique<CalibrationAnalyticImpl>(inputs), calibrationAnalyticSubAnalytics, inputs, analyticsManager, false,
5864
false, false, false) {}
65+
bool requiresMarketData() const override;
5966
};
6067

6168
} // namespace analytics

OREAnalytics/orea/app/analyticsmanager.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ void AnalyticsManager::runAnalytics(
9898
return;
9999

100100
std::vector<QuantLib::ext::shared_ptr<ore::data::TodaysMarketParameters>> tmps = todaysMarketParams();
101-
102101
// load the market data if at least one analytic requires that and we have non-empty tmps
103102
if (std::any_of(analytics_.begin(), analytics_.end(),
104103
[](const std::pair<std::string, QuantLib::ext::shared_ptr<Analytic>>& a) {
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
Copyright (C) 2023 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+
19+
#include <orea/app/hwhistoricalcalibrationdataloader.hpp>
20+
#include <orea/scenario/scenariofilereader.hpp>
21+
#include <orea/scenario/simplescenariofactory.hpp>
22+
#include <orea/scenario/scenarioloader.hpp>
23+
24+
#include <ored/utilities/csvfilereader.hpp>
25+
#include <ored/utilities/parsers.hpp>
26+
#include <ored/utilities/indexparser.hpp>
27+
#include <ored/utilities/log.hpp>
28+
29+
#include <sstream>
30+
#include <vector>
31+
#include <map>
32+
#include <string>
33+
34+
namespace ore {
35+
namespace analytics {
36+
37+
using namespace QuantLib;
38+
using namespace ore::data;
39+
40+
HwHistoricalCalibrationDataLoader::HwHistoricalCalibrationDataLoader(const std::string& baseCurrency,
41+
const std::vector<std::string>& foreignCurrency,
42+
const std::vector<Period>& curveTenors,
43+
const Date& startDate, const Date& endDate)
44+
: baseCurrency_(baseCurrency), foreignCurrency_(foreignCurrency), tenors_(curveTenors), startDate_(startDate),
45+
endDate_(endDate) {}
46+
47+
void HwHistoricalCalibrationDataLoader::loadFromScenarioFile(const std::string& fileName) {
48+
LOG("Load Historical time series data from scenario file " << fileName);
49+
50+
ext::shared_ptr<SimpleScenarioFactory> scenarioFactory = ext::make_shared<SimpleScenarioFactory>(false);
51+
ext::shared_ptr<ScenarioFileReader> scenarioReader =
52+
ext::make_shared<ScenarioFileReader>(fileName, scenarioFactory);
53+
ext::shared_ptr<HistoricalScenarioLoader> historicalScenarioLoader =
54+
ext::make_shared<HistoricalScenarioLoader>(scenarioReader, startDate_, endDate_, NullCalendar());
55+
56+
QL_REQUIRE(historicalScenarioLoader->scenarios().size() == 1,
57+
"Only one scenario allowed for HW historical calibration.");
58+
std::map<Date, ext::shared_ptr<Scenario>> scenarioMap = historicalScenarioLoader->scenarios()[0];
59+
QL_REQUIRE(scenarioMap.size() > 0, "Scenario file is empty.");
60+
const auto& keys = scenarioMap.begin()->second->keys();
61+
for (const auto& [date, scenario] : scenarioMap) {
62+
for (const auto& key : keys) {
63+
if (key.keytype == RiskFactorKey::KeyType::IndexCurve) {
64+
std::string ccy = parseCurrency(key.name);
65+
if (ccy == baseCurrency_ ||
66+
std::find(foreignCurrency_.begin(), foreignCurrency_.end(), ccy) != foreignCurrency_.end()) {
67+
loadIr(key.name, key.index, date, scenario->get(key));
68+
}
69+
} else if (key.keytype == RiskFactorKey::KeyType::FXSpot) {
70+
for (const auto& foreignCcy : foreignCurrency_) {
71+
std::string pair1 = foreignCcy + baseCurrency_;
72+
std::string pair2 = baseCurrency_ + foreignCcy;
73+
Real fxRate;
74+
if (key.name == pair1)
75+
fxRate = scenario->get(key);
76+
else if (key.name == pair2)
77+
fxRate = 1.0 / scenario->get(key);
78+
else
79+
continue;
80+
loadFx(pair1, date, fxRate);
81+
break;
82+
}
83+
}
84+
}
85+
}
86+
cleanData();
87+
}
88+
89+
void HwHistoricalCalibrationDataLoader::loadPCAFromCsv(const std::vector<std::string>& fileNames) {
90+
for (const auto& fileName : fileNames) {
91+
LOG("Load PCA data from file " << fileName);
92+
CSVFileReader dataReader(fileName, false);
93+
dataReader.next();
94+
QL_REQUIRE(dataReader.numberOfColumns() == tenors_.size() + 1,
95+
"Number of columns in pca file must be number of tenor + 1.");
96+
QL_REQUIRE(dataReader.get(0) == "EigenValue", "EigenValue column must be the first column in the data file.");
97+
QL_REQUIRE(dataReader.get(1) == "EigenVector", "EigenVector column must exist in the data file.");
98+
QL_REQUIRE(dataReader.get(2) == "Currency", "Currency column must exist in the data file.");
99+
std::string ccy = dataReader.get(3);
100+
Size lines = 0;
101+
// std::vector<std::string> remove_chars = { "\"", "[", "]", " " };
102+
vector<Real> eig_val;
103+
vector<vector<Real>> eig_vec;
104+
while (dataReader.next()) {
105+
++lines;
106+
vector<Real> eig_vec_i;
107+
eig_val.push_back(parseReal(dataReader.get(0)));
108+
for (Size i = 0; i < tenors_.size(); i++) {
109+
eig_vec_i.push_back(parseReal(dataReader.get(i + 1)));
110+
}
111+
eig_vec.push_back(eig_vec_i);
112+
}
113+
Array eigenValue = Array(lines, 0.0);
114+
Matrix eigenVector = Matrix(lines, tenors_.size(), 0.0);
115+
for (Size i = 0; i < lines; i++) {
116+
eigenValue[i] = eig_val[i];
117+
for (Size j = 0; j < tenors_.size(); j++) {
118+
eigenVector[i][j] = eig_vec[i][j];
119+
}
120+
}
121+
loadEigenValue(ccy, eigenValue);
122+
loadEigenVector(ccy, eigenVector);
123+
}
124+
}
125+
126+
void HwHistoricalCalibrationDataLoader::loadIr(const std::string& curveId, const Size& index, const Date& d,
127+
const Real& df) {
128+
std::map<Date, std::vector<Real>>& dateMap = irCurves_[curveId];
129+
std::vector<Real>& discountFactors = dateMap[d];
130+
if (discountFactors.empty()) {
131+
discountFactors.resize(tenors_.size(), 0.0);
132+
}
133+
QL_REQUIRE(index < tenors_.size(), "Tenor index " << index << " out of range for curve " << curveId << " on date "
134+
<< d << " (max: " << tenors_.size() << ")");
135+
discountFactors[index] = df;
136+
}
137+
138+
void HwHistoricalCalibrationDataLoader::loadFx(const std::string& curveId, const Date& d, const Real& fxSpot) {
139+
// Check if the date is within the start and end date specified in ore.xml
140+
if (fxSpots_.find(curveId) != fxSpots_.end()) {
141+
if (fxSpots_[curveId].find(d) != fxSpots_[curveId].end()) {
142+
ALOG("Encounter duplicated records for curveId " << curveId << ", date " << d << " in the input file.");
143+
return;
144+
}
145+
}
146+
fxSpots_[curveId][d] = fxSpot;
147+
}
148+
149+
void HwHistoricalCalibrationDataLoader::loadEigenValue(const std::string& ccy, const Array& eigenValue) {
150+
eigenValue_[ccy] = eigenValue;
151+
principalComponent_[ccy] = eigenValue.size();
152+
}
153+
154+
void HwHistoricalCalibrationDataLoader::loadEigenVector(const std::string& ccy, const Matrix& eigenVector) {
155+
eigenVector_[ccy] = transpose(eigenVector);
156+
}
157+
158+
void HwHistoricalCalibrationDataLoader::cleanData() {
159+
// Check if all required currency exist in irCurves_
160+
std::vector<std::string> requiredCcy = foreignCurrency_;
161+
requiredCcy.push_back(baseCurrency_);
162+
163+
for (const auto& [curveId, dataMap] : irCurves_) {
164+
auto position = std::find(requiredCcy.begin(), requiredCcy.end(), parseCurrency(curveId));
165+
if (position != requiredCcy.end()) {
166+
requiredCcy.erase(position);
167+
}
168+
}
169+
170+
std::string missingCcy;
171+
for (auto& c : requiredCcy)
172+
missingCcy += c + " ";
173+
QL_REQUIRE(requiredCcy.size() == 0, "Discount factor for " << missingCcy << "are not found in input file.");
174+
175+
// Check if all required fx spot exist in fxSpots
176+
std::vector<std::string> missingFxPairs;
177+
for (const auto& foreignCcy : foreignCurrency_) {
178+
std::string expectedPair = foreignCcy + baseCurrency_;
179+
180+
if (fxSpots_.find(expectedPair) == fxSpots_.end()) {
181+
missingFxPairs.push_back(expectedPair);
182+
}
183+
}
184+
missingCcy = "";
185+
for (auto& c : missingFxPairs)
186+
missingCcy += c + " ";
187+
QL_REQUIRE(missingFxPairs.size() == 0, "FX spot for " << missingCcy << "are not found in input file.");
188+
}
189+
190+
std::string HwHistoricalCalibrationDataLoader::parseCurrency(const std::string& curveId) {
191+
vector<string> tokens;
192+
split(tokens, curveId, boost::is_any_of("-"));
193+
QL_REQUIRE(tokens.size() == 2 || tokens.size() == 3,
194+
"Two or three tokens required in " << curveId << ": CCY-INDEX or CCY-INDEX-TERM");
195+
return tokens[0];
196+
}
197+
198+
} // namespace analytics
199+
} // namespace ore
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright (C) 2023 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+
19+
/*! \file orea/app/hwhistoricalcalibrationdataloader.hpp
20+
\brief Class for loading required data for Hull-White historical calibration
21+
\ingroup app
22+
*/
23+
24+
#include <ql/time/date.hpp>
25+
#include <ql/types.hpp>
26+
#include <ql/math/matrix.hpp>
27+
#include <ql/math/array.hpp>
28+
29+
#include <string>
30+
#include <vector>
31+
#include <map>
32+
33+
#pragma once
34+
35+
namespace ore {
36+
namespace analytics {
37+
38+
// Loader class (curve discount factors only)
39+
class HwHistoricalCalibrationDataLoader {
40+
public:
41+
HwHistoricalCalibrationDataLoader(const std::string& baseCurrency, const std::vector<std::string>& foreignCurrency,
42+
const std::vector<QuantLib::Period>& curveTenors,
43+
const QuantLib::Date& startDate = QuantLib::Date(),
44+
const QuantLib::Date& endDate = QuantLib::Date());
45+
46+
void loadFromScenarioFile(const std::string& fileName);
47+
void loadPCAFromCsv(const std::vector<std::string>& fileNames);
48+
49+
// Getters
50+
const std::map<std::string, std::map<QuantLib::Date, std::vector<QuantLib::Real>>>& getIrCurves() const { return irCurves_; }
51+
const std::map<std::string, std::map<QuantLib::Date, QuantLib::Real>>& getFxSpots() const { return fxSpots_; }
52+
const std::map<std::string, QuantLib::Array>& getEigenValue() const { return eigenValue_; }
53+
const std::map<std::string, QuantLib::Matrix>& getEigenVector() const { return eigenVector_; }
54+
55+
// Move
56+
std::map<std::string, std::map<QuantLib::Date, std::vector<QuantLib::Real>>> moveIrCurves() { return std::move(irCurves_); }
57+
std::map<std::string, std::map<QuantLib::Date, QuantLib::Real>> moveFxSpots() { return std::move(fxSpots_); }
58+
std::map<std::string, QuantLib::Size> movePrincipalComponent() { return std::move(principalComponent_); }
59+
std::map<std::string, QuantLib::Array> moveEigenValue() { return std::move(eigenValue_); }
60+
std::map<std::string, QuantLib::Matrix> moveEigenVector() { return std::move(eigenVector_); }
61+
62+
private:
63+
void loadIr(const std::string& curveId, const QuantLib::Size& index, const QuantLib::Date& d, const QuantLib::Real& df);
64+
void loadFx(const std::string& curveId, const QuantLib::Date& d, const QuantLib::Real& fxSpot);
65+
void loadEigenValue(const std::string& ccy, const QuantLib::Array& eigenValue);
66+
void loadEigenVector(const std::string& ccy, const QuantLib::Matrix& eigenVector);
67+
void cleanData();
68+
69+
// Helper
70+
std::string parseCurrency(const std::string& curveId);
71+
72+
std::string baseCurrency_;
73+
std::vector<std::string> foreignCurrency_;
74+
std::vector<QuantLib::Period> tenors_;
75+
QuantLib::Date startDate_, endDate_;
76+
std::map<std::string, std::map<QuantLib::Date, std::vector<QuantLib::Real>>> irCurves_;
77+
std::map<std::string, std::map<QuantLib::Date, QuantLib::Real>> fxSpots_;
78+
std::map<std::string, QuantLib::Size> principalComponent_;
79+
std::map<std::string, QuantLib::Array> eigenValue_;
80+
std::map<std::string, QuantLib::Matrix> eigenVector_;
81+
};
82+
83+
} // namespace analytics
84+
} // namespace ore

0 commit comments

Comments
 (0)