Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
pricecurve.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#include "toplevelfixture.hpp"
19#include <boost/assign/list_of.hpp>
20#include <boost/make_shared.hpp>
21#include <boost/test/unit_test.hpp>
22
23#include <ql/currencies/america.hpp>
24#include <ql/math/interpolations/all.hpp>
25#include <ql/quotes/simplequote.hpp>
26#include <ql/time/daycounters/actual365fixed.hpp>
27
29
30using namespace std;
31
32using namespace boost::unit_test_framework;
33using namespace boost::assign;
34
35using namespace QuantLib;
36using namespace QuantExt;
37
38namespace {
39
40class CommonData {
41public:
42 // Variables
43 vector<Date> testDates;
44 vector<Period> curveTenors;
45 vector<Real> prices;
46 vector<Real> shiftedPrices;
47 vector<QuantLib::ext::shared_ptr<SimpleQuote> > pQuotes;
48 vector<Handle<Quote> > quotes;
49
50 // Dates on which we will check interpolated values
51 vector<Date> interpolationDates;
52 vector<Time> interpolationTimes;
53 // Expected (linear) interpolation results on first test date
54 vector<Real> baseExpInterpResults;
55 // Expected (linear) interpolation results for floating reference date
56 // curve after moving to second test date and requesting price by date
57 vector<Real> afterExpInterpResults;
58 // Expected (linear) interpolation results on first test date after
59 // shifting quotes
60 vector<Real> baseNewInterpResults;
61 // Expected (linear) interpolation results for floating reference date
62 // curve after shifting quotes and moving to second test date and
63 // requesting price by date
64 vector<Real> afterNewInterpResults;
65
66 // Expected loglinear interpolation results
67 vector<Real> expLogInterpResults;
68
69 DayCounter curveDayCounter;
70 Real tolerance;
71 bool extrapolate;
72 USDCurrency currency;
73
74 // cleanup
75 SavedSettings backup;
76
77 // Default constructor
78 CommonData()
79 : testDates(2), curveTenors(6), prices(6), shiftedPrices(6), pQuotes(6), quotes(6), interpolationDates(3),
80 interpolationTimes(3), baseExpInterpResults(3), afterExpInterpResults(3), baseNewInterpResults(3),
81 afterNewInterpResults(3), expLogInterpResults(3), curveDayCounter(Actual365Fixed()), tolerance(1e-10),
82 extrapolate(true) {
83
84 // Evaluation dates on which tests will be performed
85 testDates[0] = Date(15, Feb, 2018);
86 testDates[1] = Date(15, Mar, 2018);
87
88 // Curve tenors and prices
89 curveTenors[0] = 0 * Days;
90 prices[0] = 14.5;
91 shiftedPrices[0] = 16.6;
92 curveTenors[1] = 181 * Days;
93 prices[1] = 16.7;
94 shiftedPrices[1] = 19.9;
95 curveTenors[2] = 365 * Days;
96 prices[2] = 19.9;
97 shiftedPrices[2] = 24.4;
98 curveTenors[3] = 546 * Days;
99 prices[3] = 24.5;
100 shiftedPrices[3] = 31.3;
101 curveTenors[4] = 730 * Days;
102 prices[4] = 28.5;
103 shiftedPrices[4] = 38.1;
104 curveTenors[5] = 1826 * Days;
105 prices[5] = 38.8;
106 shiftedPrices[5] = 54.5;
107
108 // Create quotes
109 for (Size i = 0; i < quotes.size(); i++) {
110 pQuotes[i] = QuantLib::ext::make_shared<SimpleQuote>(prices[i]);
111 quotes[i] = Handle<Quote>(pQuotes[i]);
112 }
113
114 // Interpolation variables
115 interpolationDates[0] = Date(1, Jan, 2019);
116 interpolationDates[1] = Date(1, Jun, 2021);
117 interpolationDates[2] = Date(1, Aug, 2025);
118
119 for (Size i = 0; i < interpolationDates.size(); i++) {
120 interpolationTimes[i] = curveDayCounter.yearFraction(testDates[0], interpolationDates[i]);
121 }
122
123 baseExpInterpResults[0] = 19.1173913043478;
124 baseExpInterpResults[1] = 32.9357664233577;
125 baseExpInterpResults[2] = 47.2392335766423;
126
127 afterExpInterpResults[0] = 18.6304347826087;
128 afterExpInterpResults[1] = 32.6726277372263;
129 afterExpInterpResults[2] = 46.9760948905109;
130
131 baseNewInterpResults[0] = 23.2994565217391;
132 baseNewInterpResults[1] = 45.1627737226277;
133 baseNewInterpResults[2] = 67.9372262773723;
134
135 afterNewInterpResults[0] = 22.6146739130435;
136 afterNewInterpResults[1] = 44.7437956204380;
137 afterNewInterpResults[2] = 67.5182481751825;
138
139 expLogInterpResults[0] = 19.0648200765280;
140 expLogInterpResults[1] = 32.5497181830507;
141 expLogInterpResults[2] = 49.9589077461237;
142 }
143
144 // Give curve dates for one of the two test dates
145 vector<Date> dates(Size testDatesIdx) const {
146 vector<Date> result(curveTenors.size());
147 for (Size i = 0; i < result.size(); i++) {
148 result[i] = testDates[testDatesIdx] + curveTenors[i];
149 }
150 return result;
151 }
152
153 // Update quotes with new shifted prices
154 void updateQuotes() const {
155 for (Size i = 0; i < quotes.size(); i++) {
156 pQuotes[i]->setValue(shiftedPrices[i]);
157 }
158 }
159};
160
161// Perform some common curve checks on the first test date
162template <class I> void commonChecks(CommonData& td, InterpolatedPriceCurve<I>& priceCurve, bool isLogLinear = false) {
163 BOOST_TEST_MESSAGE("Performing common curve checks");
164
165 // Check the prices at the pillar dates
166 for (Size i = 0; i < td.dates(0).size(); i++) {
167 BOOST_CHECK_CLOSE(td.prices[i], priceCurve.price(td.dates(0)[i], td.extrapolate), td.tolerance);
168 }
169
170 // Check some interpolated & extrapolated values
171 vector<Real> expResults = isLogLinear ? td.expLogInterpResults : td.baseExpInterpResults;
172
173 for (Size i = 0; i < td.interpolationDates.size(); i++) {
174 BOOST_CHECK_CLOSE(expResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate), td.tolerance);
175 BOOST_CHECK_CLOSE(expResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate), td.tolerance);
176 }
177}
178
179} // namespace
180
181BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
182
183BOOST_AUTO_TEST_SUITE(PriceCurveTest)
184
185BOOST_AUTO_TEST_CASE(testPeriodsAndPricesCurve) {
186
187 BOOST_TEST_MESSAGE("Testing interpolated price curve built from periods and prices");
188
189 CommonData td;
190
191 // Look at the first test date
192 Settings::instance().evaluationDate() = td.testDates[0];
193
194 // Create a linearly interpolated price curve
195 InterpolatedPriceCurve<Linear> priceCurve(td.curveTenors, td.prices, td.curveDayCounter, td.currency);
196
197 // Common checks on curve
198 commonChecks(td, priceCurve, false);
199
200 // Create a loglinearly interpolated price curve
201 InterpolatedPriceCurve<LogLinear> logPriceCurve(td.curveTenors, td.prices, td.curveDayCounter, td.currency);
202
203 // Common checks on curve
204 commonChecks(td, logPriceCurve, true);
205
206 // Check linearly interpolated price curve after moving reference date
207 Settings::instance().evaluationDate() = td.testDates[1];
208
209 // Check curve reference date is now second test date
210 BOOST_CHECK_EQUAL(priceCurve.referenceDate(), td.testDates[1]);
211
212 // Requesting price by time should give the same results as previously
213 // Requesting by date should give new results (floating reference date curve)
214 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
215 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
216 td.tolerance);
217 BOOST_CHECK_CLOSE(td.afterExpInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
218 td.tolerance);
219 }
220}
221
222BOOST_AUTO_TEST_CASE(testPeriodsAndQuotesCurve) {
223
224 BOOST_TEST_MESSAGE("Testing interpolated price curve built from periods and quotes");
225
226 CommonData td;
227
228 // Look at the first test date
229 Settings::instance().evaluationDate() = td.testDates[0];
230
231 // Create a linearly interpolated price curve
232 InterpolatedPriceCurve<Linear> priceCurve(td.curveTenors, td.quotes, td.curveDayCounter, td.currency);
233
234 // Common checks on curve
235 commonChecks(td, priceCurve, false);
236
237 // Create a loglinearly interpolated price curve
238 InterpolatedPriceCurve<LogLinear> logPriceCurve(td.curveTenors, td.quotes, td.curveDayCounter, td.currency);
239
240 // Common checks on curve
241 commonChecks(td, logPriceCurve, true);
242
243 // Check linearly interpolated price curve after moving reference date
244 Settings::instance().evaluationDate() = td.testDates[1];
245
246 // Check curve reference date is now second test date
247 BOOST_CHECK_EQUAL(priceCurve.referenceDate(), td.testDates[1]);
248
249 // Requesting price by time should give the same results as previously
250 // Requesting by date should give new results (floating reference date curve)
251 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
252 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
253 td.tolerance);
254 BOOST_CHECK_CLOSE(td.afterExpInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
255 td.tolerance);
256 }
257
258 // Update quotes and check interpolations again (on this second test date)
259 td.updateQuotes();
260 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
261 BOOST_CHECK_CLOSE(td.baseNewInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
262 td.tolerance);
263 BOOST_CHECK_CLOSE(td.afterNewInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
264 td.tolerance);
265 }
266
267 // Move date back to first test date
268 Settings::instance().evaluationDate() = td.testDates[0];
269
270 // Check interpolations again with the new quotes
271 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
272 BOOST_CHECK_CLOSE(td.baseNewInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
273 td.tolerance);
274 BOOST_CHECK_CLOSE(td.baseNewInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
275 td.tolerance);
276 }
277}
278
279BOOST_AUTO_TEST_CASE(testDatesAndPricesCurve) {
280
281 BOOST_TEST_MESSAGE("Testing interpolated price curve built from dates and prices");
282
283 CommonData td;
284
285 // Look at the first test date
286 Settings::instance().evaluationDate() = td.testDates[0];
287
288 // Create a linearly interpolated price curve
289 vector<Date> dates = td.dates(0);
290 InterpolatedPriceCurve<Linear> priceCurve(dates[0], dates, td.prices, td.curveDayCounter, td.currency);
291
292 // Common checks on curve
293 commonChecks(td, priceCurve, false);
294
295 // Create a loglinearly interpolated price curve
296 InterpolatedPriceCurve<LogLinear> logPriceCurve(dates[0], dates, td.prices, td.curveDayCounter, td.currency);
297
298 // Common checks on curve
299 commonChecks(td, logPriceCurve, true);
300
301 // Check linearly interpolated price curve after moving reference date
302 Settings::instance().evaluationDate() = td.testDates[1];
303
304 // Check curve reference date is still first test date
305 BOOST_CHECK_EQUAL(priceCurve.referenceDate(), td.testDates[0]);
306
307 // Requesting price by time or date should give the same results as previously
308 // because this is a fixed reference date curve
309 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
310 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
311 td.tolerance);
312 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
313 td.tolerance);
314 }
315}
316
317BOOST_AUTO_TEST_CASE(testDatesAndQuotesCurve) {
318
319 BOOST_TEST_MESSAGE("Testing interpolated price curve built from dates and quotes");
320
321 CommonData td;
322
323 // Look at the first test date
324 Settings::instance().evaluationDate() = td.testDates[0];
325
326 // Create a linearly interpolated price curve
327 vector<Date> dates = td.dates(0);
328 InterpolatedPriceCurve<Linear> priceCurve(dates[0], dates, td.quotes, td.curveDayCounter, td.currency);
329
330 // Common checks on curve
331 commonChecks(td, priceCurve, false);
332
333 // Create a loglinearly interpolated price curve
334 InterpolatedPriceCurve<LogLinear> logPriceCurve(dates[0], dates, td.quotes, td.curveDayCounter, td.currency);
335
336 // Common checks on curve
337 commonChecks(td, logPriceCurve, true);
338
339 // Check linearly interpolated price curve after moving reference date
340 Settings::instance().evaluationDate() = td.testDates[1];
341
342 // Check curve reference date is still first test date
343 BOOST_CHECK_EQUAL(priceCurve.referenceDate(), td.testDates[0]);
344
345 // Requesting price by time or date should give the same results as previously
346 // because this is a fixed reference date curve
347 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
348 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
349 td.tolerance);
350 BOOST_CHECK_CLOSE(td.baseExpInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
351 td.tolerance);
352 }
353
354 // Update quotes and check the new interpolated values
355 td.updateQuotes();
356 for (Size i = 0; i < td.interpolationTimes.size(); i++) {
357 BOOST_CHECK_CLOSE(td.baseNewInterpResults[i], priceCurve.price(td.interpolationTimes[i], td.extrapolate),
358 td.tolerance);
359 BOOST_CHECK_CLOSE(td.baseNewInterpResults[i], priceCurve.price(td.interpolationDates[i], td.extrapolate),
360 td.tolerance);
361 }
362}
363
364BOOST_AUTO_TEST_CASE(testNoTimeZeroWorks) {
365
366 BOOST_TEST_MESSAGE("Test building with periods without a time 0 works with extrapolation on");
367
368 CommonData td;
369
370 // Look at the first test date
371 Settings::instance().evaluationDate() = td.testDates[0];
372
373 // Create the price curve after removing the time 0 pillar
374 vector<Period> tenors = td.curveTenors;
375 tenors.erase(tenors.begin());
376 td.prices.erase(td.prices.begin());
377 InterpolatedPriceCurve<Linear> priceCurve(tenors, td.prices, td.curveDayCounter, td.currency);
378
379 // Check requests for prices between first curve time ~0.5 and 0
380 BOOST_CHECK_CLOSE(15.1391304347826, priceCurve.price(0.25, td.extrapolate), td.tolerance);
381 BOOST_CHECK_CLOSE(13.5521739130435, priceCurve.price(0.0, td.extrapolate), td.tolerance);
382
383 // Test log-linear interpolation also
384 InterpolatedPriceCurve<LogLinear> logPriceCurve(tenors, td.prices, td.curveDayCounter, td.currency);
385 BOOST_CHECK_CLOSE(15.331307232214800, logPriceCurve.price(0.25, td.extrapolate), td.tolerance);
386 BOOST_CHECK_CLOSE(14.054688467053400, logPriceCurve.price(0.0, td.extrapolate), td.tolerance);
387
388 // Test that an exception is thrown when extrapolation is off
389 BOOST_CHECK_THROW(priceCurve.price(0.25, false), QuantLib::Error);
390}
391
392BOOST_AUTO_TEST_CASE(testNegativeTimeRequestThrows) {
393
394 BOOST_TEST_MESSAGE("Test that requesting a price at a time before zero throws");
395
396 CommonData td;
397
398 // Look at the first test date
399 Date today = td.testDates[0];
400 Settings::instance().evaluationDate() = today;
401
402 // Create the price curve
403 InterpolatedPriceCurve<Linear> priceCurve(td.curveTenors, td.prices, td.curveDayCounter, td.currency);
404
405 // Check requests for prices at times < 0
406 Time t = -0.5;
407 Date d = today - 1 * Weeks;
408 BOOST_CHECK_THROW(priceCurve.price(t, td.extrapolate), QuantLib::Error);
409 BOOST_CHECK_THROW(priceCurve.price(d, td.extrapolate), QuantLib::Error);
410
411 // Update evaluation date and request on today should throw similarly
412 Settings::instance().evaluationDate() = today + 1 * Weeks;
413 BOOST_CHECK_THROW(priceCurve.price(today, td.extrapolate), QuantLib::Error);
414}
415
416BOOST_AUTO_TEST_SUITE_END()
417
418BOOST_AUTO_TEST_SUITE_END()
Interpolated price curve.
Definition: pricecurve.hpp:50
QuantLib::Real price(QuantLib::Time t, bool extrapolate=false) const
BOOST_AUTO_TEST_CASE(testPeriodsAndPricesCurve)
Definition: pricecurve.cpp:185
Interpolated price curve.
Fixture that can be used at top level.