1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
---|---|
2 | |
3 | /* |
4 | Copyright (C) 2005, 2006, 2007, 2008, 2009, 2017 StatPro Italia 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 "piecewiseyieldcurve.hpp" |
21 | #include "utilities.hpp" |
22 | #include <ql/cashflows/iborcoupon.hpp> |
23 | #include <ql/indexes/bmaindex.hpp> |
24 | #include <ql/indexes/ibor/euribor.hpp> |
25 | #include <ql/indexes/ibor/jpylibor.hpp> |
26 | #include <ql/indexes/ibor/usdlibor.hpp> |
27 | #include <ql/instruments/forwardrateagreement.hpp> |
28 | #include <ql/instruments/makevanillaswap.hpp> |
29 | #include <ql/math/comparison.hpp> |
30 | #include <ql/math/interpolations/backwardflatinterpolation.hpp> |
31 | #include <ql/math/interpolations/convexmonotoneinterpolation.hpp> |
32 | #include <ql/math/interpolations/cubicinterpolation.hpp> |
33 | #include <ql/math/interpolations/forwardflatinterpolation.hpp> |
34 | #include <ql/math/interpolations/linearinterpolation.hpp> |
35 | #include <ql/math/interpolations/loginterpolation.hpp> |
36 | #include <ql/pricingengines/bond/discountingbondengine.hpp> |
37 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
38 | #include <ql/quotes/simplequote.hpp> |
39 | #include <ql/termstructures/globalbootstrap.hpp> |
40 | #include <ql/termstructures/yield/bondhelpers.hpp> |
41 | #include <ql/termstructures/yield/flatforward.hpp> |
42 | #include <ql/termstructures/yield/piecewiseyieldcurve.hpp> |
43 | #include <ql/termstructures/yield/ratehelpers.hpp> |
44 | #include <ql/time/asx.hpp> |
45 | #include <ql/time/calendars/japan.hpp> |
46 | #include <ql/time/calendars/jointcalendar.hpp> |
47 | #include <ql/time/calendars/target.hpp> |
48 | #include <ql/time/calendars/weekendsonly.hpp> |
49 | #include <ql/time/daycounters/actual360.hpp> |
50 | #include <ql/time/daycounters/actualactual.hpp> |
51 | #include <ql/time/daycounters/thirty360.hpp> |
52 | #include <ql/time/imm.hpp> |
53 | #include <ql/utilities/dataformatters.hpp> |
54 | #include <iomanip> |
55 | #include <map> |
56 | #include <string> |
57 | #include <utility> |
58 | #include <vector> |
59 | |
60 | using namespace QuantLib; |
61 | using namespace boost::unit_test_framework; |
62 | using std::map; |
63 | using std::vector; |
64 | using std::string; |
65 | |
66 | namespace piecewise_yield_curve_test { |
67 | |
68 | struct Datum { |
69 | Integer n; |
70 | TimeUnit units; |
71 | Rate rate; |
72 | }; |
73 | |
74 | struct BondDatum { |
75 | Integer n; |
76 | TimeUnit units; |
77 | Integer length; |
78 | Frequency frequency; |
79 | Rate coupon; |
80 | Real price; |
81 | }; |
82 | |
83 | Datum depositData[] = { |
84 | { .n: 1, .units: Weeks, .rate: 4.559 }, |
85 | { .n: 1, .units: Months, .rate: 4.581 }, |
86 | { .n: 2, .units: Months, .rate: 4.573 }, |
87 | { .n: 3, .units: Months, .rate: 4.557 }, |
88 | { .n: 6, .units: Months, .rate: 4.496 }, |
89 | { .n: 9, .units: Months, .rate: 4.490 } |
90 | }; |
91 | |
92 | Datum fraData[] = { |
93 | { .n: 1, .units: Months, .rate: 4.581 }, |
94 | { .n: 2, .units: Months, .rate: 4.573 }, |
95 | { .n: 3, .units: Months, .rate: 4.557 }, |
96 | { .n: 6, .units: Months, .rate: 4.496 }, |
97 | { .n: 9, .units: Months, .rate: 4.490 } |
98 | }; |
99 | |
100 | Datum immFutData[] = { |
101 | { .n: 1, .units: Months, .rate: 4.581 }, |
102 | { .n: 2, .units: Months, .rate: 4.573 }, |
103 | { .n: 3, .units: Months, .rate: 4.557 } |
104 | }; |
105 | |
106 | Datum asxFutData[] = { |
107 | { .n: 1, .units: Months, .rate: 4.581 }, |
108 | { .n: 2, .units: Months, .rate: 4.573 }, |
109 | { .n: 3, .units: Months, .rate: 4.557 } |
110 | }; |
111 | |
112 | Datum swapData[] = { |
113 | { .n: 1, .units: Years, .rate: 4.54 }, |
114 | { .n: 2, .units: Years, .rate: 4.63 }, |
115 | { .n: 3, .units: Years, .rate: 4.75 }, |
116 | { .n: 4, .units: Years, .rate: 4.86 }, |
117 | { .n: 5, .units: Years, .rate: 4.99 }, |
118 | { .n: 6, .units: Years, .rate: 5.11 }, |
119 | { .n: 7, .units: Years, .rate: 5.23 }, |
120 | { .n: 8, .units: Years, .rate: 5.33 }, |
121 | { .n: 9, .units: Years, .rate: 5.41 }, |
122 | { .n: 10, .units: Years, .rate: 5.47 }, |
123 | { .n: 12, .units: Years, .rate: 5.60 }, |
124 | { .n: 15, .units: Years, .rate: 5.75 }, |
125 | { .n: 20, .units: Years, .rate: 5.89 }, |
126 | { .n: 25, .units: Years, .rate: 5.95 }, |
127 | { .n: 30, .units: Years, .rate: 5.96 } |
128 | }; |
129 | |
130 | BondDatum bondData[] = { |
131 | { .n: 6, .units: Months, .length: 5, .frequency: Semiannual, .coupon: 4.75, .price: 101.320 }, |
132 | { .n: 1, .units: Years, .length: 3, .frequency: Semiannual, .coupon: 2.75, .price: 100.590 }, |
133 | { .n: 2, .units: Years, .length: 5, .frequency: Semiannual, .coupon: 5.00, .price: 105.650 }, |
134 | { .n: 5, .units: Years, .length: 11, .frequency: Semiannual, .coupon: 5.50, .price: 113.610 }, |
135 | { .n: 10, .units: Years, .length: 11, .frequency: Semiannual, .coupon: 3.75, .price: 104.070 } |
136 | }; |
137 | |
138 | Datum bmaData[] = { |
139 | { .n: 1, .units: Years, .rate: 67.56 }, |
140 | { .n: 2, .units: Years, .rate: 68.00 }, |
141 | { .n: 3, .units: Years, .rate: 68.25 }, |
142 | { .n: 4, .units: Years, .rate: 68.50 }, |
143 | { .n: 5, .units: Years, .rate: 68.81 }, |
144 | { .n: 7, .units: Years, .rate: 69.50 }, |
145 | { .n: 10, .units: Years, .rate: 70.44 }, |
146 | { .n: 15, .units: Years, .rate: 71.69 }, |
147 | { .n: 20, .units: Years, .rate: 72.69 }, |
148 | { .n: 30, .units: Years, .rate: 73.81 } |
149 | }; |
150 | |
151 | struct CommonVars { |
152 | // global variables |
153 | Calendar calendar; |
154 | Natural settlementDays; |
155 | Date today, settlement; |
156 | BusinessDayConvention fixedLegConvention; |
157 | Frequency fixedLegFrequency; |
158 | DayCounter fixedLegDayCounter; |
159 | Natural bondSettlementDays; |
160 | DayCounter bondDayCounter; |
161 | BusinessDayConvention bondConvention; |
162 | Real bondRedemption; |
163 | Frequency bmaFrequency; |
164 | BusinessDayConvention bmaConvention; |
165 | DayCounter bmaDayCounter; |
166 | |
167 | Size deposits, fras, immFuts, asxFuts, swaps, bonds, bmas; |
168 | std::vector<ext::shared_ptr<SimpleQuote> > rates, fraRates, |
169 | immFutPrices, asxFutPrices, |
170 | prices, fractions; |
171 | std::vector<ext::shared_ptr<RateHelper> > instruments, |
172 | immFutHelpers, asxFutHelpers, |
173 | bondHelpers, bmaHelpers; |
174 | |
175 | std::vector<ext::shared_ptr<RateHelper> > fraHelpers(bool useIndexedFra) const { |
176 | auto helpers = std::vector<ext::shared_ptr<RateHelper> >(fras); |
177 | auto euribor3m = ext::make_shared<Euribor3M>(); |
178 | for (Size i=0; i<fras; i++) { |
179 | Handle<Quote> r(fraRates[i]); |
180 | helpers[i] = ext::make_shared<FraRateHelper>( |
181 | args&: r, args&: fraData[i].n, args: fraData[i].n + 3, |
182 | args: euribor3m->fixingDays(), |
183 | args: euribor3m->fixingCalendar(), |
184 | args: euribor3m->businessDayConvention(), |
185 | args: euribor3m->endOfMonth(), |
186 | args: euribor3m->dayCounter(), |
187 | args: Pillar::LastRelevantDate, |
188 | args: Date(), |
189 | args&: useIndexedFra); |
190 | } |
191 | |
192 | return helpers; |
193 | } |
194 | |
195 | std::vector<Schedule> schedules; |
196 | ext::shared_ptr<YieldTermStructure> termStructure; |
197 | |
198 | // setup |
199 | CommonVars(Date evaluationDate = Date()) { |
200 | // data |
201 | calendar = TARGET(); |
202 | settlementDays = 2; |
203 | today = calendar.adjust(evaluationDate != Date() ? evaluationDate : Date::todaysDate()); |
204 | Settings::instance().evaluationDate() = today; |
205 | settlement = calendar.advance(today,n: settlementDays,unit: Days); |
206 | fixedLegConvention = Unadjusted; |
207 | fixedLegFrequency = Annual; |
208 | fixedLegDayCounter = Thirty360(Thirty360::BondBasis); |
209 | bondSettlementDays = 3; |
210 | bondDayCounter = ActualActual(ActualActual::ISDA); |
211 | bondConvention = Following; |
212 | bondRedemption = 100.0; |
213 | bmaFrequency = Quarterly; |
214 | bmaConvention = Following; |
215 | bmaDayCounter = ActualActual(ActualActual::ISDA); |
216 | |
217 | deposits = LENGTH(depositData); |
218 | fras = LENGTH(fraData); |
219 | immFuts = LENGTH(immFutData); |
220 | asxFuts = LENGTH(asxFutData); |
221 | swaps = LENGTH(swapData); |
222 | bonds = LENGTH(bondData); |
223 | bmas = LENGTH(bmaData); |
224 | |
225 | // market elements |
226 | rates = std::vector<ext::shared_ptr<SimpleQuote> >(deposits+swaps); |
227 | fraRates = std::vector<ext::shared_ptr<SimpleQuote> >(fras); |
228 | immFutPrices = std::vector<ext::shared_ptr<SimpleQuote> >(immFuts); |
229 | asxFutPrices = std::vector<ext::shared_ptr<SimpleQuote> >(asxFuts); |
230 | prices = std::vector<ext::shared_ptr<SimpleQuote> >(bonds); |
231 | fractions = std::vector<ext::shared_ptr<SimpleQuote> >(bmas); |
232 | for (Size i=0; i<deposits; i++) { |
233 | rates[i] = ext::make_shared<SimpleQuote>( |
234 | args: depositData[i].rate/100); |
235 | } |
236 | for (Size i=0; i<swaps; i++) { |
237 | rates[i+deposits] = ext::make_shared<SimpleQuote>( |
238 | args: swapData[i].rate/100); |
239 | } |
240 | for (Size i=0; i<fras; i++) { |
241 | fraRates[i] = ext::make_shared<SimpleQuote>( |
242 | args: fraData[i].rate/100); |
243 | } |
244 | for (Size i = 0; i<bonds; i++) { |
245 | prices[i] = ext::make_shared<SimpleQuote>( |
246 | args&: bondData[i].price); |
247 | } |
248 | for (Size i = 0; i<immFuts; i++) { |
249 | immFutPrices[i] = ext::make_shared<SimpleQuote>( |
250 | args: 100.0 - immFutData[i].rate); |
251 | } |
252 | for (Size i = 0; i<asxFuts; i++) { |
253 | asxFutPrices[i] = ext::make_shared<SimpleQuote>( |
254 | args: 100.0 - asxFutData[i].rate); |
255 | } |
256 | for (Size i = 0; i<bmas; i++) { |
257 | fractions[i] = ext::make_shared<SimpleQuote>( |
258 | args: bmaData[i].rate/100); |
259 | } |
260 | |
261 | // rate helpers |
262 | instruments = std::vector<ext::shared_ptr<RateHelper> >(deposits+swaps); |
263 | immFutHelpers = std::vector<ext::shared_ptr<RateHelper> >(immFuts); |
264 | asxFutHelpers = std::vector<ext::shared_ptr<RateHelper> >(); |
265 | bondHelpers = std::vector<ext::shared_ptr<RateHelper> >(bonds); |
266 | schedules = std::vector<Schedule>(bonds); |
267 | bmaHelpers = std::vector<ext::shared_ptr<RateHelper> >(bmas); |
268 | |
269 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M); |
270 | for (Size i=0; i<deposits; i++) { |
271 | Handle<Quote> r(rates[i]); |
272 | instruments[i] = ext::shared_ptr<RateHelper>(new |
273 | DepositRateHelper(r, |
274 | ext::make_shared<Euribor>( |
275 | args: depositData[i].n*depositData[i].units))); |
276 | } |
277 | for (Size i=0; i<swaps; i++) { |
278 | Handle<Quote> r(rates[i+deposits]); |
279 | instruments[i+deposits] = ext::shared_ptr<RateHelper>(new |
280 | SwapRateHelper(r, swapData[i].n*swapData[i].units, |
281 | calendar, |
282 | fixedLegFrequency, fixedLegConvention, |
283 | fixedLegDayCounter, euribor6m)); |
284 | } |
285 | |
286 | Date immDate = Date(); |
287 | auto euribor3m = ext::make_shared<Euribor3M>(); |
288 | for (Size i = 0; i<immFuts; i++) { |
289 | Handle<Quote> r(immFutPrices[i]); |
290 | immDate = IMM::nextDate(d: immDate, mainCycle: false); |
291 | // if the fixing is before the evaluation date, we |
292 | // just jump forward by one future maturity |
293 | if (euribor3m->fixingDate(valueDate: immDate) < |
294 | Settings::instance().evaluationDate()) |
295 | immDate = IMM::nextDate(d: immDate, mainCycle: false); |
296 | immFutHelpers[i] = ext::shared_ptr<RateHelper>(new |
297 | FuturesRateHelper(r, immDate, euribor3m, Handle<Quote>(), |
298 | Futures::IMM)); |
299 | } |
300 | Date asxDate = Date(); |
301 | for (Size i = 0; i<asxFuts; i++) { |
302 | Handle<Quote> r(asxFutPrices[i]); |
303 | asxDate = ASX::nextDate(d: asxDate, mainCycle: false); |
304 | // if the fixing is before the evaluation date, we |
305 | // just jump forward by one future maturity |
306 | if (euribor3m->fixingDate(valueDate: asxDate) < |
307 | Settings::instance().evaluationDate()) |
308 | asxDate = ASX::nextDate(d: asxDate, mainCycle: false); |
309 | if (euribor3m->fixingCalendar().isBusinessDay(d: asxDate)) |
310 | asxFutHelpers.push_back(x: ext::shared_ptr<RateHelper>(new |
311 | FuturesRateHelper(r, asxDate, euribor3m, |
312 | Handle<Quote>(), Futures::ASX))); |
313 | } |
314 | |
315 | for (Size i=0; i<bonds; i++) { |
316 | Handle<Quote> p(prices[i]); |
317 | Date maturity = |
318 | calendar.advance(today, n: bondData[i].n, unit: bondData[i].units); |
319 | Date issue = |
320 | calendar.advance(maturity, n: -bondData[i].length, unit: Years); |
321 | std::vector<Rate> coupons(1, bondData[i].coupon/100.0); |
322 | schedules[i] = Schedule(issue, maturity, |
323 | Period(bondData[i].frequency), |
324 | calendar, |
325 | bondConvention, bondConvention, |
326 | DateGeneration::Backward, false); |
327 | bondHelpers[i] = ext::shared_ptr<RateHelper>(new |
328 | FixedRateBondHelper(p, |
329 | bondSettlementDays, |
330 | bondRedemption, schedules[i], |
331 | coupons, bondDayCounter, |
332 | bondConvention, |
333 | bondRedemption, issue)); |
334 | } |
335 | } |
336 | }; |
337 | |
338 | |
339 | template <class T, class I, template<class C> class B> |
340 | void testCurveConsistency(CommonVars& vars, |
341 | const I& interpolator = I(), |
342 | Real tolerance = 1.0e-9) { |
343 | |
344 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
345 | PiecewiseYieldCurve<T,I,B>(vars.settlement, vars.instruments, |
346 | Actual360(), |
347 | interpolator)); |
348 | |
349 | RelinkableHandle<YieldTermStructure> curveHandle; |
350 | curveHandle.linkTo(h: vars.termStructure); |
351 | |
352 | // check deposits |
353 | for (Size i=0; i<vars.deposits; i++) { |
354 | Euribor index(depositData[i].n*depositData[i].units,curveHandle); |
355 | Rate expectedRate = depositData[i].rate/100, |
356 | estimatedRate = index.fixing(fixingDate: vars.today); |
357 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
358 | BOOST_ERROR( |
359 | depositData[i].n << " " |
360 | << (depositData[i].units == Weeks ? "week(s)": "month(s)") |
361 | << " deposit:" |
362 | << std::setprecision(8) |
363 | << "\n estimated rate: "<< io::rate(estimatedRate) |
364 | << "\n expected rate: "<< io::rate(expectedRate)); |
365 | } |
366 | } |
367 | |
368 | // check swaps |
369 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M(curveHandle)); |
370 | for (Size i=0; i<vars.swaps; i++) { |
371 | Period tenor = swapData[i].n*swapData[i].units; |
372 | |
373 | VanillaSwap swap = MakeVanillaSwap(tenor, euribor6m, 0.0) |
374 | .withEffectiveDate(vars.settlement) |
375 | .withFixedLegDayCount(dc: vars.fixedLegDayCounter) |
376 | .withFixedLegTenor(t: Period(vars.fixedLegFrequency)) |
377 | .withFixedLegConvention(bdc: vars.fixedLegConvention) |
378 | .withFixedLegTerminationDateConvention(bdc: vars.fixedLegConvention); |
379 | |
380 | Rate expectedRate = swapData[i].rate/100, |
381 | estimatedRate = swap.fairRate(); |
382 | Spread error = std::fabs(x: expectedRate-estimatedRate); |
383 | if (error > tolerance) { |
384 | BOOST_ERROR( |
385 | swapData[i].n << " year(s) swap:\n" |
386 | << std::setprecision(8) |
387 | << "\n estimated rate: "<< io::rate(estimatedRate) |
388 | << "\n expected rate: "<< io::rate(expectedRate) |
389 | << "\n error: "<< io::rate(error) |
390 | << "\n tolerance: "<< io::rate(tolerance)); |
391 | } |
392 | } |
393 | |
394 | // check bonds |
395 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
396 | PiecewiseYieldCurve<T,I,B>(vars.settlement, vars.bondHelpers, |
397 | Actual360(), |
398 | interpolator)); |
399 | curveHandle.linkTo(h: vars.termStructure); |
400 | |
401 | for (Size i=0; i<vars.bonds; i++) { |
402 | Date maturity = vars.calendar.advance(vars.today, |
403 | n: bondData[i].n, |
404 | unit: bondData[i].units); |
405 | Date issue = vars.calendar.advance(maturity, |
406 | n: -bondData[i].length, |
407 | unit: Years); |
408 | std::vector<Rate> coupons(1, bondData[i].coupon/100.0); |
409 | |
410 | FixedRateBond bond(vars.bondSettlementDays, 100.0, |
411 | vars.schedules[i], coupons, |
412 | vars.bondDayCounter, vars.bondConvention, |
413 | vars.bondRedemption, issue); |
414 | |
415 | ext::shared_ptr<PricingEngine> bondEngine( |
416 | new DiscountingBondEngine(curveHandle)); |
417 | bond.setPricingEngine(bondEngine); |
418 | |
419 | Real expectedPrice = bondData[i].price, |
420 | estimatedPrice = bond.cleanPrice(); |
421 | Real error = std::fabs(x: expectedPrice-estimatedPrice); |
422 | if (error > tolerance) { |
423 | BOOST_ERROR(io::ordinal(i+1) << " bond failure:"<< |
424 | std::setprecision(8) << |
425 | "\n estimated price: "<< estimatedPrice << |
426 | "\n expected price: "<< expectedPrice << |
427 | "\n error: "<< error); |
428 | } |
429 | } |
430 | |
431 | // check FRA, use indexed |
432 | |
433 | bool useIndexedFra = true; |
434 | ext::shared_ptr<IborIndex> euribor3m(new Euribor3M(curveHandle)); |
435 | |
436 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
437 | PiecewiseYieldCurve<T,I>(vars.settlement, vars.fraHelpers(useIndexedFra), |
438 | Actual360(), |
439 | interpolator)); |
440 | curveHandle.linkTo(h: vars.termStructure); |
441 | |
442 | for (Size i=0; i<vars.fras; i++) { |
443 | Date start = |
444 | vars.calendar.advance(vars.settlement, |
445 | n: fraData[i].n, |
446 | unit: fraData[i].units, |
447 | convention: euribor3m->businessDayConvention(), |
448 | endOfMonth: euribor3m->endOfMonth()); |
449 | BOOST_REQUIRE(fraData[i].units == Months); |
450 | |
451 | ForwardRateAgreement fra(euribor3m, start, Position::Long, |
452 | fraData[i].rate/100, 100.0, curveHandle); |
453 | Rate expectedRate = fraData[i].rate/100, |
454 | estimatedRate = fra.forwardRate(); |
455 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
456 | BOOST_ERROR(io::ordinal(i+1) << " FRA (indexed) failure:"<< |
457 | std::setprecision(8) << |
458 | "\n estimated rate: "<< io::rate(estimatedRate) << |
459 | "\n expected rate: "<< io::rate(expectedRate)); |
460 | } |
461 | } |
462 | |
463 | // check FRA, don't use indexed |
464 | |
465 | useIndexedFra = false; |
466 | |
467 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
468 | PiecewiseYieldCurve<T,I>(vars.settlement, vars.fraHelpers(useIndexedFra), |
469 | Actual360(), |
470 | interpolator)); |
471 | curveHandle.linkTo(h: vars.termStructure); |
472 | |
473 | for (Size i=0; i<vars.fras; i++) { |
474 | Date start = |
475 | vars.calendar.advance(vars.settlement, |
476 | n: fraData[i].n, |
477 | unit: fraData[i].units, |
478 | convention: euribor3m->businessDayConvention(), |
479 | endOfMonth: euribor3m->endOfMonth()); |
480 | BOOST_REQUIRE(fraData[i].units == Months); |
481 | |
482 | Date end = vars.calendar.advance(vars.settlement, n: 3 + fraData[i].n, unit: Months, |
483 | convention: euribor3m->businessDayConvention(), |
484 | endOfMonth: euribor3m->endOfMonth()); |
485 | ForwardRateAgreement fra(euribor3m, start, end, Position::Long, |
486 | fraData[i].rate/100, 100.0, curveHandle); |
487 | Rate expectedRate = fraData[i].rate/100, |
488 | estimatedRate = fra.forwardRate(); |
489 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
490 | BOOST_ERROR(io::ordinal(i+1) << " FRA (at par) failure:"<< |
491 | std::setprecision(8) << |
492 | "\n estimated rate: "<< io::rate(estimatedRate) << |
493 | "\n expected rate: "<< io::rate(expectedRate)); |
494 | } |
495 | } |
496 | |
497 | // check immFuts |
498 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
499 | PiecewiseYieldCurve<T, I>(vars.settlement, vars.immFutHelpers, |
500 | Actual360(), |
501 | interpolator)); |
502 | curveHandle.linkTo(h: vars.termStructure); |
503 | |
504 | Date immStart = Date(); |
505 | for (Size i = 0; i<vars.immFuts; i++) { |
506 | immStart = IMM::nextDate(d: immStart, mainCycle: false); |
507 | // if the fixing is before the evaluation date, we |
508 | // just jump forward by one future maturity |
509 | if (euribor3m->fixingDate(valueDate: immStart) < |
510 | Settings::instance().evaluationDate()) |
511 | immStart = IMM::nextDate(d: immStart, mainCycle: false); |
512 | |
513 | ForwardRateAgreement immFut(euribor3m, immStart, Position::Long, |
514 | immFutData[i].rate / 100, 100.0, curveHandle); |
515 | Rate expectedRate = immFutData[i].rate / 100, |
516 | estimatedRate = immFut.forwardRate(); |
517 | if (std::fabs(x: expectedRate - estimatedRate) > tolerance) { |
518 | BOOST_ERROR(io::ordinal(i + 1) << " IMM futures failure:"<< |
519 | std::setprecision(8) << |
520 | "\n estimated rate: "<< io::rate(estimatedRate) << |
521 | "\n expected rate: "<< io::rate(expectedRate)); |
522 | } |
523 | } |
524 | |
525 | // check asxFuts |
526 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
527 | PiecewiseYieldCurve<T, I>(vars.settlement, vars.asxFutHelpers, |
528 | Actual360(), |
529 | interpolator)); |
530 | curveHandle.linkTo(h: vars.termStructure); |
531 | |
532 | Date asxStart = Date(); |
533 | for (Size i = 0; i<vars.asxFuts; i++) { |
534 | asxStart = ASX::nextDate(d: asxStart, mainCycle: false); |
535 | // if the fixing is before the evaluation date, we |
536 | // just jump forward by one future maturity |
537 | if (euribor3m->fixingDate(valueDate: asxStart) < |
538 | Settings::instance().evaluationDate()) |
539 | asxStart = ASX::nextDate(d: asxStart, mainCycle: false); |
540 | if (euribor3m->fixingCalendar().isHoliday(d: asxStart)) |
541 | continue; |
542 | |
543 | ForwardRateAgreement asxFut(euribor3m, asxStart, Position::Long, |
544 | asxFutData[i].rate / 100, 100.0, curveHandle); |
545 | Rate expectedRate = asxFutData[i].rate / 100, |
546 | estimatedRate = asxFut.forwardRate(); |
547 | if (std::fabs(x: expectedRate - estimatedRate) > tolerance) { |
548 | BOOST_ERROR(io::ordinal(i + 1) << " ASX futures failure:"<< |
549 | std::setprecision(8) << |
550 | "\n estimated rate: "<< io::rate(estimatedRate) << |
551 | "\n expected rate: "<< io::rate(expectedRate)); |
552 | } |
553 | } |
554 | |
555 | // end checks |
556 | } |
557 | |
558 | template <class T, class I, template<class C> class B> |
559 | void testBMACurveConsistency(CommonVars& vars, |
560 | const I& interpolator = I(), |
561 | Real tolerance = 1.0e-9) { |
562 | |
563 | // re-adjust settlement |
564 | vars.calendar = JointCalendar(BMAIndex().fixingCalendar(), |
565 | USDLibor(3*Months).fixingCalendar(), |
566 | JoinHolidays); |
567 | vars.today = vars.calendar.adjust(Date::todaysDate()); |
568 | Settings::instance().evaluationDate() = vars.today; |
569 | vars.settlement = |
570 | vars.calendar.advance(vars.today,n: vars.settlementDays,unit: Days); |
571 | |
572 | |
573 | Handle<YieldTermStructure> riskFreeCurve( |
574 | ext::shared_ptr<YieldTermStructure>( |
575 | new FlatForward(vars.settlement, 0.04, Actual360()))); |
576 | |
577 | ext::shared_ptr<BMAIndex> bmaIndex(new BMAIndex); |
578 | ext::shared_ptr<IborIndex> liborIndex( |
579 | new USDLibor(3*Months,riskFreeCurve)); |
580 | for (Size i=0; i<vars.bmas; ++i) { |
581 | Handle<Quote> f(vars.fractions[i]); |
582 | vars.bmaHelpers[i] = ext::shared_ptr<RateHelper>( |
583 | new BMASwapRateHelper(f, bmaData[i].n*bmaData[i].units, |
584 | vars.settlementDays, |
585 | vars.calendar, |
586 | Period(vars.bmaFrequency), |
587 | vars.bmaConvention, |
588 | vars.bmaDayCounter, |
589 | bmaIndex, |
590 | liborIndex)); |
591 | } |
592 | |
593 | Weekday w = vars.today.weekday(); |
594 | Date lastWednesday = |
595 | (w >= 4) ? vars.today - (w - 4) : vars.today + (4 - w - 7); |
596 | Date lastFixing = bmaIndex->fixingCalendar().adjust(lastWednesday); |
597 | bmaIndex->addFixing(fixingDate: lastFixing, fixing: 0.03); |
598 | |
599 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
600 | PiecewiseYieldCurve<T,I,B>(vars.today, vars.bmaHelpers, |
601 | Actual360(), |
602 | interpolator)); |
603 | |
604 | RelinkableHandle<YieldTermStructure> curveHandle; |
605 | curveHandle.linkTo(h: vars.termStructure); |
606 | |
607 | // check BMA swaps |
608 | ext::shared_ptr<BMAIndex> bma(new BMAIndex(curveHandle)); |
609 | ext::shared_ptr<IborIndex> libor3m(new USDLibor(3*Months, |
610 | riskFreeCurve)); |
611 | for (Size i=0; i<vars.bmas; i++) { |
612 | Period tenor = bmaData[i].n*bmaData[i].units; |
613 | |
614 | Schedule bmaSchedule = |
615 | MakeSchedule().from(effectiveDate: vars.settlement) |
616 | .to(terminationDate: vars.settlement+tenor) |
617 | .withFrequency(vars.bmaFrequency) |
618 | .withCalendar(bma->fixingCalendar()) |
619 | .withConvention(vars.bmaConvention) |
620 | .backwards(); |
621 | Schedule liborSchedule = |
622 | MakeSchedule().from(effectiveDate: vars.settlement) |
623 | .to(terminationDate: vars.settlement+tenor) |
624 | .withTenor(libor3m->tenor()) |
625 | .withCalendar(libor3m->fixingCalendar()) |
626 | .withConvention(libor3m->businessDayConvention()) |
627 | .endOfMonth(flag: libor3m->endOfMonth()) |
628 | .backwards(); |
629 | |
630 | |
631 | BMASwap swap(Swap::Payer, 100.0, |
632 | liborSchedule, 0.75, 0.0, |
633 | libor3m, libor3m->dayCounter(), |
634 | bmaSchedule, bma, vars.bmaDayCounter); |
635 | swap.setPricingEngine(ext::shared_ptr<PricingEngine>( |
636 | new DiscountingSwapEngine(libor3m->forwardingTermStructure()))); |
637 | |
638 | Real expectedFraction = bmaData[i].rate/100, |
639 | estimatedFraction = swap.fairLiborFraction(); |
640 | Real error = std::fabs(x: expectedFraction-estimatedFraction); |
641 | if (error > tolerance) { |
642 | BOOST_ERROR(bmaData[i].n << " year(s) BMA swap:\n" |
643 | << std::setprecision(8) |
644 | << "\n estimated libor fraction: "<< estimatedFraction |
645 | << "\n expected libor fraction: "<< expectedFraction |
646 | << "\n error: "<< error |
647 | << "\n tolerance: "<< tolerance); |
648 | } |
649 | } |
650 | } |
651 | |
652 | } |
653 | |
654 | |
655 | void PiecewiseYieldCurveTest::testLogCubicDiscountConsistency() { |
656 | |
657 | BOOST_TEST_MESSAGE( |
658 | "Testing consistency of piecewise-log-cubic discount curve..."); |
659 | |
660 | using namespace piecewise_yield_curve_test; |
661 | |
662 | CommonVars vars; |
663 | |
664 | testCurveConsistency<Discount,LogCubic,IterativeBootstrap>( |
665 | vars, |
666 | interpolator: MonotonicLogCubic()); |
667 | testBMACurveConsistency<Discount,LogCubic,IterativeBootstrap>( |
668 | vars, |
669 | interpolator: MonotonicLogCubic()); |
670 | } |
671 | |
672 | void PiecewiseYieldCurveTest::testLogLinearDiscountConsistency() { |
673 | |
674 | BOOST_TEST_MESSAGE( |
675 | "Testing consistency of piecewise-log-linear discount curve..."); |
676 | |
677 | using namespace piecewise_yield_curve_test; |
678 | |
679 | CommonVars vars; |
680 | |
681 | testCurveConsistency<Discount,LogLinear,IterativeBootstrap>(vars); |
682 | testBMACurveConsistency<Discount,LogLinear,IterativeBootstrap>(vars); |
683 | } |
684 | |
685 | void PiecewiseYieldCurveTest::testLinearDiscountConsistency() { |
686 | |
687 | BOOST_TEST_MESSAGE( |
688 | "Testing consistency of piecewise-linear discount curve..."); |
689 | |
690 | using namespace piecewise_yield_curve_test; |
691 | |
692 | CommonVars vars; |
693 | |
694 | testCurveConsistency<Discount,Linear,IterativeBootstrap>(vars); |
695 | testBMACurveConsistency<Discount,Linear,IterativeBootstrap>(vars); |
696 | } |
697 | |
698 | void PiecewiseYieldCurveTest::testLinearZeroConsistency() { |
699 | |
700 | BOOST_TEST_MESSAGE( |
701 | "Testing consistency of piecewise-linear zero-yield curve..."); |
702 | |
703 | using namespace piecewise_yield_curve_test; |
704 | |
705 | CommonVars vars; |
706 | |
707 | testCurveConsistency<ZeroYield,Linear,IterativeBootstrap>(vars); |
708 | testBMACurveConsistency<ZeroYield,Linear,IterativeBootstrap>(vars); |
709 | } |
710 | |
711 | void PiecewiseYieldCurveTest::testSplineZeroConsistency() { |
712 | |
713 | BOOST_TEST_MESSAGE( |
714 | "Testing consistency of piecewise-cubic zero-yield curve..."); |
715 | |
716 | using namespace piecewise_yield_curve_test; |
717 | |
718 | CommonVars vars; |
719 | |
720 | testCurveConsistency<ZeroYield,Cubic,IterativeBootstrap>( |
721 | vars, |
722 | interpolator: Cubic(CubicInterpolation::Spline, true, |
723 | CubicInterpolation::SecondDerivative, 0.0, |
724 | CubicInterpolation::SecondDerivative, 0.0)); |
725 | testBMACurveConsistency<ZeroYield,Cubic,IterativeBootstrap>( |
726 | vars, |
727 | interpolator: Cubic(CubicInterpolation::Spline, true, |
728 | CubicInterpolation::SecondDerivative, 0.0, |
729 | CubicInterpolation::SecondDerivative, 0.0)); |
730 | } |
731 | |
732 | void PiecewiseYieldCurveTest::testLinearForwardConsistency() { |
733 | |
734 | BOOST_TEST_MESSAGE( |
735 | "Testing consistency of piecewise-linear forward-rate curve..."); |
736 | |
737 | using namespace piecewise_yield_curve_test; |
738 | |
739 | CommonVars vars; |
740 | |
741 | testCurveConsistency<ForwardRate,Linear,IterativeBootstrap>(vars); |
742 | testBMACurveConsistency<ForwardRate,Linear,IterativeBootstrap>(vars); |
743 | } |
744 | |
745 | void PiecewiseYieldCurveTest::testFlatForwardConsistency() { |
746 | |
747 | BOOST_TEST_MESSAGE( |
748 | "Testing consistency of piecewise-flat forward-rate curve..."); |
749 | |
750 | using namespace piecewise_yield_curve_test; |
751 | |
752 | CommonVars vars; |
753 | |
754 | testCurveConsistency<ForwardRate,BackwardFlat,IterativeBootstrap>(vars); |
755 | testBMACurveConsistency<ForwardRate,BackwardFlat,IterativeBootstrap>(vars); |
756 | } |
757 | |
758 | void PiecewiseYieldCurveTest::testSplineForwardConsistency() { |
759 | |
760 | BOOST_TEST_MESSAGE( |
761 | "Testing consistency of piecewise-cubic forward-rate curve..."); |
762 | |
763 | using namespace piecewise_yield_curve_test; |
764 | |
765 | CommonVars vars; |
766 | |
767 | testCurveConsistency<ForwardRate,Cubic,IterativeBootstrap>( |
768 | vars, |
769 | interpolator: Cubic(CubicInterpolation::Spline, true, |
770 | CubicInterpolation::SecondDerivative, 0.0, |
771 | CubicInterpolation::SecondDerivative, 0.0)); |
772 | testBMACurveConsistency<ForwardRate,Cubic,IterativeBootstrap>( |
773 | vars, |
774 | interpolator: Cubic(CubicInterpolation::Spline, true, |
775 | CubicInterpolation::SecondDerivative, 0.0, |
776 | CubicInterpolation::SecondDerivative, 0.0)); |
777 | } |
778 | |
779 | void PiecewiseYieldCurveTest::testConvexMonotoneForwardConsistency() { |
780 | BOOST_TEST_MESSAGE( |
781 | "Testing consistency of convex monotone forward-rate curve..."); |
782 | |
783 | using namespace piecewise_yield_curve_test; |
784 | |
785 | CommonVars vars; |
786 | testCurveConsistency<ForwardRate,ConvexMonotone,IterativeBootstrap>(vars); |
787 | |
788 | testBMACurveConsistency<ForwardRate,ConvexMonotone, |
789 | IterativeBootstrap>(vars); |
790 | } |
791 | |
792 | |
793 | void PiecewiseYieldCurveTest::testLocalBootstrapConsistency() { |
794 | BOOST_TEST_MESSAGE( |
795 | "Testing consistency of local-bootstrap algorithm..."); |
796 | |
797 | using namespace piecewise_yield_curve_test; |
798 | |
799 | CommonVars vars; |
800 | testCurveConsistency<ForwardRate,ConvexMonotone,LocalBootstrap>( |
801 | vars, interpolator: ConvexMonotone(), tolerance: 1.0e-6); |
802 | testBMACurveConsistency<ForwardRate,ConvexMonotone,LocalBootstrap>( |
803 | vars, interpolator: ConvexMonotone(), tolerance: 1.0e-7); |
804 | } |
805 | |
806 | void PiecewiseYieldCurveTest::testParFraRegression() { |
807 | BOOST_TEST_MESSAGE("Testing regression for at-par FRA..."); |
808 | |
809 | using namespace piecewise_yield_curve_test; |
810 | |
811 | CommonVars vars(Date(23, February, 2023)); |
812 | |
813 | bool useIndexedFra = false; |
814 | RelinkableHandle<YieldTermStructure> curveHandle; |
815 | auto euribor3m = ext::make_shared<Euribor3M>(args&: curveHandle); |
816 | |
817 | vars.termStructure = ext::make_shared<PiecewiseYieldCurve<ZeroYield, Linear>>( |
818 | args&: vars.settlement, args: vars.fraHelpers(useIndexedFra), args: Actual360()); |
819 | curveHandle.linkTo(h: vars.termStructure); |
820 | |
821 | for (Size i=0; i<vars.fras; i++) { |
822 | Date start = vars.calendar.advance(vars.settlement, |
823 | n: fraData[i].n, |
824 | unit: fraData[i].units, |
825 | convention: euribor3m->businessDayConvention(), |
826 | endOfMonth: euribor3m->endOfMonth()); |
827 | BOOST_REQUIRE(fraData[i].units == Months); |
828 | |
829 | Date end = vars.calendar.advance(vars.settlement, n: 3 + fraData[i].n, unit: Months, |
830 | convention: euribor3m->businessDayConvention(), |
831 | endOfMonth: euribor3m->endOfMonth()); |
832 | ForwardRateAgreement fra(euribor3m, start, end, Position::Long, |
833 | fraData[i].rate/100, 100.0, curveHandle); |
834 | Rate expectedRate = fraData[i].rate/100; |
835 | Rate estimatedRate = fra.forwardRate(); |
836 | Real tolerance = 1.0e-6; |
837 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
838 | BOOST_ERROR(io::ordinal(i+1) << " FRA (at par) failure:"<< |
839 | std::setprecision(8) << |
840 | "\n estimated rate: "<< io::rate(estimatedRate) << |
841 | "\n expected rate: "<< io::rate(expectedRate)); |
842 | } |
843 | } |
844 | } |
845 | |
846 | void PiecewiseYieldCurveTest::testObservability() { |
847 | |
848 | BOOST_TEST_MESSAGE("Testing observability of piecewise yield curve..."); |
849 | |
850 | using namespace piecewise_yield_curve_test; |
851 | |
852 | CommonVars vars; |
853 | |
854 | vars.termStructure = ext::shared_ptr<YieldTermStructure>( |
855 | new PiecewiseYieldCurve<Discount,LogLinear>(vars.settlementDays, |
856 | vars.calendar, |
857 | vars.instruments, |
858 | Actual360())); |
859 | |
860 | ext::dynamic_pointer_cast<LazyObject>(r: vars.termStructure)->forwardFirstNotificationOnly(); |
861 | |
862 | Flag f; |
863 | f.registerWith(h: vars.termStructure); |
864 | |
865 | for (Size i=0; i<vars.deposits+vars.swaps; i++) { |
866 | Time testTime = |
867 | Actual360().yearFraction(d1: vars.settlement, |
868 | d2: vars.instruments[i]->pillarDate()); |
869 | DiscountFactor discount = vars.termStructure->discount(t: testTime); |
870 | f.lower(); |
871 | vars.rates[i]->setValue(vars.rates[i]->value()*1.01); |
872 | if (!f.isUp()) |
873 | BOOST_FAIL("Observer was not notified of underlying rate change"); |
874 | if (vars.termStructure->discount(t: testTime,extrapolate: true) == discount) |
875 | BOOST_FAIL("rate change did not trigger recalculation"); |
876 | vars.rates[i]->setValue(vars.rates[i]->value()/1.01); |
877 | } |
878 | |
879 | vars.termStructure->maxDate(); |
880 | f.lower(); |
881 | Settings::instance().evaluationDate() = |
882 | vars.calendar.advance(vars.today,n: 15,unit: Days); |
883 | if (!f.isUp()) |
884 | BOOST_FAIL("Observer was not notified of date change"); |
885 | |
886 | f.lower(); |
887 | Settings::instance().evaluationDate() = vars.today; |
888 | if (f.isUp()) |
889 | BOOST_FAIL("Observer was notified of date change" |
890 | " without an intervening recalculation"); |
891 | } |
892 | |
893 | |
894 | void PiecewiseYieldCurveTest::testLiborFixing() { |
895 | |
896 | BOOST_TEST_MESSAGE( |
897 | "Testing use of today's LIBOR fixings in swap curve..."); |
898 | |
899 | using namespace piecewise_yield_curve_test; |
900 | |
901 | CommonVars vars; |
902 | |
903 | std::vector<ext::shared_ptr<RateHelper> > swapHelpers(vars.swaps); |
904 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M); |
905 | |
906 | for (Size i=0; i<vars.swaps; i++) { |
907 | Handle<Quote> r(vars.rates[i+vars.deposits]); |
908 | swapHelpers[i] = ext::shared_ptr<RateHelper>(new |
909 | SwapRateHelper(r, Period(swapData[i].n, swapData[i].units), |
910 | vars.calendar, |
911 | vars.fixedLegFrequency, vars.fixedLegConvention, |
912 | vars.fixedLegDayCounter, euribor6m)); |
913 | } |
914 | |
915 | vars.termStructure = ext::shared_ptr<YieldTermStructure>(new |
916 | PiecewiseYieldCurve<Discount,LogLinear>(vars.settlement, |
917 | swapHelpers, |
918 | Actual360())); |
919 | |
920 | Handle<YieldTermStructure> curveHandle = |
921 | Handle<YieldTermStructure>(vars.termStructure); |
922 | |
923 | ext::shared_ptr<IborIndex> index(new Euribor6M(curveHandle)); |
924 | for (Size i=0; i<vars.swaps; i++) { |
925 | Period tenor = swapData[i].n*swapData[i].units; |
926 | |
927 | VanillaSwap swap = MakeVanillaSwap(tenor, index, 0.0) |
928 | .withEffectiveDate(vars.settlement) |
929 | .withFixedLegDayCount(dc: vars.fixedLegDayCounter) |
930 | .withFixedLegTenor(t: Period(vars.fixedLegFrequency)) |
931 | .withFixedLegConvention(bdc: vars.fixedLegConvention) |
932 | .withFixedLegTerminationDateConvention(bdc: vars.fixedLegConvention); |
933 | |
934 | Rate expectedRate = swapData[i].rate/100, |
935 | estimatedRate = swap.fairRate(); |
936 | Real tolerance = 1.0e-9; |
937 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
938 | BOOST_ERROR("before LIBOR fixing:\n" |
939 | << swapData[i].n << " year(s) swap:\n" |
940 | << std::setprecision(8) |
941 | << " estimated rate: " |
942 | << io::rate(estimatedRate) << "\n" |
943 | << " expected rate: " |
944 | << io::rate(expectedRate)); |
945 | } |
946 | } |
947 | |
948 | Flag f; |
949 | f.registerWith(h: vars.termStructure); |
950 | f.lower(); |
951 | |
952 | index->addFixing(fixingDate: vars.today, fixing: 0.0425); |
953 | |
954 | if (!f.isUp()) |
955 | BOOST_ERROR("Observer was not notified of rate fixing"); |
956 | |
957 | for (Size i=0; i<vars.swaps; i++) { |
958 | Period tenor = swapData[i].n*swapData[i].units; |
959 | |
960 | VanillaSwap swap = MakeVanillaSwap(tenor, index, 0.0) |
961 | .withEffectiveDate(vars.settlement) |
962 | .withFixedLegDayCount(dc: vars.fixedLegDayCounter) |
963 | .withFixedLegTenor(t: Period(vars.fixedLegFrequency)) |
964 | .withFixedLegConvention(bdc: vars.fixedLegConvention) |
965 | .withFixedLegTerminationDateConvention(bdc: vars.fixedLegConvention); |
966 | |
967 | Rate expectedRate = swapData[i].rate/100, |
968 | estimatedRate = swap.fairRate(); |
969 | Real tolerance = 1.0e-9; |
970 | if (std::fabs(x: expectedRate-estimatedRate) > tolerance) { |
971 | BOOST_ERROR("after LIBOR fixing:\n" |
972 | << swapData[i].n << " year(s) swap:\n" |
973 | << std::setprecision(8) |
974 | << " estimated rate: " |
975 | << io::rate(estimatedRate) << "\n" |
976 | << " expected rate: " |
977 | << io::rate(expectedRate)); |
978 | } |
979 | } |
980 | } |
981 | |
982 | void PiecewiseYieldCurveTest::testJpyLibor() { |
983 | BOOST_TEST_MESSAGE( |
984 | "Testing bootstrap over JPY LIBOR swaps..."); |
985 | |
986 | using namespace piecewise_yield_curve_test; |
987 | |
988 | CommonVars vars; |
989 | |
990 | vars.today = Date(4, October, 2007); |
991 | Settings::instance().evaluationDate() = vars.today; |
992 | |
993 | vars.calendar = Japan(); |
994 | vars.settlement = |
995 | vars.calendar.advance(vars.today,n: vars.settlementDays,unit: Days); |
996 | |
997 | // market elements |
998 | vars.rates = std::vector<ext::shared_ptr<SimpleQuote> >(vars.swaps); |
999 | for (Size i=0; i<vars.swaps; i++) { |
1000 | vars.rates[i] = ext::make_shared<SimpleQuote>( |
1001 | args: swapData[i].rate/100); |
1002 | } |
1003 | |
1004 | // rate helpers |
1005 | vars.instruments = std::vector<ext::shared_ptr<RateHelper> >(vars.swaps); |
1006 | |
1007 | ext::shared_ptr<IborIndex> index(new JPYLibor(6*Months)); |
1008 | for (Size i=0; i<vars.swaps; i++) { |
1009 | Handle<Quote> r(vars.rates[i]); |
1010 | vars.instruments[i] = ext::shared_ptr<RateHelper>( |
1011 | new SwapRateHelper(r, swapData[i].n*swapData[i].units, |
1012 | vars.calendar, |
1013 | vars.fixedLegFrequency, vars.fixedLegConvention, |
1014 | vars.fixedLegDayCounter, index)); |
1015 | } |
1016 | |
1017 | vars.termStructure = ext::shared_ptr<YieldTermStructure>( |
1018 | new PiecewiseYieldCurve<Discount,LogLinear>( |
1019 | vars.settlement, vars.instruments, |
1020 | Actual360())); |
1021 | |
1022 | RelinkableHandle<YieldTermStructure> curveHandle; |
1023 | curveHandle.linkTo(h: vars.termStructure); |
1024 | |
1025 | // check swaps |
1026 | ext::shared_ptr<IborIndex> jpylibor6m(new JPYLibor(6*Months,curveHandle)); |
1027 | for (Size i=0; i<vars.swaps; i++) { |
1028 | Period tenor = swapData[i].n*swapData[i].units; |
1029 | |
1030 | VanillaSwap swap = MakeVanillaSwap(tenor, jpylibor6m, 0.0) |
1031 | .withEffectiveDate(vars.settlement) |
1032 | .withFixedLegDayCount(dc: vars.fixedLegDayCounter) |
1033 | .withFixedLegTenor(t: Period(vars.fixedLegFrequency)) |
1034 | .withFixedLegConvention(bdc: vars.fixedLegConvention) |
1035 | .withFixedLegTerminationDateConvention(bdc: vars.fixedLegConvention) |
1036 | .withFixedLegCalendar(cal: vars.calendar) |
1037 | .withFloatingLegCalendar(cal: vars.calendar); |
1038 | |
1039 | Rate expectedRate = swapData[i].rate/100, |
1040 | estimatedRate = swap.fairRate(); |
1041 | Spread error = std::fabs(x: expectedRate-estimatedRate); |
1042 | Real tolerance = 1.0e-9; |
1043 | |
1044 | if (error > tolerance) { |
1045 | BOOST_ERROR(swapData[i].n << " year(s) swap:\n" |
1046 | << std::setprecision(8) |
1047 | << "\n estimated rate: "<< io::rate(estimatedRate) |
1048 | << "\n expected rate: "<< io::rate(expectedRate) |
1049 | << "\n error: "<< io::rate(error) |
1050 | << "\n tolerance: "<< io::rate(tolerance)); |
1051 | } |
1052 | } |
1053 | } |
1054 | |
1055 | void PiecewiseYieldCurveTest::testDefaultInstantiation() { |
1056 | |
1057 | BOOST_TEST_MESSAGE("Testing instantiation of curves without passing an interpolator..."); |
1058 | |
1059 | using namespace piecewise_yield_curve_test; |
1060 | |
1061 | CommonVars vars; |
1062 | |
1063 | // no actual tests at runtime; this tests that all these instantiations compile |
1064 | PiecewiseYieldCurve<Discount, Linear> linear(vars.settlement, vars.instruments, Actual360()); |
1065 | PiecewiseYieldCurve<Discount, LogLinear> log_linear(vars.settlement, vars.instruments, Actual360()); |
1066 | PiecewiseYieldCurve<Discount, Cubic> cubic(vars.settlement, vars.instruments, Actual360()); |
1067 | PiecewiseYieldCurve<Discount, DefaultLogCubic> log_cubic(vars.settlement, vars.instruments, Actual360()); |
1068 | PiecewiseYieldCurve<Discount, MonotonicLogCubic> monotonic_log_cubic(vars.settlement, vars.instruments, Actual360()); |
1069 | PiecewiseYieldCurve<Discount, KrugerLog> kruger_log_cubic(vars.settlement, vars.instruments, Actual360()); |
1070 | PiecewiseYieldCurve<ForwardRate, BackwardFlat> backward(vars.settlement, vars.instruments, Actual360()); |
1071 | PiecewiseYieldCurve<ForwardRate, ForwardFlat> forward(vars.settlement, vars.instruments, Actual360()); |
1072 | PiecewiseYieldCurve<ForwardRate, ConvexMonotone> convex(vars.settlement, vars.instruments, Actual360()); |
1073 | } |
1074 | |
1075 | void PiecewiseYieldCurveTest::testSwapRateHelperLastRelevantDate() { |
1076 | BOOST_TEST_MESSAGE("Testing SwapRateHelper last relevant date..."); |
1077 | |
1078 | Settings::instance().evaluationDate() = Date(22, Dec, 2016); |
1079 | Date today = Settings::instance().evaluationDate(); |
1080 | |
1081 | Handle<YieldTermStructure> flat3m( |
1082 | ext::make_shared<FlatForward>(args&: today, args: Handle<Quote>(ext::make_shared<SimpleQuote>(args: 0.02)), args: Actual365Fixed())); |
1083 | ext::shared_ptr<IborIndex> usdLibor3m = ext::make_shared<USDLibor>(args: 3 * Months, args&: flat3m); |
1084 | |
1085 | // note that the calendar should be US+UK here actually, but technically it should also work with |
1086 | // the US calendar only |
1087 | ext::shared_ptr<RateHelper> helper = ext::make_shared<SwapRateHelper>( |
1088 | args: 0.02, args: 50 * Years, args: UnitedStates(UnitedStates::GovernmentBond), args: Semiannual, args: ModifiedFollowing, |
1089 | args: Thirty360(Thirty360::BondBasis), args&: usdLibor3m); |
1090 | |
1091 | PiecewiseYieldCurve<Discount, LogLinear> curve(today, std::vector<ext::shared_ptr<RateHelper> >(1, helper), |
1092 | Actual365Fixed()); |
1093 | BOOST_CHECK_NO_THROW(curve.discount(1.0)); |
1094 | } |
1095 | |
1096 | void PiecewiseYieldCurveTest::testSwapRateHelperSpotDate() { |
1097 | BOOST_TEST_MESSAGE("Testing SwapRateHelper spot date..."); |
1098 | |
1099 | ext::shared_ptr<IborIndex> usdLibor3m = ext::make_shared<USDLibor>(args: 3 * Months); |
1100 | |
1101 | ext::shared_ptr<SwapRateHelper> helper = ext::make_shared<SwapRateHelper>( |
1102 | args: 0.02, args: 5 * Years, args: UnitedStates(UnitedStates::GovernmentBond), args: Semiannual, args: ModifiedFollowing, |
1103 | args: Thirty360(Thirty360::BondBasis), args&: usdLibor3m); |
1104 | |
1105 | Settings::instance().evaluationDate() = Date(11, October, 2019); |
1106 | |
1107 | // Advancing 2 days on the US calendar would yield October 16th (because October 14th |
1108 | // is Columbus day), but the LIBOR spot is calculated advancing on the UK calendar, |
1109 | // resulting in October 15th which is also a business day for the US calendar. |
1110 | Date expected = Date(15, October, 2019); |
1111 | Date calculated = helper->swap()->startDate(); |
1112 | if (calculated != expected) |
1113 | BOOST_ERROR("expected spot date: "<< expected << "\n" |
1114 | "calculated: "<< calculated); |
1115 | |
1116 | // Settings::instance().evaluationDate() = Date(1, July, 2020); |
1117 | |
1118 | // TODO: July 3rd is holiday in the US, but not for LIBOR purposes. This should probably |
1119 | // be considered when building the schedule. |
1120 | // expected = Date(3, July, 2020); |
1121 | // calculated = helper->swap()->startDate(); |
1122 | // if (calculated != expected) |
1123 | // BOOST_ERROR("expected spot date: " << expected << "\n" |
1124 | // "calculated: " << calculated); |
1125 | } |
1126 | |
1127 | void PiecewiseYieldCurveTest::testBadPreviousCurve() { |
1128 | BOOST_TEST_MESSAGE("Testing bootstrap starting from bad guess..."); |
1129 | |
1130 | using namespace piecewise_yield_curve_test; |
1131 | |
1132 | Datum data[] = { |
1133 | { .n: 1, .units: Weeks, .rate: -0.003488 }, |
1134 | { .n: 2, .units: Weeks, .rate: -0.0033 }, |
1135 | { .n: 6, .units: Months, .rate: -0.00339 }, |
1136 | { .n: 2, .units: Years, .rate: -0.00336 }, |
1137 | { .n: 8, .units: Years, .rate: 0.00302 }, |
1138 | { .n: 50, .units: Years, .rate: 0.01185 } |
1139 | }; |
1140 | |
1141 | std::vector<ext::shared_ptr<RateHelper> > helpers; |
1142 | ext::shared_ptr<Euribor> euribor1m(new Euribor1M); |
1143 | for (auto& i : data) { |
1144 | helpers.push_back(x: ext::make_shared<SwapRateHelper>( |
1145 | args&: i.rate, args: Period(i.n, i.units), args: TARGET(), args: Monthly, args: Unadjusted, |
1146 | args: Thirty360(Thirty360::BondBasis), args&: euribor1m)); |
1147 | } |
1148 | |
1149 | Date today = Date(12, October, 2017); |
1150 | Date test_date = Date(16, December, 2016); |
1151 | |
1152 | Settings::instance().evaluationDate() = today; |
1153 | |
1154 | ext::shared_ptr<YieldTermStructure> curve = |
1155 | ext::make_shared<PiecewiseYieldCurve<ForwardRate, BackwardFlat> >( |
1156 | args&: test_date, args&: helpers, args: Actual360()); |
1157 | |
1158 | // force bootstrap on today's date, so we have a previous curve... |
1159 | curve->discount(t: 1.0); |
1160 | |
1161 | // ...then move to a date where the previous curve is a bad guess. |
1162 | Settings::instance().evaluationDate() = test_date; |
1163 | |
1164 | RelinkableHandle<YieldTermStructure> h; |
1165 | h.linkTo(h: curve); |
1166 | |
1167 | ext::shared_ptr<Euribor1M> index = ext::make_shared<Euribor1M>(args&: h); |
1168 | for (auto& i : data) { |
1169 | Period tenor = i.n * i.units; |
1170 | |
1171 | VanillaSwap swap = MakeVanillaSwap(tenor, index, 0.0) |
1172 | .withFixedLegDayCount(dc: Thirty360(Thirty360::BondBasis)) |
1173 | .withFixedLegTenor(t: Period(1, Months)) |
1174 | .withFixedLegConvention(bdc: Unadjusted); |
1175 | swap.setPricingEngine(ext::make_shared<DiscountingSwapEngine>(args&: h)); |
1176 | |
1177 | Rate expectedRate = i.rate, estimatedRate = swap.fairRate(); |
1178 | Spread error = std::fabs(x: expectedRate-estimatedRate); |
1179 | Real tolerance = 1.0e-9; |
1180 | if (error > tolerance) { |
1181 | BOOST_ERROR(tenor << " swap:\n" |
1182 | << std::setprecision(8) |
1183 | << "\n estimated rate: "<< io::rate(estimatedRate) |
1184 | << "\n expected rate: "<< io::rate(expectedRate) |
1185 | << "\n error: "<< io::rate(error) |
1186 | << "\n tolerance: "<< io::rate(tolerance)); |
1187 | } |
1188 | } |
1189 | } |
1190 | |
1191 | void PiecewiseYieldCurveTest::testConstructionWithExplicitBootstrap() { |
1192 | |
1193 | BOOST_TEST_MESSAGE("Testing that construction with an explicit bootstrap succeeds..."); |
1194 | |
1195 | using namespace piecewise_yield_curve_test; |
1196 | |
1197 | CommonVars vars; |
1198 | |
1199 | // With an explicit IterativeBootstrap object |
1200 | typedef PiecewiseYieldCurve<ForwardRate, Linear, IterativeBootstrap> PwLinearForward; |
1201 | ext::shared_ptr<YieldTermStructure> yts = |
1202 | ext::make_shared<PwLinearForward>( |
1203 | args&: vars.settlement, args&: vars.instruments, args: Actual360(), args: Linear(), |
1204 | args: PwLinearForward::bootstrap_type()); |
1205 | |
1206 | // Check anything to show that the construction succeeded |
1207 | BOOST_CHECK_NO_THROW(yts->discount(1.0, true)); |
1208 | |
1209 | // With an explicit LocalBootstrap object |
1210 | typedef PiecewiseYieldCurve<ForwardRate, ConvexMonotone, LocalBootstrap> PwCmForward; |
1211 | yts = ext::make_shared<PwCmForward>( |
1212 | args&: vars.settlement, args&: vars.instruments, args: Actual360(), args: ConvexMonotone(), |
1213 | args: PwCmForward::bootstrap_type()); |
1214 | |
1215 | BOOST_CHECK_NO_THROW(yts->discount(1.0, true)); |
1216 | } |
1217 | |
1218 | void PiecewiseYieldCurveTest::testLargeRates() { |
1219 | BOOST_TEST_MESSAGE("Testing bootstrap with large input rates..."); |
1220 | |
1221 | using namespace piecewise_yield_curve_test; |
1222 | |
1223 | Datum data[] = { |
1224 | { .n: 1, .units: Weeks, .rate: 2.418633 }, |
1225 | { .n: 2, .units: Weeks, .rate: 1.361540 }, |
1226 | { .n: 3, .units: Weeks, .rate: 1.195362 }, |
1227 | { .n: 1, .units: Months, .rate: 0.829009 } |
1228 | }; |
1229 | |
1230 | std::vector<ext::shared_ptr<RateHelper> > helpers; |
1231 | for (auto& i : data) { |
1232 | helpers.push_back(x: ext::make_shared<DepositRateHelper>( |
1233 | args&: i.rate, args: Period(i.n, i.units), args: 0, args: WeekendsOnly(), args: Following, args: false, args: Actual360())); |
1234 | } |
1235 | |
1236 | Date today = Date(12, October, 2017); |
1237 | |
1238 | Settings::instance().evaluationDate() = today; |
1239 | |
1240 | Real accuracy = Null<Real>(); // use the default |
1241 | Real minValue = Null<Real>(); // use the default |
1242 | Real maxValue = 3.0; // override |
1243 | |
1244 | typedef PiecewiseYieldCurve<ForwardRate, BackwardFlat> PiecewiseCurve; |
1245 | ext::shared_ptr<YieldTermStructure> curve = |
1246 | ext::make_shared<PiecewiseCurve>( |
1247 | args&: today, args&: helpers, args: Actual360(), args: BackwardFlat(), |
1248 | args: PiecewiseCurve::bootstrap_type(accuracy, minValue, maxValue)); |
1249 | |
1250 | // force bootstrap and check it worked |
1251 | curve->discount(t: 0.01); |
1252 | BOOST_CHECK_NO_THROW(curve->discount(0.01)); |
1253 | } |
1254 | |
1255 | namespace piecewise_yield_curve_test { |
1256 | // helper classes for testGlobalBootstrap() below: |
1257 | |
1258 | // functor returning the additional error terms for the cost function |
1259 | struct additionalErrors { |
1260 | explicit additionalErrors( |
1261 | std::vector<ext::shared_ptr<BootstrapHelper<YieldTermStructure> > > additionalHelpers) |
1262 | : additionalHelpers(std::move(additionalHelpers)) {} |
1263 | std::vector<ext::shared_ptr<BootstrapHelper<YieldTermStructure> > > additionalHelpers; |
1264 | Array operator()() { |
1265 | Array errors(5); |
1266 | Real a = additionalHelpers[0]->impliedQuote(); |
1267 | Real b = additionalHelpers[6]->impliedQuote(); |
1268 | for (Size k = 0; k < 5; ++k) { |
1269 | errors[k] = (5.0 - k) / 6.0 * a + (1.0 + k) / 6.0 * b - |
1270 | additionalHelpers[1 + k]->impliedQuote(); |
1271 | } |
1272 | return errors; |
1273 | } |
1274 | }; |
1275 | |
1276 | // functor returning additional dates used in the bootstrap |
1277 | struct additionalDates { |
1278 | std::vector<Date> operator()() { |
1279 | Date settl = TARGET().advance(date: Settings::instance().evaluationDate(), period: 2 * Days); |
1280 | std::vector<Date> dates; |
1281 | for (Size i = 0; i < 5; ++i) |
1282 | dates.push_back(x: TARGET().advance(date: settl, period: (1 + i) * Months)); |
1283 | return dates; |
1284 | } |
1285 | }; |
1286 | } |
1287 | |
1288 | void PiecewiseYieldCurveTest::testGlobalBootstrap() { |
1289 | |
1290 | BOOST_TEST_MESSAGE("Testing global bootstrap..."); |
1291 | |
1292 | using namespace piecewise_yield_curve_test; |
1293 | |
1294 | Date today(26, Sep, 2019); |
1295 | Settings::instance().evaluationDate() = today; |
1296 | |
1297 | // market rates |
1298 | Real refMktRate[] = {-0.373, -0.388, -0.402, -0.418, -0.431, -0.441, -0.45, |
1299 | -0.457, -0.463, -0.469, -0.461, -0.463, -0.479, -0.4511, |
1300 | -0.45418, -0.439, -0.4124, -0.37703, -0.3335, -0.28168, -0.22725, |
1301 | -0.1745, -0.12425, -0.07746, 0.0385, 0.1435, 0.17525, 0.17275, |
1302 | 0.1515, 0.1225, 0.095, 0.0644}; |
1303 | |
1304 | // expected outputs |
1305 | Date refDate[] = { |
1306 | Date(31, Mar, 2020), Date(30, Apr, 2020), Date(29, May, 2020), Date(30, Jun, 2020), |
1307 | Date(31, Jul, 2020), Date(31, Aug, 2020), Date(30, Sep, 2020), Date(30, Oct, 2020), |
1308 | Date(30, Nov, 2020), Date(31, Dec, 2020), Date(29, Jan, 2021), Date(26, Feb, 2021), |
1309 | Date(31, Mar, 2021), Date(30, Sep, 2021), Date(30, Sep, 2022), Date(29, Sep, 2023), |
1310 | Date(30, Sep, 2024), Date(30, Sep, 2025), Date(30, Sep, 2026), Date(30, Sep, 2027), |
1311 | Date(29, Sep, 2028), Date(28, Sep, 2029), Date(30, Sep, 2030), Date(30, Sep, 2031), |
1312 | Date(29, Sep, 2034), Date(30, Sep, 2039), Date(30, Sep, 2044), Date(30, Sep, 2049), |
1313 | Date(30, Sep, 2054), Date(30, Sep, 2059), Date(30, Sep, 2064), Date(30, Sep, 2069)}; |
1314 | |
1315 | Real refZeroRate[] = {-0.00373354, -0.00381005, -0.00387689, -0.00394124, -0.00407706, -0.00413633, -0.00411935, |
1316 | -0.00416370, -0.00420557, -0.00424431, -0.00427824, -0.00430977, -0.00434401, -0.00445243, |
1317 | -0.00448506, -0.00433690, -0.00407401, -0.00372752, -0.00330050, -0.00279139, -0.00225477, |
1318 | -0.00173422, -0.00123688, -0.00077237, 0.00038554, 0.00144248, 0.00175995, 0.00172873, |
1319 | 0.00150782, 0.00121145, 0.000933912, 0.000628946}; |
1320 | |
1321 | // build ql helpers |
1322 | std::vector<ext::shared_ptr<RateHelper> > helpers; |
1323 | ext::shared_ptr<IborIndex> index = ext::make_shared<Euribor>(args: 6 * Months); |
1324 | |
1325 | helpers.push_back(x: ext::make_shared<DepositRateHelper>( |
1326 | args: refMktRate[0] / 100.0, args: 6 * Months, args: 2, args: TARGET(), args: ModifiedFollowing, args: true, args: Actual360())); |
1327 | |
1328 | for (Size i = 0; i < 12; ++i) { |
1329 | helpers.push_back( |
1330 | x: ext::make_shared<FraRateHelper>(args: refMktRate[1 + i] / 100.0, args: (i + 1) * Months, args&: index)); |
1331 | } |
1332 | |
1333 | Size swapTenors[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 25, 30, 35, 40, 45, 50}; |
1334 | for (Size i = 0; i < 19; ++i) { |
1335 | helpers.push_back(x: ext::make_shared<SwapRateHelper>( |
1336 | args: refMktRate[13 + i] / 100.0, args: swapTenors[i] * Years, args: TARGET(), args: Annual, args: ModifiedFollowing, |
1337 | args: Thirty360(Thirty360::BondBasis), args&: index)); |
1338 | } |
1339 | |
1340 | // global bootstrap constraints |
1341 | std::vector<ext::shared_ptr<BootstrapHelper<YieldTermStructure> > > additionalHelpers; |
1342 | |
1343 | // set up the additional rate helpers we need in the cost function |
1344 | for (Size i = 0; i < 7; ++i) { |
1345 | additionalHelpers.push_back( |
1346 | x: ext::make_shared<FraRateHelper>(args: -0.004, args: (12 + i) * Months, args&: index)); |
1347 | } |
1348 | |
1349 | // build curve with additional dates and constraints using a global bootstrapper |
1350 | typedef PiecewiseYieldCurve<SimpleZeroYield, Linear, GlobalBootstrap> Curve; |
1351 | ext::shared_ptr<Curve> curve = ext::make_shared<Curve>( |
1352 | args: 2, args: TARGET(), args&: helpers, args: Actual365Fixed(), args: std::vector<Handle<Quote> >(), args: std::vector<Date>(), |
1353 | args: Linear(), |
1354 | args: Curve::bootstrap_type(additionalHelpers, additionalDates(), |
1355 | additionalErrors(additionalHelpers), 1.0e-12)); |
1356 | curve->enableExtrapolation(); |
1357 | |
1358 | // check expected pillar dates |
1359 | for (Size i = 0; i < LENGTH(refDate); ++i) { |
1360 | BOOST_CHECK_EQUAL(refDate[i], helpers[i]->pillarDate()); |
1361 | } |
1362 | |
1363 | // check expected zero rates |
1364 | for (Size i = 0; i < LENGTH(refZeroRate); ++i) { |
1365 | // 0.01 basis points tolerance |
1366 | QL_CHECK_SMALL(std::fabs(refZeroRate[i] - curve->zeroRate(refDate[i], Actual360(), Continuous).rate()), |
1367 | 1E-6); |
1368 | } |
1369 | } |
1370 | |
1371 | /* This test attempts to build an ARS collateralised in USD curve as of 25 Sep 2019. Using the default |
1372 | IterativeBootstrap with no retries, the yield curve building fails. Allowing retries, it expands the min and max |
1373 | bounds and passes. |
1374 | */ |
1375 | void PiecewiseYieldCurveTest::testIterativeBootstrapRetries() { |
1376 | |
1377 | BOOST_TEST_MESSAGE("Testing iterative bootstrap with retries..."); |
1378 | |
1379 | Date asof(25, Sep, 2019); |
1380 | Settings::instance().evaluationDate() = asof; |
1381 | Actual365Fixed tsDayCounter; |
1382 | |
1383 | // USD discount curve built out of FedFunds OIS swaps. |
1384 | vector<Date> usdCurveDates = { |
1385 | Date(25, Sep, 2019), |
1386 | Date(26, Sep, 2019), |
1387 | Date(8, Oct, 2019), |
1388 | Date(16, Oct, 2019), |
1389 | Date(22, Oct, 2019), |
1390 | Date(30, Oct, 2019), |
1391 | Date(2, Dec, 2019), |
1392 | Date(31, Dec, 2019), |
1393 | Date(29, Jan, 2020), |
1394 | Date(2, Mar, 2020), |
1395 | Date(31, Mar, 2020), |
1396 | Date(29, Apr, 2020), |
1397 | Date(29, May, 2020), |
1398 | Date(1, Jul, 2020), |
1399 | Date(29, Jul, 2020), |
1400 | Date(31, Aug, 2020), |
1401 | Date(30, Sep, 2020) |
1402 | }; |
1403 | |
1404 | vector<DiscountFactor> usdCurveDfs = { |
1405 | 1.000000000, |
1406 | 0.999940837, |
1407 | 0.999309357, |
1408 | 0.998894646, |
1409 | 0.998574816, |
1410 | 0.998162528, |
1411 | 0.996552511, |
1412 | 0.995197584, |
1413 | 0.993915264, |
1414 | 0.992530008, |
1415 | 0.991329696, |
1416 | 0.990179606, |
1417 | 0.989005698, |
1418 | 0.987751691, |
1419 | 0.986703371, |
1420 | 0.985495036, |
1421 | 0.984413446 |
1422 | }; |
1423 | |
1424 | Handle<YieldTermStructure> usdYts(ext::make_shared<InterpolatedDiscountCurve<LogLinear> >( |
1425 | args&: usdCurveDates, args&: usdCurveDfs, args&: tsDayCounter)); |
1426 | |
1427 | // USD/ARS forward points |
1428 | Handle<Quote> arsSpot(ext::make_shared<SimpleQuote>(args: 56.881)); |
1429 | map<Period, Real> arsFwdPoints = { |
1430 | {1 * Months, 8.5157}, |
1431 | {2 * Months, 12.7180}, |
1432 | {3 * Months, 17.8310}, |
1433 | {6 * Months, 30.3680}, |
1434 | {9 * Months, 45.5520}, |
1435 | {1 * Years, 60.7370} |
1436 | }; |
1437 | |
1438 | // Create the FX swap rate helpers for the ARS in USD curve. |
1439 | vector<ext::shared_ptr<RateHelper> > instruments; |
1440 | for (map<Period, Real>::const_iterator it = arsFwdPoints.begin(); it != arsFwdPoints.end(); ++it) { |
1441 | Handle<Quote> arsFwd(ext::make_shared<SimpleQuote>(args: it->second)); |
1442 | instruments.push_back(x: ext::make_shared<FxSwapRateHelper>(args&: arsFwd, args&: arsSpot, args: it->first, args: 2, |
1443 | args: UnitedStates(UnitedStates::GovernmentBond), args: Following, args: false, args: true, args&: usdYts)); |
1444 | } |
1445 | |
1446 | // Create the ARS in USD curve with the default IterativeBootstrap. |
1447 | typedef PiecewiseYieldCurve<Discount, LogLinear, IterativeBootstrap> LLDFCurve; |
1448 | ext::shared_ptr<YieldTermStructure> arsYts = ext::make_shared<LLDFCurve>(args&: asof, args&: instruments, args&: tsDayCounter); |
1449 | |
1450 | // USD/ARS spot date. The date on which we check the ARS discount curve. |
1451 | Date spotDate(27, Sep, 2019); |
1452 | |
1453 | // Check that the ARS in USD curve throws by requesting a discount factor. |
1454 | BOOST_CHECK_EXCEPTION(arsYts->discount(spotDate), Error, |
1455 | ExpectedErrorMessage("1st iteration: failed at 1st alive instrument")); |
1456 | |
1457 | // Create the ARS in USD curve with an IterativeBootstrap allowing for 4 retries. |
1458 | IterativeBootstrap<LLDFCurve> ib(Null<Real>(), Null<Real>(), Null<Real>(), 5); |
1459 | arsYts = ext::make_shared<LLDFCurve>(args&: asof, args&: instruments, args&: tsDayCounter, args&: ib); |
1460 | |
1461 | // Check that the ARS in USD curve builds and populate the spot ARS discount factor. |
1462 | DiscountFactor spotDfArs = 1.0; |
1463 | BOOST_REQUIRE_NO_THROW(spotDfArs = arsYts->discount(spotDate)); |
1464 | |
1465 | // Additional dates and discount factors used in the final check i.e. that calculated 1Y FX forward equals input. |
1466 | Date oneYearFwdDate(28, Sep, 2020); |
1467 | DiscountFactor spotDfUsd = usdYts->discount(d: spotDate); |
1468 | DiscountFactor oneYearDfUsd = usdYts->discount(d: oneYearFwdDate); |
1469 | |
1470 | // Given that the ARS in USD curve builds, check that the 1Y USD/ARS forward rate is as expected. |
1471 | DiscountFactor oneYearDfArs = arsYts->discount(d: oneYearFwdDate); |
1472 | Real calcFwd = (spotDfArs * arsSpot->value() / oneYearDfArs) / (spotDfUsd / oneYearDfUsd); |
1473 | Real expFwd = arsSpot->value() + arsFwdPoints.at(k: 1 * Years); |
1474 | QL_CHECK_SMALL(calcFwd - expFwd, 1e-10); |
1475 | } |
1476 | |
1477 | test_suite* PiecewiseYieldCurveTest::suite() { |
1478 | |
1479 | auto* suite = BOOST_TEST_SUITE("Piecewise yield curve tests"); |
1480 | |
1481 | // unstable |
1482 | // suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLogCubicDiscountConsistency)); |
1483 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLogLinearDiscountConsistency)); |
1484 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLinearDiscountConsistency)); |
1485 | |
1486 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLinearZeroConsistency)); |
1487 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testSplineZeroConsistency)); |
1488 | |
1489 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLinearForwardConsistency)); |
1490 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testFlatForwardConsistency)); |
1491 | // unstable |
1492 | // suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testSplineForwardConsistency)); |
1493 | |
1494 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testConvexMonotoneForwardConsistency)); |
1495 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLocalBootstrapConsistency)); |
1496 | |
1497 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testParFraRegression)); |
1498 | |
1499 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testObservability)); |
1500 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLiborFixing)); |
1501 | |
1502 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testDefaultInstantiation)); |
1503 | |
1504 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testJpyLibor)); |
1505 | |
1506 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testSwapRateHelperLastRelevantDate)); |
1507 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testSwapRateHelperSpotDate)); |
1508 | |
1509 | if (IborCoupon::Settings::instance().usingAtParCoupons()) { |
1510 | // This regression test didn't work with indexed coupons anyway. |
1511 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testBadPreviousCurve)); |
1512 | } |
1513 | |
1514 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testConstructionWithExplicitBootstrap)); |
1515 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testLargeRates)); |
1516 | |
1517 | if (IborCoupon::Settings::instance().usingAtParCoupons()) { |
1518 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testGlobalBootstrap)); |
1519 | } |
1520 | |
1521 | suite->add(QUANTLIB_TEST_CASE(&PiecewiseYieldCurveTest::testIterativeBootstrapRetries)); |
1522 | |
1523 | return suite; |
1524 | } |
1525 |
Definitions
- Datum
- BondDatum
- depositData
- fraData
- immFutData
- asxFutData
- swapData
- bondData
- bmaData
- CommonVars
- fraHelpers
- CommonVars
- testCurveConsistency
- testBMACurveConsistency
- testLogCubicDiscountConsistency
- testLogLinearDiscountConsistency
- testLinearDiscountConsistency
- testLinearZeroConsistency
- testSplineZeroConsistency
- testLinearForwardConsistency
- testFlatForwardConsistency
- testSplineForwardConsistency
- testConvexMonotoneForwardConsistency
- testLocalBootstrapConsistency
- testParFraRegression
- testObservability
- testLiborFixing
- testJpyLibor
- testDefaultInstantiation
- testSwapRateHelperLastRelevantDate
- testSwapRateHelperSpotDate
- testBadPreviousCurve
- testConstructionWithExplicitBootstrap
- testLargeRates
- additionalErrors
- additionalErrors
- operator()
- additionalDates
- operator()
- testGlobalBootstrap
- testIterativeBootstrapRetries
Learn to use CMake with our Intro Training
Find out more