1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2003, 2004, 2007 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 "swap.hpp"
21#include "utilities.hpp"
22#include <ql/instruments/vanillaswap.hpp>
23#include <ql/pricingengines/swap/discountingswapengine.hpp>
24#include <ql/termstructures/yield/flatforward.hpp>
25#include <ql/time/calendars/nullcalendar.hpp>
26#include <ql/time/daycounters/thirty360.hpp>
27#include <ql/time/daycounters/actual365fixed.hpp>
28#include <ql/time/daycounters/simpledaycounter.hpp>
29#include <ql/time/schedule.hpp>
30#include <ql/indexes/ibor/euribor.hpp>
31#include <ql/cashflows/iborcoupon.hpp>
32#include <ql/cashflows/cashflowvectors.hpp>
33#include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp>
34#include <ql/utilities/dataformatters.hpp>
35#include <ql/cashflows/cashflows.hpp>
36#include <ql/cashflows/couponpricer.hpp>
37#include <ql/currencies/europe.hpp>
38
39using namespace QuantLib;
40using namespace boost::unit_test_framework;
41
42namespace swap_test {
43
44 struct CommonVars {
45 // global data
46 Date today, settlement;
47 Swap::Type type;
48 Real nominal;
49 Calendar calendar;
50 BusinessDayConvention fixedConvention, floatingConvention;
51 Frequency fixedFrequency, floatingFrequency;
52 DayCounter fixedDayCount;
53 ext::shared_ptr<IborIndex> index;
54 Natural settlementDays;
55 RelinkableHandle<YieldTermStructure> termStructure;
56
57 // utilities
58 ext::shared_ptr<VanillaSwap>
59 makeSwap(Integer length, Rate fixedRate, Spread floatingSpread, DateGeneration::Rule rule = DateGeneration::Forward) const {
60 Date maturity = calendar.advance(settlement,n: length,unit: Years,
61 convention: floatingConvention);
62 Schedule fixedSchedule(settlement,maturity,Period(fixedFrequency),
63 calendar,fixedConvention,fixedConvention, rule, false);
64 Schedule floatSchedule(settlement,maturity,
65 Period(floatingFrequency),
66 calendar,floatingConvention,
67 floatingConvention, rule, false);
68 ext::shared_ptr<VanillaSwap> swap(
69 new VanillaSwap(type, nominal,
70 fixedSchedule, fixedRate, fixedDayCount,
71 floatSchedule, index, floatingSpread,
72 index->dayCounter()));
73 swap->setPricingEngine(ext::shared_ptr<PricingEngine>(
74 new DiscountingSwapEngine(termStructure)));
75 return swap;
76 }
77
78 CommonVars() {
79 type = Swap::Payer;
80 settlementDays = 2;
81 nominal = 100.0;
82 fixedConvention = Unadjusted;
83 floatingConvention = ModifiedFollowing;
84 fixedFrequency = Annual;
85 floatingFrequency = Semiannual;
86 fixedDayCount = Thirty360(Thirty360::BondBasis);
87 index = ext::shared_ptr<IborIndex>(new
88 Euribor(Period(floatingFrequency), termStructure));
89 calendar = index->fixingCalendar();
90 today = calendar.adjust(Settings::instance().evaluationDate());
91 settlement = calendar.advance(today,n: settlementDays,unit: Days);
92 termStructure.linkTo(h: flatRate(today: settlement,forward: 0.05,dc: Actual365Fixed()));
93 }
94 };
95
96}
97
98
99void SwapTest::testFairRate() {
100
101 BOOST_TEST_MESSAGE("Testing vanilla-swap calculation of fair fixed rate...");
102
103 using namespace swap_test;
104
105 CommonVars vars;
106
107 Integer lengths[] = { 1, 2, 5, 10, 20 };
108 Spread spreads[] = { -0.001, -0.01, 0.0, 0.01, 0.001 };
109
110 for (int& length : lengths) {
111 for (Real spread : spreads) {
112
113 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length, fixedRate: 0.0, floatingSpread: spread);
114 swap = vars.makeSwap(length, fixedRate: swap->fairRate(), floatingSpread: spread);
115 if (std::fabs(x: swap->NPV()) > 1.0e-10) {
116 BOOST_ERROR("recalculating with implied rate:\n"
117 << std::setprecision(2) << " length: " << length << " years\n"
118 << " floating spread: " << io::rate(spread) << "\n"
119 << " swap value: " << swap->NPV());
120 }
121 }
122 }
123}
124
125void SwapTest::testFairSpread() {
126
127 BOOST_TEST_MESSAGE("Testing vanilla-swap calculation of "
128 "fair floating spread...");
129
130 using namespace swap_test;
131
132 CommonVars vars;
133
134 Integer lengths[] = { 1, 2, 5, 10, 20 };
135 Rate rates[] = { 0.04, 0.05, 0.06, 0.07 };
136
137 for (int& length : lengths) {
138 for (Real j : rates) {
139
140 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length, fixedRate: j, floatingSpread: 0.0);
141 swap = vars.makeSwap(length, fixedRate: j, floatingSpread: swap->fairSpread());
142 if (std::fabs(x: swap->NPV()) > 1.0e-10) {
143 BOOST_ERROR("recalculating with implied spread:\n"
144 << std::setprecision(2) << " length: " << length << " years\n"
145 << " fixed rate: " << io::rate(j) << "\n"
146 << " swap value: " << swap->NPV());
147 }
148 }
149 }
150}
151
152void SwapTest::testRateDependency() {
153
154 BOOST_TEST_MESSAGE("Testing vanilla-swap dependency on fixed rate...");
155
156 using namespace swap_test;
157
158 CommonVars vars;
159
160 Integer lengths[] = { 1, 2, 5, 10, 20 };
161 Spread spreads[] = { -0.001, -0.01, 0.0, 0.01, 0.001 };
162 Rate rates[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
163
164 for (int& length : lengths) {
165 for (Real spread : spreads) {
166 // store the results for different rates...
167 std::vector<Real> swap_values;
168 for (Real rate : rates) {
169 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length, fixedRate: rate, floatingSpread: spread);
170 swap_values.push_back(x: swap->NPV());
171 }
172 // and check that they go the right way
173 auto it = std::adjacent_find(first: swap_values.begin(), last: swap_values.end(), binary_pred: std::less<>());
174 if (it != swap_values.end()) {
175 Size n = it - swap_values.begin();
176 BOOST_ERROR("NPV is increasing with the fixed rate in a swap: \n"
177 << " length: " << length << " years\n"
178 << " value: " << swap_values[n]
179 << " paying fixed rate: " << io::rate(rates[n]) << "\n"
180 << " value: " << swap_values[n + 1]
181 << " paying fixed rate: " << io::rate(rates[n + 1]));
182 }
183 }
184 }
185}
186
187void SwapTest::testSpreadDependency() {
188
189 BOOST_TEST_MESSAGE("Testing vanilla-swap dependency on floating spread...");
190
191 using namespace swap_test;
192
193 CommonVars vars;
194
195 Integer lengths[] = { 1, 2, 5, 10, 20 };
196 Rate rates[] = { 0.04, 0.05, 0.06, 0.07 };
197 Spread spreads[] = { -0.01, -0.002, -0.001, 0.0, 0.001, 0.002, 0.01 };
198
199 for (int& length : lengths) {
200 for (Real j : rates) {
201 // store the results for different spreads...
202 std::vector<Real> swap_values;
203 for (Real spread : spreads) {
204 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length, fixedRate: j, floatingSpread: spread);
205 swap_values.push_back(x: swap->NPV());
206 }
207 // and check that they go the right way
208 auto it =
209 std::adjacent_find(first: swap_values.begin(), last: swap_values.end(), binary_pred: std::greater<>());
210 if (it != swap_values.end()) {
211 Size n = it - swap_values.begin();
212 BOOST_ERROR("NPV is decreasing with the floating spread in a swap: \n"
213 << " length: " << length << " years\n"
214 << " value: " << swap_values[n]
215 << " receiving spread: " << io::rate(spreads[n]) << "\n"
216 << " value: " << swap_values[n + 1]
217 << " receiving spread: " << io::rate(spreads[n + 1]));
218 }
219 }
220 }
221}
222
223void SwapTest::testInArrears() {
224
225 BOOST_TEST_MESSAGE("Testing in-arrears swap calculation...");
226
227 using namespace swap_test;
228
229 CommonVars vars;
230
231 /* See Hull, 4th ed., page 550
232 Note: the calculation in the book is wrong (work out the
233 adjustment and you'll get 0.05 + 0.000115 T1)
234 */
235
236 Date maturity = vars.today + 5*Years;
237 Calendar calendar = NullCalendar();
238 Schedule schedule(vars.today, maturity,Period(Annual),calendar,
239 Following,Following,
240 DateGeneration::Forward,false);
241 DayCounter dayCounter = SimpleDayCounter();
242 std::vector<Real> nominals(1, 100000000.0);
243 ext::shared_ptr<IborIndex> index(new IborIndex("dummy", 1*Years, 0,
244 EURCurrency(), calendar,
245 Following, false, dayCounter,
246 vars.termStructure));
247 Rate oneYear = 0.05;
248 Rate r = std::log(x: 1.0+oneYear);
249 vars.termStructure.linkTo(h: flatRate(today: vars.today,forward: r,dc: dayCounter));
250
251
252 std::vector<Rate> coupons(1, oneYear);
253 Leg fixedLeg = FixedRateLeg(schedule)
254 .withNotionals(nominals)
255 .withCouponRates(coupons, paymentDayCounter: dayCounter);
256
257 std::vector<Real> gearings;
258 std::vector<Rate> spreads;
259 Natural fixingDays = 0;
260
261 Volatility capletVolatility = 0.22;
262 Handle<OptionletVolatilityStructure> vol(
263 ext::shared_ptr<OptionletVolatilityStructure>(new
264 ConstantOptionletVolatility(vars.today, NullCalendar(), Following,
265 capletVolatility, dayCounter)));
266 ext::shared_ptr<IborCouponPricer> pricer(new
267 BlackIborCouponPricer(vol));
268
269 Leg floatingLeg = IborLeg(schedule, index)
270 .withNotionals(notionals: nominals)
271 .withPaymentDayCounter(dayCounter)
272 .withFixingDays(fixingDays)
273 .withGearings(gearings)
274 .withSpreads(spreads)
275 .inArrears();
276 setCouponPricer(leg: floatingLeg, pricer);
277
278 Swap swap(floatingLeg,fixedLeg);
279 swap.setPricingEngine(ext::shared_ptr<PricingEngine>(
280 new DiscountingSwapEngine(vars.termStructure)));
281
282 Decimal storedValue = -144813.0;
283 Real tolerance = 1.0;
284
285 if (std::fabs(x: swap.NPV()-storedValue) > tolerance)
286 BOOST_ERROR("Wrong NPV calculation:\n"
287 << " expected: " << storedValue << "\n"
288 << " calculated: " << swap.NPV());
289}
290
291void SwapTest::testCachedValue() {
292
293 BOOST_TEST_MESSAGE("Testing vanilla-swap calculation against cached value...");
294
295 using namespace swap_test;
296
297 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
298
299 CommonVars vars;
300
301 vars.today = Date(17,June,2002);
302 Settings::instance().evaluationDate() = vars.today;
303 vars.settlement =
304 vars.calendar.advance(vars.today,n: vars.settlementDays,unit: Days);
305 vars.termStructure.linkTo(h: flatRate(today: vars.settlement,forward: 0.05,dc: Actual365Fixed()));
306
307 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length: 10, fixedRate: 0.06, floatingSpread: 0.001);
308
309 if (swap->numberOfLegs() != 2)
310 BOOST_ERROR("failed to return correct number of legs:\n"
311 << std::fixed << std::setprecision(12)
312 << " calculated: " << swap->numberOfLegs() << "\n"
313 << " expected: " << 2);
314
315 Real cachedNPV = usingAtParCoupons ? -5.872863313209 : -5.872342992212;
316
317 if (std::fabs(x: swap->NPV()-cachedNPV) > 1.0e-11)
318 BOOST_ERROR("failed to reproduce cached swap value:\n"
319 << std::fixed << std::setprecision(12)
320 << " calculated: " << swap->NPV() << "\n"
321 << " expected: " << cachedNPV);
322}
323
324void SwapTest::testThirdWednesdayAdjustment() {
325
326 BOOST_TEST_MESSAGE("Testing third-Wednesday adjustment...");
327
328 using namespace swap_test;
329
330 CommonVars vars;
331
332 ext::shared_ptr<VanillaSwap> swap = vars.makeSwap(length: 1, fixedRate: 0.0, floatingSpread: -0.001, rule: DateGeneration::ThirdWednesdayInclusive);
333
334 if (swap->floatingSchedule().startDate() != Date(16, September, 2015)) {
335 BOOST_ERROR("Wrong Start Date " << swap->floatingSchedule().startDate());
336 }
337
338 if (swap->floatingSchedule().endDate() != Date(21, September, 2016)) {
339 BOOST_ERROR("Wrong End Date " << swap->floatingSchedule().endDate());
340 }
341}
342
343test_suite* SwapTest::suite() {
344 auto* suite = BOOST_TEST_SUITE("Swap tests");
345 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testFairRate));
346 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testFairSpread));
347 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testRateDependency));
348 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testSpreadDependency));
349 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testInArrears));
350 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testCachedValue));
351 suite->add(QUANTLIB_TEST_CASE(&SwapTest::testThirdWednesdayAdjustment));
352 return suite;
353}
354
355

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