1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2003 RiskMap srl
5 Copyright (C) 2006, 2007 Ferdinando Ametrano
6 Copyright (C) 2006 Marco Bianchetti
7 Copyright (C) 2006 Cristina Duminuco
8 Copyright (C) 2007, 2008 StatPro Italia srl
9 Copyright (C) 2020 Marcin Rybacki
10
11 This file is part of QuantLib, a free-software/open-source library
12 for financial quantitative analysts and developers - http://quantlib.org/
13
14 QuantLib is free software: you can redistribute it and/or modify it
15 under the terms of the QuantLib license. You should have received a
16 copy of the license along with this program; if not, please email
17 <quantlib-dev@lists.sf.net>. The license is also available online at
18 <http://quantlib.org/license.shtml>.
19
20 This program is distributed in the hope that it will be useful, but WITHOUT
21 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22 FOR A PARTICULAR PURPOSE. See the license for more details.
23*/
24
25#include "swaption.hpp"
26#include "utilities.hpp"
27#include <ql/cashflows/iborcoupon.hpp>
28#include <ql/instruments/swaption.hpp>
29#include <ql/instruments/makevanillaswap.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
31#include <ql/indexes/ibor/euribor.hpp>
32#include <ql/time/daycounters/actual365fixed.hpp>
33#include <ql/time/daycounters/thirty360.hpp>
34#include <ql/time/schedule.hpp>
35#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
36#include <ql/pricingengines/swap/discountingswapengine.hpp>
37#include <ql/utilities/dataformatters.hpp>
38#include <ql/quotes/simplequote.hpp>
39
40using namespace QuantLib;
41using namespace boost::unit_test_framework;
42
43namespace swaption_test {
44
45 Period exercises[] = { 1*Years, 2*Years, 3*Years,
46 5*Years, 7*Years, 10*Years };
47 Period lengths[] = { 1*Years, 2*Years, 3*Years,
48 5*Years, 7*Years, 10*Years,
49 15*Years, 20*Years };
50 Swap::Type type[] = { Swap::Receiver, Swap::Payer };
51
52 struct CommonVars {
53 // global data
54 Date today, settlement;
55 Real nominal;
56 Calendar calendar;
57
58 BusinessDayConvention fixedConvention;
59 Frequency fixedFrequency;
60 DayCounter fixedDayCount;
61
62 BusinessDayConvention floatingConvention;
63 Period floatingTenor;
64 ext::shared_ptr<IborIndex> index;
65
66 Natural settlementDays;
67 RelinkableHandle<YieldTermStructure> termStructure;
68
69 // utilities
70 ext::shared_ptr<Swaption> makeSwaption(
71 const ext::shared_ptr<VanillaSwap>& swap,
72 const Date& exercise,
73 Volatility volatility,
74 Settlement::Type settlementType = Settlement::Physical,
75 Settlement::Method settlementMethod = Settlement::PhysicalOTC,
76 BlackSwaptionEngine::CashAnnuityModel model = BlackSwaptionEngine::SwapRate) const {
77 Handle<Quote> vol(ext::shared_ptr<Quote>(
78 new SimpleQuote(volatility)));
79 ext::shared_ptr<PricingEngine> engine(new BlackSwaptionEngine(
80 termStructure, vol, Actual365Fixed(), 0.0, model));
81
82 ext::shared_ptr<Swaption> result(new
83 Swaption(swap,
84 ext::shared_ptr<Exercise>(
85 new EuropeanExercise(exercise)),
86 settlementType, settlementMethod));
87 result->setPricingEngine(engine);
88 return result;
89 }
90
91 ext::shared_ptr<PricingEngine> makeEngine(
92 Volatility volatility,
93 BlackSwaptionEngine::CashAnnuityModel model = BlackSwaptionEngine::SwapRate) const {
94 Handle<Quote> h(ext::shared_ptr<Quote>(new SimpleQuote(volatility)));
95 return ext::shared_ptr<PricingEngine>(
96 new BlackSwaptionEngine(termStructure, h, Actual365Fixed(), 0.0, model));
97 }
98
99 CommonVars() {
100 settlementDays = 2;
101 nominal = 1000000.0;
102 fixedConvention = Unadjusted;
103 fixedFrequency = Annual;
104 fixedDayCount = Thirty360(Thirty360::BondBasis);
105
106 index = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure));
107 floatingConvention = index->businessDayConvention();
108 floatingTenor = index->tenor();
109 calendar = index->fixingCalendar();
110 today = calendar.adjust(Date::todaysDate());
111 Settings::instance().evaluationDate() = today;
112 settlement = calendar.advance(today,n: settlementDays,unit: Days);
113 termStructure.linkTo(h: flatRate(today: settlement,forward: 0.05,dc: Actual365Fixed()));
114 }
115 };
116
117}
118
119
120void SwaptionTest::testStrikeDependency() {
121
122 BOOST_TEST_MESSAGE("Testing swaption dependency on strike...");
123
124 using namespace swaption_test;
125
126 CommonVars vars;
127
128 Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
129
130 for (auto& exercise : exercises) {
131 for (auto& length : lengths) {
132 for (auto& k : type) {
133 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
134 Date startDate =
135 vars.calendar.advance(exerciseDate,
136 n: vars.settlementDays,unit: Days);
137 // store the results for different rates...
138 std::vector<Real> values;
139 std::vector<Real> values_cash;
140 Volatility vol = 0.20;
141 for (Real strike : strikes) {
142 ext::shared_ptr<VanillaSwap> swap =
143 MakeVanillaSwap(length, vars.index, strike)
144 .withEffectiveDate(startDate)
145 .withFixedLegTenor(t: 1 * Years)
146 .withFixedLegDayCount(dc: vars.fixedDayCount)
147 .withFloatingLegSpread(sp: 0.0)
148 .withType(type: k);
149 ext::shared_ptr<Swaption> swaption =
150 vars.makeSwaption(swap,exercise: exerciseDate,volatility: vol);
151 values.push_back(x: swaption->NPV());
152 ext::shared_ptr<Swaption> swaption_cash =
153 vars.makeSwaption(swap,exercise: exerciseDate,volatility: vol,
154 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
155 values_cash.push_back(x: swaption_cash->NPV());
156 }
157 // and check that they go the right way
158 if (k == Swap::Payer) {
159 auto it = std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::less<>());
160 if (it != values.end()) {
161 Size n = it - values.begin();
162 BOOST_ERROR("NPV of Payer swaption with delivery settlement"
163 "is increasing with the strike:"
164 << "\noption tenor: " << exercise << "\noption date: "
165 << exerciseDate << "\nvolatility: " << io::rate(vol)
166 << "\nswap tenor: " << length << "\nvalue: "
167 << values[n] << " at strike: " << io::rate(strikes[n])
168 << "\nvalue: " << values[n + 1]
169 << " at strike: " << io::rate(strikes[n + 1]));
170 }
171 auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::less<>());
172 if (it_cash != values_cash.end()) {
173 Size n = it_cash - values_cash.begin();
174 BOOST_ERROR("NPV of Payer swaption with cash settlement"
175 "is increasing with the strike:"
176 << "\noption tenor: " << exercise << "\noption date: "
177 << exerciseDate << "\nvolatility: " << io::rate(vol)
178 << "\nswap tenor: " << length << "\nvalue: "
179 << values_cash[n] << " at strike: " << io::rate(strikes[n])
180 << "\nvalue: " << values_cash[n + 1]
181 << " at strike: " << io::rate(strikes[n + 1]));
182 }
183 } else {
184 auto it =
185 std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::greater<>());
186 if (it != values.end()) {
187 Size n = it - values.begin();
188 BOOST_ERROR("NPV of Receiver swaption with delivery settlement"
189 "is increasing with the strike:"
190 << "\noption tenor: " << exercise << "\noption date: "
191 << exerciseDate << "\nvolatility: " << io::rate(vol)
192 << "\nswap tenor: " << length << "\nvalue: "
193 << values[n] << " at strike: " << io::rate(strikes[n])
194 << "\nvalue: " << values[n + 1]
195 << " at strike: " << io::rate(strikes[n + 1]));
196 }
197 auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::greater<>());
198 if (it_cash != values_cash.end()) {
199 Size n = it_cash - values_cash.begin();
200 BOOST_ERROR("NPV of Receiver swaption with cash settlement"
201 "is increasing with the strike:"
202 << "\noption tenor: " << exercise << "\noption date: "
203 << exerciseDate << "\nvolatility: " << io::rate(vol)
204 << "\nswap tenor: " << length << "\nvalue: "
205 << values_cash[n] << " at strike: " << io::rate(strikes[n])
206 << "\nvalue: " << values_cash[n + 1]
207 << " at strike: " << io::rate(strikes[n + 1]));
208 }
209 }
210 }
211 }
212 }
213}
214
215void SwaptionTest::testSpreadDependency() {
216
217 BOOST_TEST_MESSAGE("Testing swaption dependency on spread...");
218
219 using namespace swaption_test;
220
221 CommonVars vars;
222
223 Spread spreads[] = { -0.002, -0.001, 0.0, 0.001, 0.002 };
224
225 for (auto exercise : exercises) {
226 for (auto& length : lengths) {
227 for (auto& k : type) {
228 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
229 Date startDate =
230 vars.calendar.advance(exerciseDate,
231 n: vars.settlementDays,unit: Days);
232 // store the results for different rates...
233 std::vector<Real> values;
234 std::vector<Real> values_cash;
235 for (Real spread : spreads) {
236 ext::shared_ptr<VanillaSwap> swap =
237 MakeVanillaSwap(length, vars.index, 0.06)
238 .withFixedLegTenor(t: 1 * Years)
239 .withFixedLegDayCount(dc: vars.fixedDayCount)
240 .withEffectiveDate(startDate)
241 .withFloatingLegSpread(sp: spread)
242 .withType(type: k);
243 ext::shared_ptr<Swaption> swaption =
244 vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20);
245 values.push_back(x: swaption->NPV());
246 ext::shared_ptr<Swaption> swaption_cash =
247 vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20,
248 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
249 values_cash.push_back(x: swaption_cash->NPV());
250 }
251 // and check that they go the right way
252 if (k == Swap::Payer) {
253 auto it =
254 std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::greater<>());
255 if (it != values.end()) {
256 Size n = it - values.begin();
257 BOOST_ERROR("NPV is decreasing with the spread "
258 << "in a payer swaption (physical delivered):"
259 << "\nexercise date: " << exerciseDate << "\nlength: "
260 << length << "\nvalue: " << values[n] << " for spread: "
261 << io::rate(spreads[n]) << "\nvalue: " << values[n + 1]
262 << " for spread: " << io::rate(spreads[n + 1]));
263 }
264 auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::greater<>());
265 if (it_cash != values_cash.end()) {
266 Size n = it_cash - values_cash.begin();
267 BOOST_ERROR("NPV is decreasing with the spread "
268 << "in a payer swaption (cash delivered):"
269 << "\nexercise date: " << exerciseDate << "\nlength: " << length
270 << "\nvalue: " << values_cash[n] << " for spread: "
271 << io::rate(spreads[n]) << "\nvalue: " << values_cash[n + 1]
272 << " for spread: " << io::rate(spreads[n + 1]));
273 }
274 } else {
275 auto it = std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::less<>());
276 if (it != values.end()) {
277 Size n = it - values.begin();
278 BOOST_ERROR("NPV is increasing with the spread "
279 << "in a receiver swaption (physical delivered):"
280 "\nexercise date: "
281 << exerciseDate << "\nlength: " << length << "\nvalue: "
282 << values[n] << " for spread: " << io::rate(spreads[n])
283 << "\nvalue: " << values[n + 1]
284 << " for spread: " << io::rate(spreads[n + 1]));
285 }
286 auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::less<>());
287 if (it_cash != values_cash.end()) {
288 Size n = it_cash - values_cash.begin();
289 BOOST_ERROR("NPV is increasing with the spread "
290 << "in a receiver swaption (cash delivered):"
291 "\nexercise date: "
292 << exerciseDate << "\nlength: " << length << "\nvalue: "
293 << values_cash[n] << " for spread: " << io::rate(spreads[n])
294 << "\nvalue: " << values_cash[n + 1]
295 << " for spread: " << io::rate(spreads[n + 1]));
296 }
297 }
298 }
299 }
300 }
301}
302
303void SwaptionTest::testSpreadTreatment() {
304
305 BOOST_TEST_MESSAGE("Testing swaption treatment of spread...");
306
307 using namespace swaption_test;
308
309 CommonVars vars;
310
311 Spread spreads[] = { -0.002, -0.001, 0.0, 0.001, 0.002 };
312
313 for (auto exercise : exercises) {
314 for (auto& length : lengths) {
315 for (auto& k : type) {
316 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
317 Date startDate =
318 vars.calendar.advance(exerciseDate,
319 n: vars.settlementDays,unit: Days);
320 for (Real spread : spreads) {
321 ext::shared_ptr<VanillaSwap> swap =
322 MakeVanillaSwap(length, vars.index, 0.06)
323 .withFixedLegTenor(t: 1 * Years)
324 .withFixedLegDayCount(dc: vars.fixedDayCount)
325 .withEffectiveDate(startDate)
326 .withFloatingLegSpread(sp: spread)
327 .withType(type: k);
328 Spread correction = spread * swap->floatingLegBPS() / swap->fixedLegBPS();
329 ext::shared_ptr<VanillaSwap> equivalentSwap =
330 MakeVanillaSwap(length, vars.index, 0.06 + correction)
331 .withFixedLegTenor(t: 1 * Years)
332 .withFixedLegDayCount(dc: vars.fixedDayCount)
333 .withEffectiveDate(startDate)
334 .withFloatingLegSpread(sp: 0.0)
335 .withType(type: k);
336 ext::shared_ptr<Swaption> swaption1 =
337 vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20);
338 ext::shared_ptr<Swaption> swaption2 =
339 vars.makeSwaption(swap: equivalentSwap,exercise: exerciseDate,volatility: 0.20);
340 ext::shared_ptr<Swaption> swaption1_cash =
341 vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20,
342 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
343 ext::shared_ptr<Swaption> swaption2_cash =
344 vars.makeSwaption(swap: equivalentSwap,exercise: exerciseDate,volatility: 0.20,
345 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
346 if (std::fabs(x: swaption1->NPV()-swaption2->NPV()) > 1.0e-6)
347 BOOST_ERROR("wrong spread treatment:"
348 << "\nexercise: " << exerciseDate << "\nlength: " << length
349 << "\ntype " << k << "\nspread: " << io::rate(spread)
350 << "\noriginal swaption value: " << swaption1->NPV()
351 << "\nequivalent swaption value: " << swaption2->NPV());
352
353 if (std::fabs(x: swaption1_cash->NPV()-swaption2_cash->NPV()) > 1.0e-6)
354 BOOST_ERROR("wrong spread treatment:"
355 << "\nexercise date: " << exerciseDate << "\nlength: " << length
356 << "\npay " << (k ? "fixed" : "floating")
357 << "\nspread: " << io::rate(spread)
358 << "\nvalue of original swaption: " << swaption1_cash->NPV()
359 << "\nvalue of equivalent swaption: " << swaption2_cash->NPV());
360 }
361 }
362 }
363 }
364}
365
366void SwaptionTest::testCachedValue() {
367
368 BOOST_TEST_MESSAGE("Testing swaption value against cached value...");
369
370 using namespace swaption_test;
371
372 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
373
374 CommonVars vars;
375
376 vars.today = Date(13, March, 2002);
377 vars.settlement = Date(15, March, 2002);
378 Settings::instance().evaluationDate() = vars.today;
379 vars.termStructure.linkTo(h: flatRate(today: vars.settlement, forward: 0.05, dc: Actual365Fixed()));
380 Date exerciseDate = vars.calendar.advance(date: vars.settlement, period: 5*Years);
381 Date startDate = vars.calendar.advance(exerciseDate,
382 n: vars.settlementDays, unit: Days);
383 ext::shared_ptr<VanillaSwap> swap =
384 MakeVanillaSwap(10*Years, vars.index, 0.06)
385 .withEffectiveDate(startDate)
386 .withFixedLegTenor(t: 1*Years)
387 .withFixedLegDayCount(dc: vars.fixedDayCount);
388
389 ext::shared_ptr<Swaption> swaption =
390 vars.makeSwaption(swap, exercise: exerciseDate, volatility: 0.20);
391
392 Real cachedNPV = usingAtParCoupons ? 0.036418158579 : 0.036421429684;
393
394 if (std::fabs(x: swaption->NPV()-cachedNPV) > 1.0e-12)
395 BOOST_ERROR("failed to reproduce cached swaption value:\n" <<
396 std::fixed << std::setprecision(12) <<
397 "\ncalculated: " << swaption->NPV() <<
398 "\nexpected: " << cachedNPV);
399}
400
401void SwaptionTest::testVega() {
402
403 BOOST_TEST_MESSAGE("Testing swaption vega...");
404
405 using namespace swaption_test;
406
407 CommonVars vars;
408
409 Settlement::Type types[] = { Settlement::Physical, Settlement::Cash };
410 Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve };
411 Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
412 Volatility vols[] = { 0.01, 0.20, 0.30, 0.70, 0.90 };
413 Volatility shift = 1e-8;
414 for (auto& exercise : exercises) {
415 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
416 Date startDate = vars.calendar.advance(date: exerciseDate,
417 period: vars.settlementDays*Days);
418 for (auto& length : lengths) {
419 for (Real strike : strikes) {
420 for (Size h=0; h<LENGTH(type); h++) {
421 ext::shared_ptr<VanillaSwap> swap =
422 MakeVanillaSwap(length, vars.index, strike)
423 .withEffectiveDate(startDate)
424 .withFixedLegTenor(t: 1 * Years)
425 .withFixedLegDayCount(dc: vars.fixedDayCount)
426 .withFloatingLegSpread(sp: 0.0)
427 .withType(type: type[h]);
428 for (Real vol : vols) {
429 ext::shared_ptr<Swaption> swaption =
430 vars.makeSwaption(swap, exercise: exerciseDate, volatility: vol, settlementType: types[h], settlementMethod: methods[h]);
431 ext::shared_ptr<Swaption> swaption1 = vars.makeSwaption(
432 swap, exercise: exerciseDate, volatility: vol - shift, settlementType: types[h], settlementMethod: methods[h]);
433 ext::shared_ptr<Swaption> swaption2 = vars.makeSwaption(
434 swap, exercise: exerciseDate, volatility: vol + shift, settlementType: types[h], settlementMethod: methods[h]);
435
436 Real swaptionNPV = swaption->NPV();
437 Real numericalVegaPerPoint =
438 (swaption2->NPV()-swaption1->NPV())/(200.0*shift);
439 // check only relevant vega
440 if (numericalVegaPerPoint/swaptionNPV>1.0e-7) {
441 Real analyticalVegaPerPoint =
442 swaption->result<Real>(tag: "vega")/100.0;
443 Real discrepancy = std::fabs(x: analyticalVegaPerPoint
444 - numericalVegaPerPoint);
445 discrepancy /= numericalVegaPerPoint;
446 Real tolerance = 0.015;
447 if (discrepancy > tolerance)
448 BOOST_FAIL("failed to compute swaption vega:"
449 << "\n option tenor: " << exercise
450 << "\n volatility: " << io::rate(vol)
451 << "\n option type: " << swaption->type()
452 << "\n swap tenor: " << length
453 << "\n strike: " << io::rate(strike)
454 << "\n settlement: " << types[h]
455 << "\n nominal: "
456 << swaption->underlyingSwap()->nominal()
457 << "\n npv: " << swaptionNPV
458 << "\n calculated vega: " << analyticalVegaPerPoint
459 << "\n expected vega: " << numericalVegaPerPoint
460 << "\n discrepancy: " << io::rate(discrepancy)
461 << "\n tolerance: " << io::rate(tolerance));
462 }
463 }
464 }
465 }
466 }
467 }
468}
469
470
471
472void SwaptionTest::testCashSettledSwaptions() {
473
474 BOOST_TEST_MESSAGE("Testing cash settled swaptions modified annuity...");
475
476 using namespace swaption_test;
477
478 CommonVars vars;
479
480 Rate strike = 0.05;
481
482 for (auto exercise : exercises) {
483 for (auto length : lengths) {
484
485 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
486 Date startDate = vars.calendar.advance(exerciseDate,
487 n: vars.settlementDays,unit: Days);
488 Date maturity = vars.calendar.advance(date: startDate, period: length, convention: vars.floatingConvention);
489 Schedule floatSchedule(startDate, maturity, vars.floatingTenor,
490 vars.calendar,vars.floatingConvention,
491 vars.floatingConvention,
492 DateGeneration::Forward, false);
493 // Swap with fixed leg conventions: Business Days = Unadjusted, DayCount = 30/360
494 Schedule fixedSchedule_u(startDate, maturity,
495 Period(vars.fixedFrequency),
496 vars.calendar, Unadjusted, Unadjusted,
497 DateGeneration::Forward, true);
498 ext::shared_ptr<VanillaSwap> swap_u360(
499 new VanillaSwap(type[0], vars.nominal,
500 fixedSchedule_u,strike,Thirty360(Thirty360::BondBasis),
501 floatSchedule,vars.index,0.0,
502 vars.index->dayCounter()));
503
504 // Swap with fixed leg conventions: Business Days = Unadjusted, DayCount = Act/365
505 ext::shared_ptr<VanillaSwap> swap_u365(
506 new VanillaSwap(type[0],vars.nominal,
507 fixedSchedule_u,strike,Actual365Fixed(),
508 floatSchedule,vars.index,0.0,
509 vars.index->dayCounter()));
510
511 // Swap with fixed leg conventions: Business Days = Modified Following, DayCount = 30/360
512 Schedule fixedSchedule_a(startDate,maturity,
513 Period(vars.fixedFrequency),
514 vars.calendar,ModifiedFollowing,
515 ModifiedFollowing,
516 DateGeneration::Forward, true);
517 ext::shared_ptr<VanillaSwap> swap_a360(
518 new VanillaSwap(type[0],vars.nominal,
519 fixedSchedule_a,strike,Thirty360(Thirty360::BondBasis),
520 floatSchedule,vars.index,0.0,
521 vars.index->dayCounter()));
522
523 // Swap with fixed leg conventions: Business Days = Modified Following, DayCount = Act/365
524 ext::shared_ptr<VanillaSwap> swap_a365(
525 new VanillaSwap(type[0],vars.nominal,
526 fixedSchedule_a,strike,Actual365Fixed(),
527 floatSchedule,vars.index,0.0,
528 vars.index->dayCounter()));
529
530 ext::shared_ptr<PricingEngine> swapEngine(
531 new DiscountingSwapEngine(vars.termStructure));
532
533 swap_u360->setPricingEngine(swapEngine);
534 swap_a360->setPricingEngine(swapEngine);
535 swap_u365->setPricingEngine(swapEngine);
536 swap_a365->setPricingEngine(swapEngine);
537
538 const Leg& swapFixedLeg_u360 = swap_u360->fixedLeg();
539 const Leg& swapFixedLeg_a360 = swap_a360->fixedLeg();
540 const Leg& swapFixedLeg_u365 = swap_u365->fixedLeg();
541 const Leg& swapFixedLeg_a365 = swap_a365->fixedLeg();
542
543 // FlatForward curves
544 Handle<YieldTermStructure> termStructure_u360(
545 ext::shared_ptr<YieldTermStructure>(
546 new FlatForward(vars.settlement,swap_u360->fairRate(),
547 Thirty360(Thirty360::BondBasis),Compounded,
548 vars.fixedFrequency)));
549 Handle<YieldTermStructure> termStructure_a360(
550 ext::shared_ptr<YieldTermStructure>(
551 new FlatForward(vars.settlement,swap_a360->fairRate(),
552 Thirty360(Thirty360::BondBasis),Compounded,
553 vars.fixedFrequency)));
554 Handle<YieldTermStructure> termStructure_u365(
555 ext::shared_ptr<YieldTermStructure>(
556 new FlatForward(vars.settlement,swap_u365->fairRate(),
557 Actual365Fixed(),Compounded,
558 vars.fixedFrequency)));
559 Handle<YieldTermStructure> termStructure_a365(
560 ext::shared_ptr<YieldTermStructure>(
561 new FlatForward(vars.settlement,swap_a365->fairRate(),
562 Actual365Fixed(),Compounded,
563 vars.fixedFrequency)));
564
565 // Annuity calculated by swap method fixedLegBPS().
566 // Fixed leg conventions: Unadjusted, 30/360
567 Real annuity_u360 = swap_u360->fixedLegBPS() / 0.0001;
568 annuity_u360 = swap_u360->type()==Swap::Payer ?
569 -annuity_u360 : annuity_u360;
570 // Fixed leg conventions: ModifiedFollowing, act/365
571 Real annuity_a365 = swap_a365->fixedLegBPS() / 0.0001;
572 annuity_a365 = swap_a365->type()==Swap::Payer ?
573 -annuity_a365 : annuity_a365;
574 // Fixed leg conventions: ModifiedFollowing, 30/360
575 Real annuity_a360 = swap_a360->fixedLegBPS() / 0.0001;
576 annuity_a360 = swap_a360->type()==Swap::Payer ?
577 -annuity_a360 : annuity_a360;
578 // Fixed leg conventions: Unadjusted, act/365
579 Real annuity_u365 = swap_u365->fixedLegBPS() / 0.0001;
580 annuity_u365 = swap_u365->type()==Swap::Payer ?
581 -annuity_u365 : annuity_u365;
582
583 // Calculation of Modified Annuity (cash settlement)
584 // Fixed leg conventions of swap: unadjusted, 30/360
585 Real cashannuity_u360 = 0.;
586 Size i;
587 for (i=0; i<swapFixedLeg_u360.size(); i++) {
588 cashannuity_u360 += swapFixedLeg_u360[i]->amount()/strike
589 * termStructure_u360->discount(
590 d: swapFixedLeg_u360[i]->date());
591 }
592 // Fixed leg conventions of swap: unadjusted, act/365
593 Real cashannuity_u365 = 0.;
594 for (i=0; i<swapFixedLeg_u365.size(); i++) {
595 cashannuity_u365 += swapFixedLeg_u365[i]->amount()/strike
596 * termStructure_u365->discount(
597 d: swapFixedLeg_u365[i]->date());
598 }
599 // Fixed leg conventions of swap: modified following, 30/360
600 Real cashannuity_a360 = 0.;
601 for (i=0; i<swapFixedLeg_a360.size(); i++) {
602 cashannuity_a360 += swapFixedLeg_a360[i]->amount()/strike
603 * termStructure_a360->discount(
604 d: swapFixedLeg_a360[i]->date());
605 }
606 // Fixed leg conventions of swap: modified following, act/365
607 Real cashannuity_a365 = 0.;
608 for (i=0; i<swapFixedLeg_a365.size(); i++) {
609 cashannuity_a365 += swapFixedLeg_a365[i]->amount()/strike
610 * termStructure_a365->discount(
611 d: swapFixedLeg_a365[i]->date());
612 }
613
614 // Swaptions: underlying swap fixed leg conventions:
615 // unadjusted, 30/360
616
617 // Physical settled swaption
618 ext::shared_ptr<Swaption> swaption_p_u360 =
619 vars.makeSwaption(swap: swap_u360,exercise: exerciseDate,volatility: 0.20);
620 Real value_p_u360 = swaption_p_u360->NPV();
621 // Cash settled swaption
622 ext::shared_ptr<Swaption> swaption_c_u360 =
623 vars.makeSwaption(swap: swap_u360,exercise: exerciseDate,volatility: 0.20,
624 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
625 Real value_c_u360 = swaption_c_u360->NPV();
626 // the NPV's ratio must be equal to annuities ratio
627 Real npv_ratio_u360 = value_c_u360 / value_p_u360;
628 Real annuity_ratio_u360 = cashannuity_u360 / annuity_u360;
629
630 // Swaptions: underlying swap fixed leg conventions:
631 // modified following, act/365
632
633 // Physical settled swaption
634 ext::shared_ptr<Swaption> swaption_p_a365 =
635 vars.makeSwaption(swap: swap_a365,exercise: exerciseDate,volatility: 0.20);
636 Real value_p_a365 = swaption_p_a365->NPV();
637 // Cash settled swaption
638 ext::shared_ptr<Swaption> swaption_c_a365 =
639 vars.makeSwaption(swap: swap_a365,exercise: exerciseDate,volatility: 0.20,
640 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
641 Real value_c_a365 = swaption_c_a365->NPV();
642 // the NPV's ratio must be equal to annuities ratio
643 Real npv_ratio_a365 = value_c_a365 / value_p_a365;
644 Real annuity_ratio_a365 = cashannuity_a365 / annuity_a365;
645
646 // Swaptions: underlying swap fixed leg conventions:
647 // modified following, 30/360
648
649 // Physical settled swaption
650 ext::shared_ptr<Swaption> swaption_p_a360 =
651 vars.makeSwaption(swap: swap_a360,exercise: exerciseDate,volatility: 0.20);
652 Real value_p_a360 = swaption_p_a360->NPV();
653 // Cash settled swaption
654 ext::shared_ptr<Swaption> swaption_c_a360 =
655 vars.makeSwaption(swap: swap_a360,exercise: exerciseDate,volatility: 0.20,
656 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
657 Real value_c_a360 = swaption_c_a360->NPV();
658 // the NPV's ratio must be equal to annuities ratio
659 Real npv_ratio_a360 = value_c_a360 / value_p_a360;
660 Real annuity_ratio_a360 = cashannuity_a360 / annuity_a360;
661
662 // Swaptions: underlying swap fixed leg conventions:
663 // unadjusted, act/365
664
665 // Physical settled swaption
666 ext::shared_ptr<Swaption> swaption_p_u365 =
667 vars.makeSwaption(swap: swap_u365,exercise: exerciseDate,volatility: 0.20);
668 Real value_p_u365 = swaption_p_u365->NPV();
669 // Cash settled swaption
670 ext::shared_ptr<Swaption> swaption_c_u365 =
671 vars.makeSwaption(swap: swap_u365,exercise: exerciseDate,volatility: 0.20,
672 settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve);
673 Real value_c_u365 = swaption_c_u365->NPV();
674 // the NPV's ratio must be equal to annuities ratio
675 Real npv_ratio_u365 = value_c_u365 / value_p_u365;
676 Real annuity_ratio_u365 = cashannuity_u365 / annuity_u365;
677
678 if (std::fabs(x: annuity_ratio_u360-npv_ratio_u360)>1e-10 ) {
679 BOOST_ERROR("\n"
680 << " The npv's ratio must be equal to "
681 << " annuities ratio"
682 << "\n"
683 " Swaption "
684 << exercises[i].units() << "y x " << length.units() << "y"
685 << " (underlying swap fixed leg Unadjusted, 30/360)"
686 << "\n"
687 << " Today : " << vars.today << "\n"
688 << " Settlement date : " << vars.settlement << "\n"
689 << " Exercise date : " << exerciseDate << "\n"
690 << " Swap start date : " << startDate << "\n"
691 << " Swap end date : " << maturity << "\n"
692 << " physical delivered swaption npv : " << value_p_u360 << "\t\t\t"
693 << " annuity : " << annuity_u360 << "\n"
694 << " cash delivered swaption npv : " << value_c_u360 << "\t\t\t"
695 << " annuity : " << cashannuity_u360 << "\n"
696 << " npv ratio : " << npv_ratio_u360 << "\n"
697 << " annuity ratio : " << annuity_ratio_u360 << "\n"
698 << " difference : " << (annuity_ratio_u360 - npv_ratio_u360));
699 }
700 if (std::fabs(x: annuity_ratio_a365-npv_ratio_a365)>1e-10) {
701 BOOST_ERROR("\n"
702 << " The npv's ratio must be equal to "
703 << " annuities ratio"
704 << "\n"
705 " Swaption "
706 << exercises[i].units() << "y x " << length.units() << "y"
707 << " (underlying swap fixed leg Modified Following, act/365"
708 << "\n"
709 << " Today : " << vars.today << "\n"
710 << " Settlement date : " << vars.settlement << "\n"
711 << " Exercise date : " << exerciseDate << "\n"
712 << " Swap start date : " << startDate << "\n"
713 << " Swap end date : " << maturity << "\n"
714 << " physical delivered swaption npv : " << value_p_a365 << "\t\t\t"
715 << " annuity : " << annuity_a365 << "\n"
716 << " cash delivered swaption npv : " << value_c_a365 << "\t\t\t"
717 << " annuity : " << cashannuity_a365 << "\n"
718 << " npv ratio : " << npv_ratio_a365 << "\n"
719 << " annuity ratio : " << annuity_ratio_a365 << "\n"
720 << " difference : " << (annuity_ratio_a365 - npv_ratio_a365));
721 }
722 if (std::fabs(x: annuity_ratio_a360-npv_ratio_a360)>1e-10) {
723 BOOST_ERROR("\n"
724 << " The npv's ratio must be equal to "
725 << " annuities ratio"
726 << "\n"
727 " Swaption "
728 << exercises[i].units() << "y x " << length.units() << "y"
729 << " (underlying swap fixed leg Unadjusted, 30/360)"
730 << "\n"
731 << " Today : " << vars.today << "\n"
732 << " Settlement date : " << vars.settlement << "\n"
733 << " Exercise date : " << exerciseDate << "\n"
734 << " Swap start date : " << startDate << "\n"
735 << " Swap end date : " << maturity << "\n"
736 << " physical delivered swaption npv : " << value_p_a360 << "\t\t\t"
737 << " annuity : " << annuity_a360 << "\n"
738 << " cash delivered swaption npv : " << value_c_a360 << "\t\t\t"
739 << " annuity : " << cashannuity_a360 << "\n"
740 << " npv ratio : " << npv_ratio_a360 << "\n"
741 << " annuity ratio : " << annuity_ratio_a360 << "\n"
742 << " difference : " << (annuity_ratio_a360 - npv_ratio_a360));
743 }
744 if (std::fabs(x: annuity_ratio_u365-npv_ratio_u365)>1e-10) {
745 BOOST_ERROR("\n"
746 << " The npv's ratio must be equal to "
747 << " annuities ratio"
748 << "\n"
749 " Swaption "
750 << exercises[i].units() << "y x " << length.units() << "y"
751 << " (underlying swap fixed leg Unadjusted, act/365)"
752 << "\n"
753 << " Today : " << vars.today << "\n"
754 << " Settlement date : " << vars.settlement << "\n"
755 << " Exercise date : " << exerciseDate << "\n"
756 << " Swap start date : " << startDate << "\n"
757 << " Swap end date : " << maturity << "\n"
758 << " physical delivered swaption npv : " << value_p_u365 << "\t\t\t"
759 << " annuity : " << annuity_u365 << "\n"
760 << " cash delivered swaption npv : " << value_c_u365 << "\t\t\t"
761 << " annuity : " << cashannuity_u365 << "\n"
762 << " npv ratio : " << npv_ratio_u365 << "\n"
763 << " annuity ratio : " << annuity_ratio_u365 << "\n"
764 << " difference : " << (annuity_ratio_u365 - npv_ratio_u365));
765 }
766 }
767 }
768}
769
770
771
772void SwaptionTest::testImpliedVolatility() {
773
774 BOOST_TEST_MESSAGE("Testing implied volatility for swaptions...");
775
776 using namespace swaption_test;
777
778 CommonVars vars;
779
780 Size maxEvaluations = 100;
781 Real tolerance = 1.0e-08;
782
783 Settlement::Type types[] = { Settlement::Physical, Settlement::Cash };
784 Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve };
785 // test data
786 Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 };
787 Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 };
788
789 for (auto& exercise : exercises) {
790 for (auto& length : lengths) {
791 Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise);
792 Date startDate = vars.calendar.advance(exerciseDate,
793 n: vars.settlementDays, unit: Days);
794
795 for (Real& strike : strikes) {
796 for (auto& k : type) {
797 ext::shared_ptr<VanillaSwap> swap =
798 MakeVanillaSwap(length, vars.index, strike)
799 .withEffectiveDate(startDate)
800 .withFixedLegTenor(t: 1 * Years)
801 .withFixedLegDayCount(dc: vars.fixedDayCount)
802 .withFloatingLegSpread(sp: 0.0)
803 .withType(type: k);
804 for (Size h=0; h<LENGTH(types); h++) {
805 for (Real vol : vols) {
806 ext::shared_ptr<Swaption> swaption =
807 vars.makeSwaption(swap, exercise: exerciseDate, volatility: vol, settlementType: types[h], settlementMethod: methods[h],
808 model: BlackSwaptionEngine::DiscountCurve);
809 // Black price
810 Real value = swaption->NPV();
811 Volatility implVol = 0.0;
812 try {
813 implVol =
814 swaption->impliedVolatility(price: value,
815 discountCurve: vars.termStructure,
816 guess: 0.10,
817 accuracy: tolerance,
818 maxEvaluations,
819 minVol: 1.0e-7,
820 maxVol: 4.0,
821 type: ShiftedLognormal,
822 displacement: 0.0);
823 } catch (std::exception& e) {
824 // couldn't bracket?
825 swaption->setPricingEngine(vars.makeEngine(volatility: 0.0, model: BlackSwaptionEngine::DiscountCurve));
826 Real value2 = swaption->NPV();
827 if (std::fabs(x: value-value2) < tolerance) {
828 // ok, just skip:
829 continue;
830 }
831 // otherwise, report error
832 BOOST_ERROR("implied vol failure: "
833 << exercise << "x" << length << " " << k
834 << "\nsettlement: " << types[h] << "\nstrike "
835 << strike
836 << "\natm level: " << io::rate(swap->fairRate())
837 << "\nvol: " << io::volatility(vol)
838 << "\nprice: " << value << "\n"
839 << e.what());
840 }
841 if (std::fabs(x: implVol - vol) > tolerance) {
842 // the difference might not matter
843 swaption->setPricingEngine(vars.makeEngine(volatility: implVol, model: BlackSwaptionEngine::DiscountCurve));
844 Real value2 = swaption->NPV();
845 if (std::fabs(x: value-value2) > tolerance) {
846 BOOST_ERROR("implied vol failure: "
847 << exercise << "x" << length << " " << k
848 << "\nsettlement: " << types[h]
849 << "\nstrike " << strike
850 << "\natm level: " << io::rate(swap->fairRate())
851 << "\nvol: " << io::volatility(vol)
852 << "\nprice: " << value
853 << "\nimplied vol: " << io::volatility(implVol)
854 << "\nimplied price: " << value2);
855 }
856 }
857 }
858 }
859 }
860 }
861 }
862 }
863}
864
865template <typename Engine>
866ext::shared_ptr<Engine> makeConstVolEngine(
867 const Handle<YieldTermStructure> &discountCurve,
868 Volatility volatility)
869{
870 Handle<Quote> h(ext::make_shared<SimpleQuote>(args&: volatility));
871 return ext::make_shared<Engine>(discountCurve, h);
872}
873
874template <typename Engine>
875void checkSwaptionDelta(bool useBachelierVol)
876{
877 using namespace swaption_test;
878
879 CommonVars vars;
880 Date today = vars.today;
881 Calendar calendar = vars.calendar;
882
883 const Real bump = 1.e-4;
884 const Real epsilon = 1.e-10;
885
886 RelinkableHandle<YieldTermStructure> projectionCurveHandle;
887
888 const Real projectionRate = 0.01;
889 RelinkableHandle<Quote> projectionQuoteHandle;
890
891 ext::shared_ptr<YieldTermStructure> projectionCurve = ext::make_shared<FlatForward>(
892 args&: today, args&: projectionQuoteHandle, args: Actual365Fixed());
893 projectionCurveHandle.linkTo(h: projectionCurve);
894
895 Handle<YieldTermStructure> discountHandle(ext::make_shared<FlatForward>(
896 args&: today,
897 args: Handle<Quote>(ext::make_shared<SimpleQuote>(args: 0.0085)),
898 args: Actual365Fixed()));
899 ext::shared_ptr<DiscountingSwapEngine> swapEngine = ext::make_shared<DiscountingSwapEngine>(
900 args&: discountHandle);
901
902 ext::shared_ptr<IborIndex> idx = ext::make_shared<Euribor6M>(args&: projectionCurveHandle);
903
904 Settlement::Type types[] = { Settlement::Physical, Settlement::Cash };
905 Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::CollateralizedCashPrice};
906
907 Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
908 Volatility vols[] = { 0.0, 0.10, 0.20, 0.30, 0.70, 0.90 };
909
910 for (Real vol : vols) {
911 for (auto exercise : exercises) {
912 for (auto& length : lengths) {
913 for (Real& strike : strikes) {
914 for (Size h=0; h<LENGTH(type); h++) {
915 Volatility volatility = useBachelierVol ? vol / 100.0 : vol;
916 ext::shared_ptr<Engine> swaptionEngine = makeConstVolEngine<Engine>(
917 discountHandle, volatility);
918
919 Date exerciseDate = calendar.advance(date: today, period: exercise);
920 Date startDate = calendar.advance(date: exerciseDate, period: 2*Days);
921 projectionQuoteHandle.linkTo(h: ext::make_shared<SimpleQuote>(args: projectionRate));
922
923 ext::shared_ptr<VanillaSwap> underlying =
924 MakeVanillaSwap(length, idx, strike)
925 .withEffectiveDate(startDate)
926 .withFixedLegTenor(t: 1 * Years)
927 .withFixedLegDayCount(dc: Thirty360(Thirty360::BondBasis))
928 .withFloatingLegSpread(sp: 0.0)
929 .withType(type: type[h]);
930 underlying->setPricingEngine(swapEngine);
931
932 Real fairRate = underlying->fairRate();
933
934 ext::shared_ptr<Swaption> swaption = ext::make_shared<Swaption>(
935 args&: underlying,
936 args: ext::make_shared<EuropeanExercise>(args&: exerciseDate),
937 args&: types[h],
938 args&: methods[h]);
939 swaption->setPricingEngine(swaptionEngine);
940
941 Real value = swaption->NPV();
942 Real delta = swaption->result<Real>(tag: "delta") * bump;
943
944 projectionQuoteHandle.linkTo(h: ext::make_shared<SimpleQuote>(
945 args: projectionRate + bump));
946
947 Real bumpedFairRate = underlying->fairRate();
948 Real bumpedValue = swaption->NPV();
949 Real bumpedDelta = swaption->result<Real>(tag: "delta") * bump;
950
951 Real deltaBump = bumpedFairRate - fairRate;
952 Real approxDelta = (bumpedValue - value) / deltaBump * bump;
953
954 Real lowerBound = std::min(a: delta, b: bumpedDelta) - epsilon;
955 Real upperBound = std::max(a: delta, b: bumpedDelta) + epsilon;
956
957 /*! Based on the Mean Value Theorem, the below inequality
958 should hold for any function that is monotonic in the
959 area of the bump.
960 */
961 bool checkIsCorrect = (lowerBound < approxDelta) && (approxDelta < upperBound);
962
963 if (!checkIsCorrect)
964 BOOST_FAIL(
965 "failed to compute swaption delta:"
966 << "\n option tenor: " << exerciseDate
967 << "\n volatility: " << io::rate(volatility)
968 << "\n option type: " << swaption->type()
969 << "\n swap tenor: " << length << "\n strike: "
970 << strike << "\n settlement: " << types[h]
971 << "\n method: " << methods[h]
972 << "\n nominal: " << swaption->underlyingSwap()->nominal()
973 << "\n npv: " << value << "\n calculated delta: "
974 << delta << "\n expected delta: " << approxDelta);
975 }
976 }
977 }
978 }
979 }
980}
981
982void SwaptionTest::testSwaptionDeltaInBlackModel() {
983
984 BOOST_TEST_MESSAGE("Testing swaption delta in Black model...");
985
986 checkSwaptionDelta<BlackSwaptionEngine>(useBachelierVol: false);
987}
988
989void SwaptionTest::testSwaptionDeltaInBachelierModel() {
990
991 BOOST_TEST_MESSAGE("Testing swaption delta in Bachelier model...");
992
993 checkSwaptionDelta<BachelierSwaptionEngine>(useBachelierVol: true);
994}
995
996test_suite* SwaptionTest::suite(SpeedLevel speed) {
997 auto* suite = BOOST_TEST_SUITE("Swaption tests");
998
999 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testCashSettledSwaptions));
1000 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testStrikeDependency));
1001 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSpreadDependency));
1002 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSpreadTreatment));
1003 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testCachedValue));
1004 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testVega));
1005 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSwaptionDeltaInBlackModel));
1006 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSwaptionDeltaInBachelierModel));
1007
1008 if (speed <= Fast) {
1009 suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testImpliedVolatility));
1010 };
1011
1012 return suite;
1013}
1014

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