Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fixings.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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 <boost/make_shared.hpp>
20// clang-format off
21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
23// clang-format on
38#include <oret/datapaths.hpp>
39#include <oret/toplevelfixture.hpp>
40#include <ql/time/calendars/weekendsonly.hpp>
41#include <tuple>
42
43using namespace QuantLib;
44using namespace QuantExt;
45using namespace boost::unit_test_framework;
46using namespace std;
47using namespace ore::data;
48
49using ore::test::TopLevelFixture;
50
52
53namespace {
54
55// Give back the expected results for the tests on the various trade types below
56// The results are read in from the file "test_trade_types_expected.csv"
57map<tuple<string, string, bool, bool>, map<string, set<Date>>> tradeTypeExpected() {
58
59 static map<tuple<string, string, bool, bool>, map<string, set<Date>>> exp;
60
61 string expResultsFile = TEST_INPUT_FILE("test_trade_types_expected.csv");
62 CSVFileReader reader(expResultsFile, true, ",");
63
64 while (reader.next()) {
65 string tradeType = reader.get("trade_type");
66 string tradeCase = reader.get("trade_case");
67 bool includeSettlementDateFlows = parseBool(reader.get("isdf"));
68 bool enforcesTodaysHistoricFixings = parseBool(reader.get("ethf"));
69 string indexName = reader.get("index_name");
70
71 string datesList = reader.get("dates");
72 vector<string> strDates;
73 boost::split(strDates, datesList, boost::is_any_of("|"));
74 set<Date> dates;
75 for (const auto& strDate : strDates) {
76 dates.insert(parseDate(strDate));
77 }
78
79 auto key = make_tuple(tradeType, tradeCase, includeSettlementDateFlows, enforcesTodaysHistoricFixings);
80 if (exp.count(key) == 0) {
81 exp[key] = {};
82 }
83 exp[key][indexName] = dates;
84 }
85
86 return exp;
87}
88
89// Give back the dummy fixings keyed on [Index Name, Date] pair. We will load chosen elements
90// from this map at the end of the trade tests below to check that the trade prices.
91map<tuple<string, Date>, Fixing> dummyFixings() {
92
93 static map<tuple<string, Date>, Fixing> result;
94
95 string dummyFixingsFile = TEST_INPUT_FILE("test_trade_types_dummy_fixings.csv");
96 CSVFileReader reader(dummyFixingsFile, true, ",");
97
98 while (reader.next()) {
99 string name = reader.get("name");
100 Date date = parseDate(reader.get("date"));
101 Real fixing = parseReal(reader.get("value"));
102
103 result.emplace(make_pair(name, date), Fixing(date, name, fixing));
104 }
105
106 return result;
107}
108
109// Load the requested fixings
110void loadFixings(const map<string, RequiredFixings::FixingDates>& requestedFixings) {
111
112 // Get the dummy fixings that we have provided in the input directory
113 auto fixingValues = dummyFixings();
114
115 // Fetch the relevant fixings using the requestedFixings argument
116 set<Fixing> relevantFixings;
117 for (const auto& [indexName, fixingDates] : requestedFixings) {
118 for (const auto& [d, mandatory] : fixingDates) {
119 relevantFixings.insert(fixingValues.at(make_pair(indexName, d)));
120 }
121 }
122
123 // Add the fixings in QuantLib index manager
124 applyFixings(relevantFixings);
125}
126
127// Fixture used in test case below:
128// - sets a specific valuation date for the test
129// - provides conventions
130// - provides and engine factory for the test
131class F : public TopLevelFixture {
132public:
133 Date today;
134 QuantLib::ext::shared_ptr<Conventions> conventions = QuantLib::ext::make_shared<Conventions>();
135 QuantLib::ext::shared_ptr<EngineFactory> engineFactory;
136
137 F() {
138 today = Date(12, Feb, 2019);
139 Settings::instance().evaluationDate() = today;
140
141 conventions->fromFile(TEST_INPUT_FILE("market/conventions.xml"));
142 InstrumentConventions::instance().setConventions(conventions);
143
144 auto todaysMarketParams = QuantLib::ext::make_shared<TodaysMarketParameters>();
145 todaysMarketParams->fromFile(TEST_INPUT_FILE("market/todaysmarket.xml"));
146
147 auto curveConfigs = QuantLib::ext::make_shared<CurveConfigurations>();
148 curveConfigs->fromFile(TEST_INPUT_FILE("market/curveconfig.xml"));
149
150 string marketFile = TEST_INPUT_FILE("market/market.txt");
151 string fixingsFile = TEST_INPUT_FILE("market/fixings_for_bootstrap.txt");
152 string dividendsFile = TEST_INPUT_FILE("market/dividends.txt");
153 auto loader = QuantLib::ext::make_shared<CSVLoader>(marketFile, fixingsFile, dividendsFile, false);
154
155 bool continueOnError = false;
156 QuantLib::ext::shared_ptr<TodaysMarket> market = QuantLib::ext::make_shared<TodaysMarket>(
157 today, todaysMarketParams, loader, curveConfigs, continueOnError);
158
159 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
160 engineData->fromFile(TEST_INPUT_FILE("market/pricingengine.xml"));
161
162 engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
163 }
164
165 ~F() {}
166};
167
168// List of trades that will feed the data-driven test below. This is a list of input folder names
169// under OREData/test/input/fixings. In each folder, there are three test portfolio files containing a
170// trade of the given type. The three files cover three cases:
171// - simple case where fixing date < today < payment date
172// - payment today where a coupon that relies on an index has payment date == today
173// - fixing today where a coupon that relies on an index has fixing date == today
174vector<string> tradeTypes = {"fixed_float_swap", "in_ccy_basis_swap", "zciis_with_interp",
175 "cpi_swap_with_interp", "yoy_swap_without_interp", "xccy_resetting_swap",
176 "equity_swap", "cms_spread_swap"};
177
178vector<string> tradeCases = {"simple_case", "payment_today", "fixing_today"};
179
180vector<bool> bools = {true, false};
181
182vector<bool> enforcesTodaysHistoricFixings = {true, false};
183
184} // namespace
185
186BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
187
188BOOST_AUTO_TEST_SUITE(FixingsTests)
189
190BOOST_DATA_TEST_CASE_F(F, testTradeTypes,
191 bdata::make(tradeTypes) * bdata::make(tradeCases) * bdata::make(bools) * bdata::make(bools),
192 tradeType, tradeCase, includeSettlementDateFlows, enforcesTodaysHistoricFixings) {
193
194 // Set the flag determining what happens if fixings are required today
195 Settings::instance().enforcesTodaysHistoricFixings() = enforcesTodaysHistoricFixings;
196
197 // Set the flag determining what happens when cashflows happen today
198 Settings::instance().includeTodaysCashFlows() = includeSettlementDateFlows;
199
200 // Read in the trade
201 Portfolio p;
202 string portfolioFile = "trades/" + tradeType + "/" + tradeCase + ".xml";
203 p.fromFile(TEST_INPUT_FILE(portfolioFile));
204 BOOST_REQUIRE_MESSAGE(p.size() == 1, "Expected portfolio to contain a single trade");
205
206 // Ask for fixings before trades are built should return empty set
207 auto m = p.fixings(today);
208 BOOST_CHECK_MESSAGE(m.empty(), "Expected fixings to be empty when trades not built");
209
210 // Build the portfolio and retrieve the fixings
211 p.build(engineFactory);
212 m = p.fixings(today);
213
214 // Check the retrieved fixings against the expected results
215 auto exp = tradeTypeExpected();
216 auto key = make_tuple(tradeType, tradeCase, includeSettlementDateFlows, enforcesTodaysHistoricFixings);
217 if (exp.count(key) == 0) {
218 // Expected result is no required fixings
219 BOOST_CHECK_MESSAGE(m.empty(), "Expected no required fixings for ["
220 << tradeType << ", " << tradeCase << ", "
221 << ore::data::to_string(includeSettlementDateFlows) << ", "
222 << ore::data::to_string(enforcesTodaysHistoricFixings)
223 << "] but got a map containing " << m.size() << " indices");
224
225 // Trade should not throw if we ask for NPV
226 BOOST_CHECK_NO_THROW(p.trades().begin()->second->instrument()->NPV());
227
228 } else {
229 // Check the retrieved fixings against the expected fixings
230 auto expMap = exp.at(key);
231 BOOST_CHECK_EQUAL(expMap.size(), m.size());
232 for (const auto& [indexName, expectedDates] : expMap) {
233 BOOST_CHECK_MESSAGE(m.count(indexName), "Could not find index " <<indexName << " in retrieved fixings");
234 std::set<QuantLib::Date> actualDates;
235 for (const auto& [d, _] : m.at(indexName)) {
236 actualDates.insert(d);
237 }
238 BOOST_CHECK_EQUAL_COLLECTIONS(expectedDates.begin(), expectedDates.end(), actualDates.begin(),
239 actualDates.end());
240 }
241
242 // Trade should throw if we ask for NPV and have not added the fixings
243 // If it is the zciis trade, it won't throw because the fixings were added for the bootstrap
244 if (tradeType != "zciis_with_interp" && tradeType != "cpi_swap_with_interp") {
245 BOOST_CHECK_THROW(p.trades().begin()->second->instrument()->NPV(), Error);
246 }
247
248 // Add the fixings
249 loadFixings(m);
250
251 // Trade should now not throw when we try to price it
252 BOOST_CHECK_NO_THROW(p.trades().begin()->second->instrument()->NPV());
253 }
254}
255
256BOOST_AUTO_TEST_CASE(testModifyInflationFixings) {
257
258 // Original fixings
259 map<string, RequiredFixings::FixingDates> fixings = {
260 {"EUHICP", RequiredFixings::FixingDates({Date(1, Jan, 2019), Date(1, Dec, 2018), Date(1, Nov, 2018)}, true)},
261 {"USCPI", RequiredFixings::FixingDates({Date(1, Dec, 2018), Date(1, Nov, 2018), Date(22, Oct, 2018),
262 Date(1, Feb, 2018), Date(1, Feb, 2016)},
263 true)},
264 {"EUR-EURIBOR-3M", RequiredFixings::FixingDates({Date(18, Dec, 2018), Date(13, Feb, 2019)}, true)}};
265
266 // Expected fixings after inflation modification
267 map<string, RequiredFixings::FixingDates> expectedFixings = {
268 {"EUHICP", RequiredFixings::FixingDates({Date(31, Jan, 2019), Date(31, Dec, 2018), Date(30, Nov, 2018)}, true)},
269 {"USCPI", RequiredFixings::FixingDates({Date(31, Dec, 2018), Date(30, Nov, 2018), Date(22, Oct, 2018),
270 Date(28, Feb, 2018), Date(29, Feb, 2016)},
271 true)},
272 {"EUR-EURIBOR-3M", RequiredFixings::FixingDates({Date(18, Dec, 2018), Date(13, Feb, 2019)}, true)}};
273
274 // Amend the inflation portion of the fixings
276
277 // Compare contents of the output files
278 BOOST_CHECK_EQUAL(expectedFixings.size(), fixings.size());
279 for (const auto& [indexname, expectedFixingDates] : expectedFixings) {
280 BOOST_CHECK_MESSAGE(fixings.count(indexname), "Could not find index " << indexname << " in retrieved fixings");
281 std::set<QuantLib::Date> expectedDates;
282 for (const auto& [d, _] : expectedFixingDates) {
283 expectedDates.insert(d);
284 }
285
286 std::set<QuantLib::Date> actualDates;
287 for (const auto& [d, _] : fixings[indexname]) {
288 actualDates.insert(d);
289 }
290 BOOST_CHECK_EQUAL_COLLECTIONS(expectedDates.begin(), expectedDates.end(), actualDates.begin(),
291 actualDates.end());
292 }
293}
294
295BOOST_AUTO_TEST_CASE(testAddMarketFixings) {
296
297 // Set the evaluation date
298 Date asof(21, Feb, 2019);
299 Settings::instance().evaluationDate() = asof;
300
301 // Set up a simple TodaysMarketParameters
302 TodaysMarketParameters mktParams;
304
305 // Add discount curves, we expect market fixings for EUR-EONIA
306 map<string, string> m = {{"EUR", "Yield/EUR/EUR-EONIA"}, {"USD", "Yield/USD/USD-IN-EUR"}};
307 mktParams.addMarketObject(MarketObject::DiscountCurve, Market::defaultConfiguration, m);
308
309 // Add ibor index curves
310 m = {{"EUR-EURIBOR-3M", "Yield/EUR/EUR-EURIBOR-3M"},
311 {"USD-FedFunds", "Yield/USD/USD-FedFunds"},
312 {"USD-LIBOR-3M", "Yield/USD/USD-LIBOR-3M"}};
313 mktParams.addMarketObject(MarketObject::IndexCurve, Market::defaultConfiguration, m);
314
315 // Add zero inflation curves
316 m = {{"EUHICPXT", "Inflation/EUHICPXT/EUHICPXT_ZC_Swaps"}, {"USCPI", "Inflation/USCPI/USCPI_ZC_Swaps"}};
317 mktParams.addMarketObject(MarketObject::ZeroInflationCurve, Market::defaultConfiguration, m);
318
319 // Add yoy inflation curves
320 m = {{"EUHICPXT", "Inflation/EUHICPXT/EUHICPXT_YOY_Swaps"}, {"UKRPI", "Inflation/UKRPI/UKRPI_YOY_Swaps"}};
321 mktParams.addMarketObject(MarketObject::YoYInflationCurve, Market::defaultConfiguration, m);
322
323 // Expected additional market fixings
324 set<Date> inflationDates = {Date(1, Feb, 2019), Date(1, Jan, 2019), Date(1, Dec, 2018), Date(1, Nov, 2018),
325 Date(1, Oct, 2018), Date(1, Sep, 2018), Date(1, Aug, 2018), Date(1, Jul, 2018),
326 Date(1, Jun, 2018), Date(1, May, 2018), Date(1, Apr, 2018), Date(1, Mar, 2018),
327 Date(1, Feb, 2018)};
328 set<Date> iborDates = {Date(21, Feb, 2019), Date(20, Feb, 2019), Date(19, Feb, 2019),
329 Date(18, Feb, 2019), Date(15, Feb, 2019), Date(14, Feb, 2019)};
330
331 // Default for OIS dates is a lookback of 4 months on weekend only calendar => 21 Feb 2019 -> 21 Oct 2018.
332 // 21 Oct 2018 is a Sunday => 22 Oct 2018 is the start of the lookback.
333 set<Date> oisDates;
334 Date oisDate(22, Oct, 2018);
335 WeekendsOnly cal;
336 while (oisDate <= asof) {
337 oisDates.insert(oisDate);
338 oisDate = cal.advance(oisDate, 1 * Days);
339 }
340
341 map<string, RequiredFixings::FixingDates> expectedFixings = {
342 {"EUHICPXT", RequiredFixings::FixingDates(inflationDates, false)},
343 {"USCPI", RequiredFixings::FixingDates(inflationDates, false)},
344 {"UKRPI", RequiredFixings::FixingDates(inflationDates, false)},
345 {"EUR-EURIBOR-3M", RequiredFixings::FixingDates(iborDates, false)},
346 {"USD-FedFunds", RequiredFixings::FixingDates(oisDates, false)},
347 {"USD-LIBOR-3M", RequiredFixings::FixingDates(iborDates, false)},
348 {"EUR-EONIA", RequiredFixings::FixingDates(oisDates, false)}};
349
350 // Populate empty fixings map using the function to be tested
351 map<string, RequiredFixings::FixingDates> fixings;
352 addMarketFixingDates(asof, fixings, mktParams);
353
354 // Check the results
355 BOOST_CHECK_EQUAL(expectedFixings.size(), fixings.size());
356 for (const auto& [indexName, expectedFixingDates] : expectedFixings) {
357 BOOST_CHECK_MESSAGE(fixings.count(indexName), "Could not find index " << indexName << " in retrieved fixings");
358 std::set<QuantLib::Date> expectedDates;
359 for (const auto& [d, _] : expectedFixingDates) {
360 expectedDates.insert(d);
361 }
362
363 std::set<QuantLib::Date> actualDates;
364 for (const auto& [d, _] : fixings[indexName]) {
365 actualDates.insert(d);
366 }
367 BOOST_CHECK_EQUAL_COLLECTIONS(expectedDates.begin(), expectedDates.end(), actualDates.begin(),
368 actualDates.end());
369 }
370}
371
372BOOST_FIXTURE_TEST_CASE(testFxNotionalResettingSwapFirstCoupon, F) {
373
374 // Set the flag determining what happens if fixings are required today
375 Settings::instance().enforcesTodaysHistoricFixings() = true;
376
377 // Set the flag determining what happens when cashflows happen today
378 Settings::instance().includeTodaysCashFlows() = true;
379
380 // Read in the trade
381 Portfolio p;
382 string portfolioFile = "trades/xccy_resetting_swap/simple_case_in_first_coupon.xml";
383 p.fromFile(TEST_INPUT_FILE(portfolioFile));
384 BOOST_REQUIRE_MESSAGE(p.size() == 1, "Expected portfolio to contain a single trade");
385
386 // Ask for fixings before trades are built should return empty set
387 auto m = p.fixings(today);
388 BOOST_CHECK_MESSAGE(m.empty(), "Expected fixings to be empty when trades not built");
389
390 // Build the portfolio and retrieve the fixings
391 p.build(engineFactory);
392 m = p.fixings(today);
393
394 // Expected results
395 map<string, Date> exp = {{"USD-LIBOR-3M", Date(5, Feb, 2019)}, {"EUR-EURIBOR-3M", Date(5, Feb, 2019)}};
396
397 // Check the expected results against the actual results
398 BOOST_CHECK_EQUAL(m.size(), exp.size());
399 for (const auto& kv : exp) {
400 BOOST_CHECK_MESSAGE(m.count(kv.first) == 1, "Could not find index " << kv.first << " in retrieved fixings");
401 if (m.count(kv.first) == 1) {
402 BOOST_CHECK_EQUAL(m.at(kv.first).size(), 1);
403 BOOST_CHECK_EQUAL(kv.second, m.at(kv.first).begin()->first);
404 }
405 }
406
407 // Trade should throw if we ask for NPV and have not added the fixings
408 BOOST_CHECK_THROW(p.trades().begin()->second->instrument()->NPV(), Error);
409
410 // Add the fixings
411 loadFixings(m);
412
413 // Trade should now not throw when we try to price it
414 BOOST_CHECK_NO_THROW(p.trades().begin()->second->instrument()->NPV());
415}
416
417BOOST_FIXTURE_TEST_CASE(testDividends, F) {
418
419 const string equityName = "RIC:DMIWO00000GUS";
420
421 auto eq = parseEquityIndex("EQ-" + equityName);
422 BOOST_REQUIRE_MESSAGE(eq, "Could not parse equity index EQ-" + equityName);
423
424 BOOST_REQUIRE_MESSAGE(DividendManager::instance().hasHistory(eq->name()),
425 "Could not find index " << eq->name() << " in DividendManager");
426 map<Date, QuantExt::Dividend> divMap;
427 const set<QuantExt::Dividend>& dividends = eq->dividendFixings();
428 for (const auto& d : dividends)
429 divMap[d.exDate] = d;
430
431 // Expected results
432 map<Date, Real> exp = {{Date(1, Nov, 2018), 25.313}, {Date(1, Dec, 2018), 15.957}};
433
434 BOOST_CHECK_EQUAL(dividends.size(), exp.size());
435 for (const auto& kv : exp) {
436 BOOST_CHECK_EQUAL(divMap[kv.first].rate, kv.second);
437 }
438}
439
440BOOST_AUTO_TEST_SUITE_END()
441
442BOOST_AUTO_TEST_SUITE_END()
static const string defaultConfiguration
Default configuration label.
Definition: market.hpp:296
Serializable portfolio.
Definition: portfolio.hpp:43
const std::map< std::string, QuantLib::ext::shared_ptr< Trade > > & trades() const
Return the map tradeId -> trade.
Definition: portfolio.cpp:162
std::map< std::string, RequiredFixings::FixingDates > fixings(const QuantLib::Date &settlementDate=QuantLib::Date()) const
Definition: portfolio.cpp:220
QuantLib::Size size() const
Portfolio size.
Definition: portfolio.hpp:68
void build(const QuantLib::ext::shared_ptr< EngineFactory > &, const std::string &context="unspecified", const bool emitStructuredError=true)
Call build on all trades in the portfolio, the context is included in error messages.
Definition: portfolio.cpp:122
void addMarketObject(const MarketObject o, const string &id, const map< string, string > &assignments)
void addConfiguration(const string &name, const MarketConfiguration &configuration)
void fromFile(const std::string &filename)
Definition: xmlutils.cpp:150
Currency and instrument specific conventions/defaults.
utility class to access CSV files
Market Datum Loader Implementation.
Curve configuration repository.
Pricing Engine Factory.
Logic for calculating required fixing dates on legs.
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > parseEquityIndex(const string &s)
Convert std::string (e.g SP5) to QuantExt::EquityIndex.
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
bool parseBool(const string &s)
Convert text to bool.
Definition: parsers.cpp:144
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
Map text representations to QuantLib/QuantExt types.
RandomVariable exp(RandomVariable x)
void amendInflationFixingDates(std::map< std::string, RequiredFixings::FixingDates > &fixings)
void addMarketFixingDates(const Date &asof, map< string, RequiredFixings::FixingDates > &fixings, const TodaysMarketParameters &mktParams, const Period &iborLookback, const Period &oisLookback, const Period &bmaLookback, const Period &inflationLookback)
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
void applyFixings(const set< Fixing > &fixings)
Utility to write a vector of fixings in the QuantLib index manager's fixing history.
Definition: fixings.cpp:41
Portfolio class.
Real at(const Size i) const
Fixing data structure.
Definition: fixings.hpp:44
vector< string > curveConfigs
BOOST_FIXTURE_TEST_CASE(testFxNotionalResettingSwapFirstCoupon, F)
Definition: fixings.cpp:372
BOOST_AUTO_TEST_CASE(testModifyInflationFixings)
Definition: fixings.cpp:256
BOOST_DATA_TEST_CASE_F(F, testTradeTypes, bdata::make(tradeTypes) *bdata::make(tradeCases) *bdata::make(bools) *bdata::make(bools), tradeType, tradeCase, includeSettlementDateFlows, enforcesTodaysHistoricFixings)
Definition: fixings.cpp:190
string conversion utilities
An concrete implementation of the Market class that loads todays market and builds the required curve...
string name
A class to hold todays market configuration(s)
base trade data model and serialization