1717*/
1818
1919#include < orea/app/hwhistoricalcalibrationdataloader.hpp>
20+ #include < orea/scenario/scenariofilereader.hpp>
21+ #include < orea/scenario/simplescenariofactory.hpp>
22+ #include < orea/scenario/scenarioloader.hpp>
2023
2124#include < ored/utilities/csvfilereader.hpp>
2225#include < ored/utilities/parsers.hpp>
26+ #include < ored/utilities/indexparser.hpp>
2327#include < ored/utilities/log.hpp>
2428
2529#include < sstream>
@@ -41,35 +45,46 @@ HwHistoricalCalibrationDataLoader::HwHistoricalCalibrationDataLoader(const std::
4145 : baseCurrency_(baseCurrency), foreignCurrency_(foreignCurrency), tenors_(curveTenors), startDate_(startDate),
4246 endDate_ (endDate) {}
4347
44- void HwHistoricalCalibrationDataLoader::loadHistoricalCurveDataFromCsv (const std::string& fileName) {
45- LOG (" Load Historical time series data from file " << fileName);
46- CSVFileReader dataReader (fileName, true );
47- QL_REQUIRE (dataReader.hasField (" date" ), " Date column is not found in the time series data file." );
48- QL_REQUIRE (dataReader.hasField (" curve" ), " Curve column is not found in the time series data file." );
49- QL_REQUIRE (dataReader.hasField (" data" ), " Data column is not found in the time series data file." );
50- Size lines = 0 ;
51- std::vector<std::string> remove_chars = {" \" " , " [" , " ]" , " " };
52- while (dataReader.next ()) {
53- ++lines;
54- Date d = parseDate (dataReader.get (" date" ));
55- string curveId = dataReader.get (" curve" );
56- string dataStr = dataReader.get (" data" );
57- // Remove all irrelavent chars in the data string
58- for (Size i = 0 ; i < remove_chars.size (); i++) {
59- boost::erase_all (dataStr, remove_chars[i]);
60- }
61- std::vector<std::string> tokens;
62- boost::split (tokens, dataStr, boost::is_any_of (" ,;\t " ), boost::token_compress_off);
63- if (tokens.size () != tenors_.size ()) {
64- ALOG (" Number of discount rates in line " << lines << " is not the same as tenors given." );
65- continue ;
66- }
67- std::vector<Real> discountFactor;
68- for (Size i = 0 ; i < tenors_.size (); i++) {
69- discountFactor.push_back (parseReal (tokens[i]));
48+ void HwHistoricalCalibrationDataLoader::loadFromScenarioFile (const std::string& fileName) {
49+ LOG (" Load Historical time series data from scenario file " << fileName);
50+
51+ ext::shared_ptr<SimpleScenarioFactory> scenarioFactory = ext::make_shared<SimpleScenarioFactory>(false );
52+ ext::shared_ptr<ScenarioFileReader> scenarioReader =
53+ ext::make_shared<ScenarioFileReader>(fileName, scenarioFactory);
54+ ext::shared_ptr<HistoricalScenarioLoader> historicalScenarioLoader =
55+ ext::make_shared<HistoricalScenarioLoader>(scenarioReader, startDate_, endDate_, NullCalendar ());
56+
57+ QL_REQUIRE (historicalScenarioLoader->scenarios ().size () == 1 ,
58+ " Only one scenario allowed for HW historical calibration." );
59+ std::map<Date, ext::shared_ptr<Scenario>> scenarioMap = historicalScenarioLoader->scenarios ()[0 ];
60+ QL_REQUIRE (scenarioMap.size () > 0 , " Scenario file is empty." );
61+ const auto & keys = scenarioMap.begin ()->second ->keys ();
62+ for (const auto & [date, scenario] : scenarioMap) {
63+ for (const auto & key : keys) {
64+ if (key.keytype == RiskFactorKey::KeyType::IndexCurve) {
65+ std::string ccy = parseCurrency (key.name );
66+ if (ccy == baseCurrency_ ||
67+ std::find (foreignCurrency_.begin (), foreignCurrency_.end (), ccy) != foreignCurrency_.end ()) {
68+ loadIr (key.name , key.index , date, scenario->get (key));
69+ }
70+ } else if (key.keytype == RiskFactorKey::KeyType::FXSpot) {
71+ for (const auto & foreignCcy : foreignCurrency_) {
72+ std::string pair1 = foreignCcy + baseCurrency_;
73+ std::string pair2 = baseCurrency_ + foreignCcy;
74+ Real fxRate;
75+ if (key.name == pair1)
76+ fxRate = scenario->get (key);
77+ else if (key.name == pair2)
78+ fxRate = 1.0 / scenario->get (key);
79+ else
80+ continue ;
81+ loadFx (pair1, date, fxRate);
82+ break ;
83+ }
84+ }
7085 }
71- loadIr (curveId, d, discountFactor);
7286 }
87+ cleanData ();
7388}
7489
7590void HwHistoricalCalibrationDataLoader::loadPCAFromCsv (const std::vector<std::string>& fileNames) {
@@ -109,44 +124,20 @@ void HwHistoricalCalibrationDataLoader::loadPCAFromCsv(const std::vector<std::st
109124 }
110125}
111126
112- void HwHistoricalCalibrationDataLoader::loadFixings (const std::string& fileName) {
113- LOG (" Load fixings from file " << fileName);
114- CSVFileReader dataReader (fileName, false );
115- dataReader.next ();
116- QL_REQUIRE (dataReader.numberOfColumns () == 3 , " Number of columns in fixings file must be 3." );
117- Size lines = 0 ;
118- // std::vector<std::string> remove_chars = { "\"", "[", "]", " " };
119- while (dataReader.next ()) {
120- ++lines;
121- Date d = parseDate (dataReader.get (0 ));
122- string curveId = dataReader.get (1 );
123- Real dataReal = parseReal (dataReader.get (2 ));
124- std::vector<std::string> tokens;
125- boost::split (tokens, curveId, boost::is_any_of (" -" ), boost::token_compress_off);
126- if (tokens[0 ] == " FX" )
127- loadFx (curveId, d, dataReal);
128- }
129- }
130-
131- void HwHistoricalCalibrationDataLoader::loadIr (const std::string& curveId, const Date& d, const std::vector<Real>& df) {
132- // Check if the date is within the start and end date specified in ore.xml
133- if (!(d >= startDate_ && d <= endDate_)) {
134- return ;
135- }
136- if (irCurves_.find (curveId) != irCurves_.end ()) {
137- if (irCurves_[curveId].find (d) != irCurves_[curveId].end ()) {
138- ALOG (" Encounter duplicated records for curveId " << curveId << " , date " << d << " in the input file." );
139- return ;
140- }
127+ void HwHistoricalCalibrationDataLoader::loadIr (const std::string& curveId, const Size& index, const Date& d,
128+ const Real& df) {
129+ std::map<Date, std::vector<Real>>& dateMap = irCurves_[curveId];
130+ std::vector<Real>& discountFactors = dateMap[d];
131+ if (discountFactors.empty ()) {
132+ discountFactors.resize (tenors_.size (), 0.0 );
141133 }
142- irCurves_[curveId][d] = df;
134+ QL_REQUIRE (index < tenors_.size (), " Tenor index " << index << " out of range for curve " << curveId << " on date "
135+ << d << " (max: " << tenors_.size () << " )" );
136+ discountFactors[index] = df;
143137}
144138
145139void HwHistoricalCalibrationDataLoader::loadFx (const std::string& curveId, const Date& d, const Real& fxSpot) {
146140 // Check if the date is within the start and end date specified in ore.xml
147- if (!(d >= startDate_ && d <= endDate_)) {
148- return ;
149- }
150141 if (fxSpots_.find (curveId) != fxSpots_.end ()) {
151142 if (fxSpots_[curveId].find (d) != fxSpots_[curveId].end ()) {
152143 ALOG (" Encounter duplicated records for curveId " << curveId << " , date " << d << " in the input file." );
@@ -169,167 +160,40 @@ void HwHistoricalCalibrationDataLoader::cleanData() {
169160 // Check if all required currency exist in irCurves_
170161 std::vector<std::string> requiredCcy = foreignCurrency_;
171162 requiredCcy.push_back (baseCurrency_);
172- std::vector<string> erase_list;
173- for (auto const & outer : irCurves_) {
174- std::vector<std::string> tokens;
175- boost::split (tokens, outer.first , boost::is_any_of (" /-" ), boost::token_compress_off);
176- QL_REQUIRE (tokens.size () >= 2 , " Curve should be in the format of IndexCurve/CCY-NAME-TENOR or IndexCurve/CCY" );
177- auto position = std::find (requiredCcy.begin (), requiredCcy.end (), tokens[1 ]);
178- if (position == requiredCcy.end ()) {
179- erase_list.push_back (outer.first );
180- } else {
163+
164+ for (const auto & [curveId, dataMap] : irCurves_) {
165+ auto position = std::find (requiredCcy.begin (), requiredCcy.end (), parseCurrency (curveId));
166+ if (position != requiredCcy.end ()) {
181167 requiredCcy.erase (position);
182- // Change map key to currency code
183- auto handler = irCurves_.extract (outer.first );
184- handler.key () = tokens[1 ];
185- irCurves_.insert (std::move (handler));
186168 }
187169 }
188- // Remove all the currencies that are not needed
189- for (auto const & index : erase_list) {
190- irCurves_.erase (index);
191- }
170+
192171 std::string missingCcy;
193172 for (auto & c : requiredCcy)
194173 missingCcy += c + " " ;
195174 QL_REQUIRE (requiredCcy.size () == 0 , " Discount factor for " << missingCcy << " are not found in input file." );
196175
197176 // Check if all required fx spot exist in fxSpots
198- requiredCcy = foreignCurrency_;
199- erase_list.clear ();
200- for (auto const & outer : fxSpots_) {
201- // Extract currency pair name
202- std::vector<std::string> tokens;
203- boost::split (tokens, outer.first , boost::is_any_of (" /-" ), boost::token_compress_off);
204- QL_REQUIRE (tokens.size () >= 4 , " Curve should be in the format of FX-NAME-CCY1-CCY2" );
205- string ccyPair;
206- if (tokens[2 ] == baseCurrency_) {
207- auto position = std::find (requiredCcy.begin (), requiredCcy.end (), tokens[3 ]);
208- if (position != requiredCcy.end ()) {
209- ccyPair = tokens[3 ] + tokens[2 ];
210- // Inverse the fx spot rates
211- for (const auto & inner : outer.second )
212- fxSpots_[outer.first ][inner.first ] = 1.0 / inner.second ;
213- requiredCcy.erase (position);
214- } else {
215- erase_list.push_back (outer.first );
216- continue ;
217- }
218- } else if (tokens[3 ] == baseCurrency_) {
219- auto position = std::find (requiredCcy.begin (), requiredCcy.end (), tokens[2 ]);
220- if (position != requiredCcy.end ()) {
221- ccyPair = tokens[2 ] + tokens[3 ];
222- requiredCcy.erase (position);
223- } else {
224- erase_list.push_back (outer.first );
225- continue ;
226- }
227- } else {
228- erase_list.push_back (outer.first );
229- continue ;
230- }
231- // Change map key to currency pair code
232- auto handler = fxSpots_.extract (outer.first );
233- handler.key () = ccyPair;
234- fxSpots_.insert (std::move (handler));
235- }
236- // Remove all the currencies that are not needed
237- for (auto const & index : erase_list) {
238- fxSpots_.erase (index);
239- }
240- missingCcy = " " ;
241- for (auto & c : requiredCcy)
242- missingCcy += c + " -" + baseCurrency_ + " " ;
243- QL_REQUIRE (requiredCcy.size () == 0 , " FX spot for " << missingCcy << " are not found in input file." );
244-
245- // Master date set
246- std::set<Date> masterDates;
247- for (auto const & outer : fxSpots_)
248- for (auto const & inner : outer.second )
249- masterDates.insert (inner.first );
250- for (auto const & outer : irCurves_)
251- for (auto const & inner : outer.second )
252- masterDates.insert (inner.first );
177+ std::vector<std::string> missingFxPairs;
178+ for (const auto & foreignCcy : foreignCurrency_) {
179+ std::string expectedPair = foreignCcy + baseCurrency_;
253180
254- // Forward-fill FX and IR data
255- for (auto & outer : fxSpots_) {
256- if (outer.second .empty ())
257- continue ;
258- Real first = outer.second .begin ()->second ;
259- Real last = first;
260-
261- // Collect missing dates first, then insert
262- std::vector<std::pair<Date, Real>> missingDates;
263- missingDates.reserve (masterDates.size ());
264-
265- auto masterIt = masterDates.begin ();
266- auto dataIt = outer.second .begin ();
267-
268- while (masterIt != masterDates.end ()) {
269- const Date& masterDate = *masterIt;
270- while (dataIt != outer.second .end () && dataIt->first < masterDate) {
271- last = dataIt->second ;
272- ++dataIt;
273- }
274- if (dataIt == outer.second .end () || dataIt->first != masterDate) {
275- Real fillValue = (masterDate < outer.second .begin ()->first ? first : last);
276- missingDates.emplace_back (masterDate, fillValue);
277- } else {
278- last = dataIt->second ;
279- ++dataIt;
280- }
281- ++masterIt;
282- }
283-
284- for (const auto & [date, value] : missingDates) {
285- outer.second [date] = value;
286- LOG (" Add missing fx spot rate on date " << date << " , Currency: " << outer.first << " , Value: " << value);
181+ if (fxSpots_.find (expectedPair) == fxSpots_.end ()) {
182+ missingFxPairs.push_back (expectedPair);
287183 }
288184 }
289- for (auto & outer : irCurves_) {
290- if (outer.second .empty ())
291- continue ;
292- std::vector<Real> first = outer.second .begin ()->second ;
293- std::vector<Real> last = first;
294-
295- // Collect missing dates first, then insert
296- std::vector<std::pair<Date, std::vector<Real>>> missingDates;
297- missingDates.reserve (masterDates.size ());
298-
299- auto masterIt = masterDates.begin ();
300- auto dataIt = outer.second .begin ();
301-
302- while (masterIt != masterDates.end ()) {
303- const Date& masterDate = *masterIt;
304-
305- while (dataIt != outer.second .end () && dataIt->first < masterDate) {
306- last = dataIt->second ;
307- ++dataIt;
308- }
309-
310- if (dataIt == outer.second .end () || dataIt->first != masterDate) {
311- std::vector<Real> fillValue = (masterDate < outer.second .begin ()->first ? first : last);
312- missingDates.emplace_back (masterDate, fillValue);
313- } else {
314- last = dataIt->second ;
315- ++dataIt;
316- }
317-
318- ++masterIt;
319- }
185+ missingCcy = " " ;
186+ for (auto & c : missingFxPairs)
187+ missingCcy += c + " " ;
188+ QL_REQUIRE (missingFxPairs.size () == 0 , " FX spot for " << missingCcy << " are not found in input file." );
189+ }
320190
321- for (const auto & [date, value] : missingDates) {
322- outer.second [date] = value;
323- std::ostringstream oss;
324- for (Size k = 0 ; k < value.size (); ++k) {
325- if (k > 0 )
326- oss << " , " ;
327- oss << value[k];
328- }
329- LOG (" Add missing IR curve rate on date " << date << " , Currency: " << outer.first
330- << " , Value: " << oss.str ());
331- }
332- }
191+ std::string HwHistoricalCalibrationDataLoader::parseCurrency (const std::string& curveId) {
192+ vector<string> tokens;
193+ split (tokens, curveId, boost::is_any_of (" -" ));
194+ QL_REQUIRE (tokens.size () == 2 || tokens.size () == 3 ,
195+ " Two or three tokens required in " << curveId << " : CCY-INDEX or CCY-INDEX-TERM" );
196+ return tokens[0 ];
333197}
334198
335199} // namespace analytics
0 commit comments