1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2003 RiskMap srl
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "termstructures.hpp"
21#include "utilities.hpp"
22#include <ql/termstructures/yield/compositezeroyieldstructure.hpp>
23#include <ql/termstructures/yield/ratehelpers.hpp>
24#include <ql/termstructures/yield/flatforward.hpp>
25#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
26#include <ql/termstructures/yield/impliedtermstructure.hpp>
27#include <ql/termstructures/yield/forwardspreadedtermstructure.hpp>
28#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
29#include <ql/time/calendars/target.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
31#include <ql/time/daycounters/actual360.hpp>
32#include <ql/time/daycounters/thirty360.hpp>
33#include <ql/math/comparison.hpp>
34#include <ql/indexes/iborindex.hpp>
35#include <ql/currency.hpp>
36#include <ql/utilities/dataformatters.hpp>
37
38using namespace QuantLib;
39using namespace boost::unit_test_framework;
40
41namespace term_structures_test {
42
43 struct Datum {
44 Integer n;
45 TimeUnit units;
46 Rate rate;
47 };
48
49 struct CommonVars {
50 // common data
51 Calendar calendar;
52 Natural settlementDays;
53 ext::shared_ptr<YieldTermStructure> termStructure;
54 ext::shared_ptr<YieldTermStructure> dummyTermStructure;
55
56 // setup
57 CommonVars() {
58 calendar = TARGET();
59 settlementDays = 2;
60 Date today = calendar.adjust(Date::todaysDate());
61 Settings::instance().evaluationDate() = today;
62 Date settlement = calendar.advance(today,n: settlementDays,unit: Days);
63 Datum depositData[] = {
64 { .n: 1, .units: Months, .rate: 4.581 },
65 { .n: 2, .units: Months, .rate: 4.573 },
66 { .n: 3, .units: Months, .rate: 4.557 },
67 { .n: 6, .units: Months, .rate: 4.496 },
68 { .n: 9, .units: Months, .rate: 4.490 }
69 };
70 Datum swapData[] = {
71 { .n: 1, .units: Years, .rate: 4.54 },
72 { .n: 5, .units: Years, .rate: 4.99 },
73 { .n: 10, .units: Years, .rate: 5.47 },
74 { .n: 20, .units: Years, .rate: 5.89 },
75 { .n: 30, .units: Years, .rate: 5.96 }
76 };
77 Size deposits = LENGTH(depositData),
78 swaps = LENGTH(swapData);
79
80 std::vector<ext::shared_ptr<RateHelper> > instruments(
81 deposits+swaps);
82 for (Size i=0; i<deposits; i++) {
83 instruments[i] = ext::shared_ptr<RateHelper>(new
84 DepositRateHelper(depositData[i].rate/100,
85 depositData[i].n*depositData[i].units,
86 settlementDays, calendar,
87 ModifiedFollowing, true,
88 Actual360()));
89 }
90 ext::shared_ptr<IborIndex> index(new IborIndex("dummy",
91 6*Months,
92 settlementDays,
93 Currency(),
94 calendar,
95 ModifiedFollowing,
96 false,
97 Actual360()));
98 for (Size i=0; i<swaps; ++i) {
99 instruments[i+deposits] = ext::shared_ptr<RateHelper>(new
100 SwapRateHelper(swapData[i].rate/100,
101 swapData[i].n*swapData[i].units,
102 calendar,
103 Annual, Unadjusted, Thirty360(Thirty360::BondBasis),
104 index));
105 }
106 termStructure = ext::shared_ptr<YieldTermStructure>(new
107 PiecewiseYieldCurve<Discount,LogLinear>(settlement,
108 instruments, Actual360()));
109 dummyTermStructure = ext::shared_ptr<YieldTermStructure>(new
110 PiecewiseYieldCurve<Discount,LogLinear>(settlement,
111 instruments, Actual360()));
112 }
113 };
114
115 Real sub(Real x, Real y) { return x - y; }
116}
117
118void TermStructureTest::testReferenceChange() {
119
120 BOOST_TEST_MESSAGE("Testing term structure against evaluation date change...");
121
122 using namespace term_structures_test;
123
124 CommonVars vars;
125
126 ext::shared_ptr<SimpleQuote> flatRate (new SimpleQuote);
127 Handle<Quote> flatRateHandle(flatRate);
128 vars.termStructure = ext::shared_ptr<YieldTermStructure>(
129 new FlatForward(vars.settlementDays, NullCalendar(),
130 flatRateHandle, Actual360()));
131 Date today = Settings::instance().evaluationDate();
132 flatRate->setValue(.03);
133 Integer days[] = { 10, 30, 60, 120, 360, 720 };
134 Size i;
135
136 std::vector<DiscountFactor> expected(LENGTH(days));
137 for (i=0; i<LENGTH(days); i++)
138 expected[i] = vars.termStructure->discount(d: today+days[i]);
139
140 Settings::instance().evaluationDate() = today+30;
141 std::vector<DiscountFactor> calculated(LENGTH(days));
142 for (i=0; i<LENGTH(days); i++)
143 calculated[i] = vars.termStructure->discount(d: today+30+days[i]);
144
145 for (i=0; i<LENGTH(days); i++) {
146 if (!close(x: expected[i],y: calculated[i]))
147 BOOST_ERROR("\n Discount at " << days[i] << " days:\n"
148 << std::setprecision(12)
149 << " before date change: " << expected[i] << "\n"
150 << " after date change: " << calculated[i]);
151 }
152}
153
154
155void TermStructureTest::testImplied() {
156
157 BOOST_TEST_MESSAGE("Testing consistency of implied term structure...");
158
159 using namespace term_structures_test;
160
161 CommonVars vars;
162
163 Real tolerance = 1.0e-10;
164 Date today = Settings::instance().evaluationDate();
165 Date newToday = today + 3*Years;
166 Date newSettlement = vars.calendar.advance(newToday,
167 n: vars.settlementDays,unit: Days);
168 Date testDate = newSettlement + 5*Years;
169 ext::shared_ptr<YieldTermStructure> implied(
170 new ImpliedTermStructure(Handle<YieldTermStructure>(vars.termStructure),
171 newSettlement));
172 DiscountFactor baseDiscount = vars.termStructure->discount(d: newSettlement);
173 DiscountFactor discount = vars.termStructure->discount(d: testDate);
174 DiscountFactor impliedDiscount = implied->discount(d: testDate);
175 if (std::fabs(x: discount - baseDiscount*impliedDiscount) > tolerance)
176 BOOST_ERROR(
177 "unable to reproduce discount from implied curve\n"
178 << std::fixed << std::setprecision(10)
179 << " calculated: " << baseDiscount*impliedDiscount << "\n"
180 << " expected: " << discount);
181}
182
183void TermStructureTest::testImpliedObs() {
184
185 BOOST_TEST_MESSAGE("Testing observability of implied term structure...");
186
187 using namespace term_structures_test;
188
189 CommonVars vars;
190
191 Date today = Settings::instance().evaluationDate();
192 Date newToday = today + 3*Years;
193 Date newSettlement = vars.calendar.advance(newToday,
194 n: vars.settlementDays,unit: Days);
195 RelinkableHandle<YieldTermStructure> h;
196 ext::shared_ptr<YieldTermStructure> implied(
197 new ImpliedTermStructure(h, newSettlement));
198 Flag flag;
199 flag.registerWith(h: implied);
200 h.linkTo(h: vars.termStructure);
201 if (!flag.isUp())
202 BOOST_ERROR("Observer was not notified of term structure change");
203}
204
205void TermStructureTest::testFSpreaded() {
206
207 BOOST_TEST_MESSAGE("Testing consistency of forward-spreaded term structure...");
208
209 using namespace term_structures_test;
210
211 CommonVars vars;
212
213 Real tolerance = 1.0e-10;
214 ext::shared_ptr<Quote> me(new SimpleQuote(0.01));
215 Handle<Quote> mh(me);
216 ext::shared_ptr<YieldTermStructure> spreaded(
217 new ForwardSpreadedTermStructure(
218 Handle<YieldTermStructure>(vars.termStructure),mh));
219 Date testDate = vars.termStructure->referenceDate() + 5*Years;
220 DayCounter tsdc = vars.termStructure->dayCounter();
221 DayCounter sprdc = spreaded->dayCounter();
222 Rate forward = vars.termStructure->forwardRate(d1: testDate, d2: testDate, resultDayCounter: tsdc,
223 comp: Continuous, freq: NoFrequency);
224 Rate spreadedForward = spreaded->forwardRate(d1: testDate, d2: testDate, resultDayCounter: sprdc,
225 comp: Continuous, freq: NoFrequency);
226 if (std::fabs(x: forward - (spreadedForward-me->value())) > tolerance)
227 BOOST_ERROR(
228 "unable to reproduce forward from spreaded curve\n"
229 << std::setprecision(10)
230 << " calculated: "
231 << io::rate(spreadedForward-me->value()) << "\n"
232 << " expected: " << io::rate(forward));
233}
234
235void TermStructureTest::testFSpreadedObs() {
236
237 BOOST_TEST_MESSAGE("Testing observability of forward-spreaded "
238 "term structure...");
239
240 using namespace term_structures_test;
241
242 CommonVars vars;
243
244 ext::shared_ptr<SimpleQuote> me(new SimpleQuote(0.01));
245 Handle<Quote> mh(me);
246 RelinkableHandle<YieldTermStructure> h; //(vars.dummyTermStructure);
247 ext::shared_ptr<YieldTermStructure> spreaded(
248 new ForwardSpreadedTermStructure(h,mh));
249 Flag flag;
250 flag.registerWith(h: spreaded);
251 h.linkTo(h: vars.termStructure);
252 if (!flag.isUp())
253 BOOST_ERROR("Observer was not notified of term structure change");
254 flag.lower();
255 me->setValue(0.005);
256 if (!flag.isUp())
257 BOOST_ERROR("Observer was not notified of spread change");
258}
259
260void TermStructureTest::testZSpreaded() {
261
262 BOOST_TEST_MESSAGE("Testing consistency of zero-spreaded term structure...");
263
264 using namespace term_structures_test;
265
266 CommonVars vars;
267
268 Real tolerance = 1.0e-10;
269 ext::shared_ptr<Quote> me(new SimpleQuote(0.01));
270 Handle<Quote> mh(me);
271 ext::shared_ptr<YieldTermStructure> spreaded(
272 new ZeroSpreadedTermStructure(
273 Handle<YieldTermStructure>(vars.termStructure),mh));
274 Date testDate = vars.termStructure->referenceDate() + 5*Years;
275 DayCounter rfdc = vars.termStructure->dayCounter();
276 Rate zero = vars.termStructure->zeroRate(d: testDate, resultDayCounter: rfdc,
277 comp: Continuous, freq: NoFrequency);
278 Rate spreadedZero = spreaded->zeroRate(d: testDate, resultDayCounter: rfdc,
279 comp: Continuous, freq: NoFrequency);
280 if (std::fabs(x: zero - (spreadedZero-me->value())) > tolerance)
281 BOOST_ERROR(
282 "unable to reproduce zero yield from spreaded curve\n"
283 << std::setprecision(10)
284 << " calculated: " << io::rate(spreadedZero-me->value()) << "\n"
285 << " expected: " << io::rate(zero));
286}
287
288void TermStructureTest::testZSpreadedObs() {
289
290 BOOST_TEST_MESSAGE("Testing observability of zero-spreaded term structure...");
291
292 using namespace term_structures_test;
293
294 CommonVars vars;
295
296 ext::shared_ptr<SimpleQuote> me(new SimpleQuote(0.01));
297 Handle<Quote> mh(me);
298 RelinkableHandle<YieldTermStructure> h(vars.dummyTermStructure);
299
300 ext::shared_ptr<YieldTermStructure> spreaded(
301 new ZeroSpreadedTermStructure(h,mh));
302 Flag flag;
303 flag.registerWith(h: spreaded);
304 h.linkTo(h: vars.termStructure);
305 if (!flag.isUp())
306 BOOST_ERROR("Observer was not notified of term structure change");
307 flag.lower();
308 me->setValue(0.005);
309 if (!flag.isUp())
310 BOOST_ERROR("Observer was not notified of spread change");
311}
312
313void TermStructureTest::testCreateWithNullUnderlying() {
314 BOOST_TEST_MESSAGE(
315 "Testing that a zero-spreaded curve can be created with "
316 "a null underlying curve...");
317
318 using namespace term_structures_test;
319
320 CommonVars vars;
321
322 Handle<Quote> spread(ext::shared_ptr<Quote>(new SimpleQuote(0.01)));
323 RelinkableHandle<YieldTermStructure> underlying;
324 // this shouldn't throw
325 ext::shared_ptr<YieldTermStructure> spreaded(
326 new ZeroSpreadedTermStructure(underlying,spread));
327 // if we do this, the curve can work.
328 underlying.linkTo(h: vars.termStructure);
329 // check that we can use it
330 spreaded->referenceDate();
331}
332
333void TermStructureTest::testLinkToNullUnderlying() {
334 BOOST_TEST_MESSAGE(
335 "Testing that an underlying curve can be relinked to "
336 "a null underlying curve...");
337
338 using namespace term_structures_test;
339
340 CommonVars vars;
341
342 Handle<Quote> spread(ext::shared_ptr<Quote>(new SimpleQuote(0.01)));
343 RelinkableHandle<YieldTermStructure> underlying(vars.termStructure);
344 ext::shared_ptr<YieldTermStructure> spreaded(
345 new ZeroSpreadedTermStructure(underlying,spread));
346 // check that we can use it
347 spreaded->referenceDate();
348 // if we do this, the curve can't work anymore. But it shouldn't
349 // throw as long as we don't try to use it.
350 underlying.linkTo(h: ext::shared_ptr<YieldTermStructure>());
351}
352
353void TermStructureTest::testCompositeZeroYieldStructures() {
354 BOOST_TEST_MESSAGE(
355 "Testing composite zero yield structures...");
356
357 using namespace term_structures_test;
358
359 Settings::instance().evaluationDate() = Date(10, Nov, 2017);
360
361 // First curve
362 std::vector<Date> dates = {Date(10, Nov, 2017), Date(13, Nov, 2017), Date(12, Feb, 2018),
363 Date(10, May, 2018), Date(10, Aug, 2018), Date(12, Nov, 2018),
364 Date(21, Dec, 2018), Date(15, Jan, 2020), Date(31, Mar, 2021),
365 Date(28, Feb, 2023), Date(21, Dec, 2026), Date(31, Jan, 2030),
366 Date(28, Feb, 2031), Date(31, Mar, 2036), Date(28, Feb, 2041),
367 Date(28, Feb, 2048), Date(31, Dec, 2141)};
368
369 std::vector<Rate> rates = {0.0655823213132524, 0.0655823213132524, 0.0699455024156877,
370 0.0799107139233497, 0.0813931951022577, 0.0841615820666691,
371 0.0501297919004145, 0.0823483583439658, 0.0860720030924466,
372 0.0922887604375688, 0.10588902278996, 0.117021968693922,
373 0.109824660896137, 0.109231572878364, 0.119218123236241,
374 0.128647300167664, 0.0506086995288751};
375
376 ext::shared_ptr<YieldTermStructure> termStructure1 = ext::shared_ptr<YieldTermStructure>(
377 new ForwardCurve(dates, rates, Actual365Fixed(), NullCalendar()));
378
379 // Second curve
380 dates = {Date(10, Nov, 2017), Date(13, Nov, 2017), Date(11, Dec, 2017), Date(12, Feb, 2018),
381 Date(10, May, 2018), Date(31, Jan, 2022), Date(7, Dec, 2023), Date(31, Jan, 2025),
382 Date(31, Mar, 2028), Date(7, Dec, 2033), Date(1, Feb, 2038), Date(2, Apr, 2046),
383 Date(2, Jan, 2051), Date(31, Dec, 2141)};
384
385 rates = {0.056656806197189, 0.056656806197189, 0.0419541633454473, 0.0286681050019797,
386 0.0148840226959593, 0.0246680238374363, 0.0255349067810599, 0.0298907184711927,
387 0.0263943927922053, 0.0291924526539802, 0.0270049276163556, 0.028775807327614,
388 0.0293567711641792, 0.010518655099659};
389
390 ext::shared_ptr<YieldTermStructure> termStructure2 = ext::shared_ptr<YieldTermStructure>(
391 new ForwardCurve(dates, rates, Actual365Fixed(), NullCalendar()));
392
393 typedef Real(*binary_f)(Real, Real);
394
395 ext::shared_ptr<YieldTermStructure> compoundCurve = ext::shared_ptr<YieldTermStructure>(
396 new CompositeZeroYieldStructure<binary_f>(Handle<YieldTermStructure>(termStructure1), Handle<YieldTermStructure>(termStructure2), sub));
397
398 // Expected values
399 dates = {Date(10, Nov, 2017), Date(15, Dec, 2017), Date(15, Jun, 2018), Date(15, Sep, 2029),
400 Date(15, Sep, 2038), Date(15, Mar, 2046), Date(15, Dec, 2141)};
401
402 rates = {0.00892551511527986, 0.0278755322562788, 0.0512001768603456, 0.0729941474263546,
403 0.0778333309498459, 0.0828451659139004, 0.0503573807521742};
404
405 Real tolerance = 1.0e-10;
406 for (Size i = 0; i < dates.size(); ++i) {
407 Rate actual = compoundCurve->zeroRate(d: dates[i], resultDayCounter: Actual365Fixed(), comp: Continuous).rate();
408 Rate expected = rates[i];
409
410 if (std::fabs(x: actual - expected) > tolerance)
411 BOOST_ERROR(
412 "unable to reproduce zero yield rate from composite input curve\n"
413 << std::fixed << std::setprecision(10)
414 << " calculated: " << actual << "\n"
415 << " expected: " << expected);
416 }
417}
418
419
420void TermStructureTest::testNullTimeToReference() {
421 BOOST_TEST_MESSAGE("Testing zero-rate calculation for null time-to-reference...");
422
423 Rate rate = 0.02;
424 auto dayCount = Thirty360(Thirty360::BondBasis);
425 auto curve = FlatForward(Date(30, August, 2023), rate, dayCount);
426
427 // the time between August 30th and 31st is null for the 30/360 day count convention
428 Rate expected = rate;
429 Rate calculated = curve.zeroRate(d: Date(31, August, 2023), resultDayCounter: dayCount, comp: Continuous);
430 Real tolerance = 1.0e-10;
431
432 if (std::fabs(x: calculated - expected) > tolerance)
433 BOOST_ERROR("unable to reproduce zero yield rate from curve\n"
434 << std::fixed << std::setprecision(10)
435 << " calculated: " << calculated << "\n"
436 << " expected: " << expected);
437}
438
439test_suite* TermStructureTest::suite() {
440 auto* suite = BOOST_TEST_SUITE("Term structure tests");
441 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testReferenceChange));
442 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testImplied));
443 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testImpliedObs));
444 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testFSpreaded));
445 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testFSpreadedObs));
446 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testZSpreaded));
447 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testZSpreadedObs));
448 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testCreateWithNullUnderlying));
449 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testLinkToNullUnderlying));
450 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testCompositeZeroYieldStructures));
451 suite->add(QUANTLIB_TEST_CASE(&TermStructureTest::testNullTimeToReference));
452 return suite;
453}
454
455

source code of quantlib/test-suite/termstructures.cpp