Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commoditycurve.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
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
24#include <oret/datapaths.hpp>
25#include <oret/toplevelfixture.hpp>
26
27#include <ql/math/comparison.hpp>
28#include <ql/math/interpolations/linearinterpolation.hpp>
29#include <ql/math/interpolations/loginterpolation.hpp>
30#include <ql/time/daycounters/actualactual.hpp>
31
40
41using namespace std;
42using namespace boost::unit_test_framework;
43using namespace QuantLib;
44using namespace QuantExt;
45using namespace ore::data;
46
48
49namespace {
50
51// List of curve configuration file names for data test case below
52vector<string> curveConfigFiles = {"curveconfig_linear.xml", "curveconfig_linear_flat.xml",
53 "curveconfig_loglinear.xml", "curveconfig_loglinear_flat.xml",
54 "curveconfig_cubic.xml", "curveconfig_cubic_flat.xml"};
55
56// The expected commodity curve
57map<Date, Real> expectedCurve = {{Date(29, Jul, 2019), 1417.8998900}, {Date(30, Jul, 2019), 1417.9999450},
58 {Date(31, Jul, 2019), 1418.1000000}, {Date(1, Aug, 2019), 1418.2000550},
59 {Date(30, Aug, 2019), 1421.1016535}, {Date(30, Sep, 2019), 1424.1312750}};
60
61// Pillars for interpolated curve tests (purposely leave out elements below spot to test interpolation there)
62map<Date, Real> expectedInterpCurvePillars = {{Date(31, Jul, 2019), 1418.1000000},
63 {Date(1, Aug, 2019), 1418.2000550},
64 {Date(30, Aug, 2019), 1421.1016535},
65 {Date(30, Sep, 2019), 1424.1312750}};
66
67// Expected results for extrapolation below spot, interpolation and extrapolation beyond max date for the
68// various interpolation methods
69vector<Date> offPillarDates = {Date(29, Jul, 2019), Date(15, Sep, 2019), Date(1, Nov, 2019)};
70
71map<string, vector<Real>> expectedInterpCurveOffPillars = {
72 {"curveconfig_linear.xml", {1417.89989, 1422.6653291129, 1427.2586262258}},
73 {"curveconfig_linear_flat.xml", {1418.1, 1422.6653291129, 1424.131275}},
74 {"curveconfig_loglinear.xml", {1417.89991117635, 1422.66452345277, 1427.26540106042}},
75 {"curveconfig_loglinear_flat.xml", {1418.1, 1422.66452345277, 1424.131275}},
76 {"curveconfig_cubic.xml", {1417.89988981896, 1422.67192914531, 1427.25983144911}},
77 {"curveconfig_cubic_flat.xml", {1418.1, 1422.67192914531, 1424.131275}}};
78
79QuantLib::ext::shared_ptr<CommodityCurve> createCurve(const string& inputDir,
80 const string& curveConfigFile = "curveconfig.xml") {
81
82 // As of date
83 Date asof(29, Jul, 2019);
84
85 QuantLib::ext::shared_ptr<Conventions> conventions = QuantLib::ext::make_shared<Conventions>();
86 string filename = inputDir + "/conventions.xml";
87 conventions->fromFile(TEST_INPUT_FILE(filename));
88 InstrumentConventions::instance().setConventions(conventions);
89
91 filename = inputDir + "/" + curveConfigFile;
92 curveConfigs.fromFile(TEST_INPUT_FILE(filename));
93 filename = inputDir + "/market.txt";
94 CSVLoader loader(TEST_INPUT_FILE(filename), TEST_INPUT_FILE("fixings.txt"), false);
95
96 // Commodity curve spec
97 CommodityCurveSpec curveSpec("USD", "PM:XAUUSD");
98
99 // Check commodity curve construction works
100 QuantLib::ext::shared_ptr<CommodityCurve> curve;
101 BOOST_REQUIRE_NO_THROW(curve = QuantLib::ext::make_shared<CommodityCurve>(asof, curveSpec, loader, curveConfigs));
102
103 return curve;
104}
105
106QuantLib::ext::shared_ptr<TodaysMarket> createTodaysMarket(const Date& asof, const string& inputDir) {
107
108 auto conventions = QuantLib::ext::make_shared<Conventions>();
109 conventions->fromFile(TEST_INPUT_FILE(string(inputDir + "/conventions.xml")));
110 InstrumentConventions::instance().setConventions(conventions);
111
112 auto curveConfigs = QuantLib::ext::make_shared<CurveConfigurations>();
113 curveConfigs->fromFile(TEST_INPUT_FILE(string(inputDir + "/curveconfig.xml")));
114
115 auto todaysMarketParameters = QuantLib::ext::make_shared<TodaysMarketParameters>();
116 todaysMarketParameters->fromFile(TEST_INPUT_FILE(string(inputDir + "/todaysmarket.xml")));
117
118 string fixingsFile = inputDir + "/fixings_" + to_string(io::iso_date(asof)) + ".txt";
119 auto loader = QuantLib::ext::make_shared<CSVLoader>(TEST_INPUT_FILE(string(inputDir + "/market.txt")),
120 TEST_INPUT_FILE(fixingsFile), false);
121
122 return QuantLib::ext::make_shared<TodaysMarket>(asof, todaysMarketParameters, loader, curveConfigs);
123}
124
125void checkCurve(const QuantLib::ext::shared_ptr<PriceTermStructure>& priceCurve, const map<Date, Real>& expectedValues) {
126
127 for (const auto& kv : expectedValues) {
128 Real price = priceCurve->price(kv.first);
129 BOOST_CHECK_CLOSE(price, kv.second, 1e-12);
130 }
131}
132
133} // namespace
134
135BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
136
137BOOST_AUTO_TEST_SUITE(CommodityCurveTests)
138
139BOOST_AUTO_TEST_CASE(testCommodityCurveTenorBasedOnTnPoints) {
140
141 BOOST_TEST_MESSAGE("Testing commodity curve building with tenor based points quotes including ON and TN");
142
143 QuantLib::ext::shared_ptr<CommodityCurve> curve = createCurve("tenor_based_on_tn_points");
144 checkCurve(curve->commodityPriceCurve(), expectedCurve);
145}
146
147BOOST_AUTO_TEST_CASE(testCommodityCurveFixedDatePoints) {
148
149 BOOST_TEST_MESSAGE("Testing commodity curve building with fixed date quotes");
150
151 QuantLib::ext::shared_ptr<CommodityCurve> curve = createCurve("fixed_date_points");
152 checkCurve(curve->commodityPriceCurve(), expectedCurve);
153}
154
155// Testing various interpolation methods
156BOOST_DATA_TEST_CASE(testCommodityInterpolations, bdata::make(curveConfigFiles), curveConfigFile) {
157
158 BOOST_TEST_MESSAGE("Testing with configuration file: " << curveConfigFile);
159
160 QuantLib::ext::shared_ptr<CommodityCurve> curve = createCurve("different_interpolations", curveConfigFile);
161 checkCurve(curve->commodityPriceCurve(), expectedInterpCurvePillars);
162
163 BOOST_REQUIRE(expectedInterpCurveOffPillars.count(curveConfigFile) == 1);
164 for (Size i = 0; i < offPillarDates.size(); i++) {
165 Real price = curve->commodityPriceCurve()->price(offPillarDates[i]);
166 Real expPrice = expectedInterpCurveOffPillars.at(curveConfigFile)[i];
167 BOOST_CHECK_CLOSE(price, expPrice, 1e-12);
168 }
169}
170
171// clang-format off
172
173// Test case struct for data test below.
174struct CommodityCurveTestCase {
175 Date asof;
176 string name;
177 string curveName;
178};
179
180// List of commodity test cases for the data test case below.
181vector<CommodityCurveTestCase> commodityCurveTestCases = {
182 {Date(30, Sep, 2019), "basis/wti_midland_cm", "NYMEX:FF"},
183 {Date(30, Sep, 2019), "basis/wti_midland_tm", "NYMEX:WTT"},
184 {Date(30, Sep, 2019), "basis/wti_midland_cm_base_ave", "NYMEX:FF"},
185 {Date(30, Sep, 2019), "basis/houston_ship_channel", "ICE:HXS"},
186 {Date(23, Jan, 2020), "basis/wti_midland_cm", "NYMEX:FF"},
187 {Date(23, Jan, 2020), "basis/wti_midland_tm", "NYMEX:WTT"},
188 {Date(23, Jan, 2020), "basis/wti_midland_cm_base_ave", "NYMEX:FF"},
189 {Date(23, Jan, 2020), "basis/houston_ship_channel", "ICE:HXS"},
190 {Date(27, Apr, 2020), "power/pjm_wh_rt_peak_linear_flat", "ICE:PDQ"},
191 {Date(27, Apr, 2020), "power/pjm_wh_rt_peak_backward_flat", "ICE:PDQ"},
192 {Date(27, Apr, 2020), "power/pjm_wh_rt_peak_linear_flat_switch_priority", "ICE:PDQ"}
193};
194
195// Needed for BOOST_DATA_TEST_CASE below as it writes out the case.
196ostream& operator<<(ostream& os, CommodityCurveTestCase testCase) {
197 return os << "[" << io::iso_date(testCase.asof) << "," << testCase.name << "," << testCase.curveName << "]";
198}
199
200BOOST_DATA_TEST_CASE(testCommodityCurveBuilding, bdata::make(commodityCurveTestCases), testCase) {
201
202 BOOST_TEST_MESSAGE("Testing commodity curve building " << testCase << "...");
203
204 Settings::instance().evaluationDate() = testCase.asof;
205 QuantLib::ext::shared_ptr<TodaysMarket> tm;
206 BOOST_REQUIRE_NO_THROW(tm = createTodaysMarket(testCase.asof, testCase.name));
207
208 auto pts = tm->commodityPriceCurve(testCase.curveName);
209
210 for (const Date& d : pts->pillarDates()) {
211 BOOST_TEST_MESSAGE(io::iso_date(d) << "," << fixed << setprecision(12) << pts->price(d));
212 }
213
214 // Tolerance for float comparison
215 Real tol = 1e-12;
216
217 // Read in the expected pillar results for the given date.
218 vector<Date> expPillarDates;
219 string filename = testCase.name + "/expected_" + to_string(io::iso_date(testCase.asof)) + ".csv";
220 CSVFileReader reader(TEST_INPUT_FILE(filename), true, ",");
221 BOOST_REQUIRE_EQUAL(reader.numberOfColumns(), 2);
222
223 while (reader.next()) {
224 // Get the expected expiry pillar date and price.
225 Date expiry = parseDate(reader.get(0));
226 Real price = parseReal(reader.get(1));
227 expPillarDates.push_back(expiry);
228
229 // Check the surface on the grid point.
230 Real calcPrice = pts->price(expiry);
231 BOOST_TEST_MESSAGE(io::iso_date(expiry) << "," << fixed << setprecision(12) << calcPrice);
232 BOOST_CHECK_SMALL(price - calcPrice, tol);
233 }
234
235 vector<Date> calcPillarDates = pts->pillarDates();
236 BOOST_CHECK_EQUAL_COLLECTIONS(expPillarDates.begin(), expPillarDates.end(),
237 calcPillarDates.begin(), calcPillarDates.end());
238
239 // Set up has flat extrapolation. Check it here.
240 Real lastPrice = pts->price(calcPillarDates.back());
241 Date extrapDate = calcPillarDates.back() + 1 * Years;
242 Real extrapPrice = pts->price(extrapDate);
243 BOOST_CHECK_SMALL(lastPrice - extrapPrice, tol);
244}
245
246// clang-format on
247
248BOOST_AUTO_TEST_SUITE_END()
249
250BOOST_AUTO_TEST_SUITE_END()
Utility class for loading market quotes and fixings from a file.
Definition: csvloader.hpp:41
Size numberOfColumns() const
std::string get(const std::string &field) const
Commodity curve description.
Definition: curvespec.hpp:413
Container class for all Curve Configurations.
Class for building a commodity price curve.
utility class to access CSV files
Market Datum Loader Implementation.
Curve configuration repository.
Curve requirements specification.
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
Market Datum Loader Interface.
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
ostream & operator<<(ostream &os, CommodityCurveTestCase testCase)
BOOST_DATA_TEST_CASE(testCommodityInterpolations, bdata::make(curveConfigFiles), curveConfigFile)
vector< CommodityCurveTestCase > commodityCurveTestCases
BOOST_AUTO_TEST_CASE(testCommodityCurveTenorBasedOnTnPoints)
vector< string > curveConfigs
string conversion utilities
An concrete implementation of the Market class that loads todays market and builds the required curve...
string name