[go: up one dir, main page]

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) 2004, 2005, 2006, 2007, 2008 StatPro Italia srl
6 Copyright (C) 2019 Wojciech Ĺšlusarski
7
8 This file is part of QuantLib, a free-software/open-source library
9 for financial quantitative analysts and developers - http://quantlib.org/
10
11 QuantLib is free software: you can redistribute it and/or modify it
12 under the terms of the QuantLib license. You should have received a
13 copy of the license along with this program; if not, please email
14 <quantlib-dev@lists.sf.net>. The license is also available online at
15 <http://quantlib.org/license.shtml>.
16
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the license for more details.
20*/
21
22#include "capfloor.hpp"
23#include "utilities.hpp"
24#include <ql/instruments/capfloor.hpp>
25#include <ql/instruments/vanillaswap.hpp>
26#include <ql/cashflows/cashflowvectors.hpp>
27#include <ql/termstructures/yield/flatforward.hpp>
28#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
29#include <ql/indexes/ibor/euribor.hpp>
30#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
31#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
32#include <ql/pricingengines/swap/discountingswapengine.hpp>
33#include <ql/models/marketmodels/models/flatvol.hpp>
34#include <ql/models/marketmodels/correlations/expcorrelations.hpp>
35#include <ql/math/matrix.hpp>
36#include <ql/time/daycounters/actualactual.hpp>
37#include <ql/time/daycounters/actual360.hpp>
38#include <ql/time/schedule.hpp>
39#include <ql/utilities/dataformatters.hpp>
40#include <ql/cashflows/cashflows.hpp>
41#include <ql/cashflows/couponpricer.hpp>
42#include <ql/quotes/simplequote.hpp>
43
44using namespace QuantLib;
45using namespace boost::unit_test_framework;
46
47namespace capfloor_test {
48
49 struct CommonVars {
50 // common data
51 Date settlement;
52 std::vector<Real> nominals;
53 BusinessDayConvention convention;
54 Frequency frequency;
55 ext::shared_ptr<IborIndex> index;
56 Calendar calendar;
57 Natural fixingDays;
58 RelinkableHandle<YieldTermStructure> termStructure;
59
60 // setup
61 CommonVars()
62 : nominals(1,100) {
63 frequency = Semiannual;
64 index = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure));
65 calendar = index->fixingCalendar();
66 convention = ModifiedFollowing;
67 Date today = Settings::instance().evaluationDate();
68 Natural settlementDays = 2;
69 fixingDays = 2;
70 settlement = calendar.advance(today,n: settlementDays,unit: Days);
71 termStructure.linkTo(h: flatRate(today: settlement,forward: 0.05,
72 dc: ActualActual(ActualActual::ISDA)));
73 }
74
75 // utilities
76 Leg makeLeg(const Date& startDate, Integer length) const {
77 Date endDate = calendar.advance(date: startDate,period: length*Years,convention);
78 Schedule schedule(startDate, endDate, Period(frequency), calendar,
79 convention, convention,
80 DateGeneration::Forward, false);
81 return IborLeg(schedule, index)
82 .withNotionals(notionals: nominals)
83 .withPaymentDayCounter(index->dayCounter())
84 .withPaymentAdjustment(convention)
85 .withFixingDays(fixingDays);
86 }
87
88 ext::shared_ptr<PricingEngine> makeEngine(Volatility volatility) const {
89 Handle<Quote> vol(ext::shared_ptr<Quote>(
90 new SimpleQuote(volatility)));
91 return ext::shared_ptr<PricingEngine>(
92 new BlackCapFloorEngine(termStructure, vol));
93 }
94
95 ext::shared_ptr<PricingEngine> makeBachelierEngine(Volatility volatility) const {
96 Handle<Quote> vol(ext::shared_ptr<Quote>(
97 new SimpleQuote(volatility)));
98 return ext::shared_ptr<PricingEngine>(
99 new BachelierCapFloorEngine(termStructure, vol));
100 }
101
102 ext::shared_ptr<CapFloor> makeCapFloor(CapFloor::Type type,
103 const Leg& leg,
104 Rate strike,
105 Volatility volatility,
106 bool isLogNormal = true) const {
107 ext::shared_ptr<CapFloor> result;
108 switch (type) {
109 case CapFloor::Cap:
110 result = ext::shared_ptr<CapFloor>(
111 new Cap(leg, std::vector<Rate>(1, strike)));
112 break;
113 case CapFloor::Floor:
114 result = ext::shared_ptr<CapFloor>(
115 new Floor(leg, std::vector<Rate>(1, strike)));
116 break;
117 default:
118 QL_FAIL("unknown cap/floor type");
119 }
120 if(isLogNormal){
121 result->setPricingEngine(makeEngine(volatility));
122 } else {
123 result->setPricingEngine(makeBachelierEngine(volatility));
124 }
125 return result;
126 }
127 };
128
129 bool checkAbsError(Real x1, Real x2, Real tolerance){
130 return std::fabs(x: x1 - x2) < tolerance;
131 }
132
133 std::string typeToString(CapFloor::Type type) {
134 switch (type) {
135 case CapFloor::Cap:
136 return "cap";
137 case CapFloor::Floor:
138 return "floor";
139 case CapFloor::Collar:
140 return "collar";
141 default:
142 QL_FAIL("unknown cap/floor type");
143 }
144 }
145
146}
147
148
149void CapFloorTest::testVega() {
150
151 BOOST_TEST_MESSAGE("Testing cap/floor vega...");
152
153 using namespace capfloor_test;
154
155 CommonVars vars;
156
157 Integer lengths[] = { 1, 2, 3, 4, 5, 6, 7, 10, 15, 20, 30 };
158 Volatility vols[] = { 0.01, 0.05, 0.10, 0.15, 0.20 };
159 Rate strikes[] = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09 };
160 CapFloor::Type types[] = { CapFloor::Cap, CapFloor::Floor};
161
162 Date startDate = vars.termStructure->referenceDate();
163 static const Real shift = 1e-8;
164 static const Real tolerance = 0.005;
165
166 for (int length : lengths) {
167 for (Real vol : vols) {
168 for (Real strike : strikes) {
169 for (auto& type : types) {
170 Leg leg = vars.makeLeg(startDate, length);
171 ext::shared_ptr<CapFloor> capFloor = vars.makeCapFloor(type, leg, strike, volatility: vol);
172 ext::shared_ptr<CapFloor> shiftedCapFloor2 =
173 vars.makeCapFloor(type, leg, strike, volatility: vol + shift);
174 ext::shared_ptr<CapFloor> shiftedCapFloor1 =
175 vars.makeCapFloor(type, leg, strike, volatility: vol - shift);
176 Real value1 = shiftedCapFloor1->NPV();
177 Real value2 = shiftedCapFloor2->NPV();
178 Real numericalVega = (value2 - value1) / (2*shift);
179 if (numericalVega>1.0e-4) {
180 Real analyticalVega = capFloor->result<Real>(tag: "vega");
181 Real discrepancy =
182 std::fabs(x: numericalVega - analyticalVega);
183 discrepancy /= numericalVega;
184 if (discrepancy > tolerance)
185 BOOST_FAIL("failed to compute cap/floor vega:"
186 << "\n lengths: " << length * Years
187 << "\n strike: " << io::rate(strike) <<
188 //"\n types: " << types[h] <<
189 std::fixed << std::setprecision(12) << "\n calculated: "
190 << analyticalVega << "\n expected: " << numericalVega
191 << "\n discrepancy: " << io::rate(discrepancy)
192 << "\n tolerance: " << io::rate(tolerance));
193 }
194 }
195 }
196 }
197 }
198}
199
200void CapFloorTest::testStrikeDependency() {
201
202 BOOST_TEST_MESSAGE("Testing cap/floor dependency on strike...");
203
204 using namespace capfloor_test;
205
206 CommonVars vars;
207
208 Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 };
209 Volatility vols[] = { 0.01, 0.05, 0.10, 0.15, 0.20 };
210 Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
211
212 Date startDate = vars.termStructure->referenceDate();
213
214 for (int& length : lengths) {
215 for (Real vol : vols) {
216 // store the results for different strikes...
217 std::vector<Real> cap_values, floor_values;
218 for (Real strike : strikes) {
219 Leg leg = vars.makeLeg(startDate, length);
220 ext::shared_ptr<Instrument> cap =
221 vars.makeCapFloor(type: CapFloor::Cap, leg, strike, volatility: vol);
222 cap_values.push_back(x: cap->NPV());
223 ext::shared_ptr<Instrument> floor =
224 vars.makeCapFloor(type: CapFloor::Floor, leg, strike, volatility: vol);
225 floor_values.push_back(x: floor->NPV());
226 }
227 // and check that they go the right way
228 auto it = std::adjacent_find(first: cap_values.begin(), last: cap_values.end(), binary_pred: std::less<>());
229 if (it != cap_values.end()) {
230 Size n = it - cap_values.begin();
231 BOOST_FAIL("NPV is increasing with the strike in a cap: \n"
232 << std::setprecision(2) << " length: " << length << " years\n"
233 << " volatility: " << io::volatility(vol) << "\n"
234 << " value: " << cap_values[n]
235 << " at strike: " << io::rate(strikes[n]) << "\n"
236 << " value: " << cap_values[n + 1]
237 << " at strike: " << io::rate(strikes[n + 1]));
238 }
239 // same for floors
240 it = std::adjacent_find(first: floor_values.begin(), last: floor_values.end(), binary_pred: std::greater<>());
241 if (it != floor_values.end()) {
242 Size n = it - floor_values.begin();
243 BOOST_FAIL("NPV is decreasing with the strike in a floor: \n"
244 << std::setprecision(2) << " length: " << length << " years\n"
245 << " volatility: " << io::volatility(vol) << "\n"
246 << " value: " << floor_values[n]
247 << " at strike: " << io::rate(strikes[n]) << "\n"
248 << " value: " << floor_values[n + 1]
249 << " at strike: " << io::rate(strikes[n + 1]));
250 }
251 }
252 }
253}
254
255void CapFloorTest::testConsistency() {
256
257 BOOST_TEST_MESSAGE("Testing consistency between cap, floor and collar...");
258
259 using namespace capfloor_test;
260
261 CommonVars vars;
262
263 Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 };
264 Rate cap_rates[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
265 Rate floor_rates[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
266 Volatility vols[] = { 0.01, 0.05, 0.10, 0.15, 0.20 };
267
268 Date startDate = vars.termStructure->referenceDate();
269
270 for (int& length : lengths) {
271 for (Real& cap_rate : cap_rates) {
272 for (Real& floor_rate : floor_rates) {
273 for (Real vol : vols) {
274
275 Leg leg = vars.makeLeg(startDate, length);
276 ext::shared_ptr<CapFloor> cap =
277 vars.makeCapFloor(type: CapFloor::Cap, leg, strike: cap_rate, volatility: vol);
278 ext::shared_ptr<CapFloor> floor =
279 vars.makeCapFloor(type: CapFloor::Floor, leg, strike: floor_rate, volatility: vol);
280 Collar collar(leg, std::vector<Rate>(1, cap_rate),
281 std::vector<Rate>(1, floor_rate));
282 collar.setPricingEngine(vars.makeEngine(volatility: vol));
283
284 if (std::fabs(x: (cap->NPV() - floor->NPV()) - collar.NPV()) > 1e-10) {
285 BOOST_FAIL("inconsistency between cap, floor and collar:\n"
286 << " length: " << length << " years\n"
287 << " volatility: " << io::volatility(vol) << "\n"
288 << " cap value: " << cap->NPV()
289 << " at strike: " << io::rate(cap_rate) << "\n"
290 << " floor value: " << floor->NPV()
291 << " at strike: " << io::rate(floor_rate) << "\n"
292 << " collar value: " << collar.NPV());
293
294
295 // test re-composition by optionlets, N.B. two per year
296 Real capletsNPV = 0.0;
297 std::vector<ext::shared_ptr<CapFloor> > caplets;
298 for (Integer m = 0; m < length * 2; m++) {
299 caplets.push_back(x: cap->optionlet(n: m));
300 caplets[m]->setPricingEngine(vars.makeEngine(volatility: vol));
301 capletsNPV += caplets[m]->NPV();
302 }
303
304 if (std::fabs(x: cap->NPV() - capletsNPV) > 1e-10) {
305 BOOST_FAIL("sum of caplet NPVs does not equal cap NPV:\n"
306 << " length: " << length << " years\n"
307 << " volatility: " << io::volatility(vol) << "\n"
308 << " cap value: " << cap->NPV()
309 << " at strike: " << io::rate(cap_rate) << "\n"
310 << " sum of caplets value: " << capletsNPV
311 << " at strike (first): "
312 << io::rate(caplets[0]->capRates()[0]) << "\n");
313 }
314
315 Real floorletsNPV = 0.0;
316 std::vector<ext::shared_ptr<CapFloor> > floorlets;
317 for (Integer m = 0; m < length * 2; m++) {
318 floorlets.push_back(x: floor->optionlet(n: m));
319 floorlets[m]->setPricingEngine(vars.makeEngine(volatility: vol));
320 floorletsNPV += floorlets[m]->NPV();
321 }
322
323 if (std::fabs(x: floor->NPV() - floorletsNPV) > 1e-10) {
324 BOOST_FAIL("sum of floorlet NPVs does not equal floor NPV:\n"
325 << " length: " << length << " years\n"
326 << " volatility: " << io::volatility(vol) << "\n"
327 << " cap value: " << floor->NPV()
328 << " at strike: " << io::rate(floor_rate) << "\n"
329 << " sum of floorlets value: " << floorletsNPV
330 << " at strike (first): "
331 << io::rate(floorlets[0]->floorRates()[0]) << "\n");
332 }
333
334 Real collarletsNPV = 0.0;
335 std::vector<ext::shared_ptr<CapFloor> > collarlets;
336 for (Integer m = 0; m < length * 2; m++) {
337 collarlets.push_back(x: collar.optionlet(n: m));
338 collarlets[m]->setPricingEngine(vars.makeEngine(volatility: vol));
339 collarletsNPV += collarlets[m]->NPV();
340 }
341
342 if (std::fabs(x: collar.NPV() - collarletsNPV) > 1e-10) {
343 BOOST_FAIL("sum of collarlet NPVs does not equal floor NPV:\n"
344 << " length: " << length << " years\n"
345 << " volatility: " << io::volatility(vol) << "\n"
346 << " cap value: " << collar.NPV()
347 << " at strike floor: " << io::rate(floor_rate)
348 << " at strike cap: " << io::rate(cap_rate) << "\n"
349 << " sum of collarlets value: " << collarletsNPV
350 << " at strike floor (first): "
351 << io::rate(collarlets[0]->floorRates()[0])
352 << " at strike cap (first): "
353 << io::rate(collarlets[0]->capRates()[0]) << "\n");
354 }
355 }
356 }
357 }
358 }
359 }
360}
361
362void CapFloorTest::testParity() {
363
364 BOOST_TEST_MESSAGE("Testing cap/floor parity...");
365
366 using namespace capfloor_test;
367
368 CommonVars vars;
369
370 Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 };
371 Rate strikes[] = { 0., 0.03, 0.04, 0.05, 0.06, 0.07 };
372 Volatility vols[] = { 0.01, 0.05, 0.10, 0.15, 0.20 };
373
374 Date startDate = vars.termStructure->referenceDate();
375
376 for (int& length : lengths) {
377 for (Real strike : strikes) {
378 for (Real vol : vols) {
379
380 Leg leg = vars.makeLeg(startDate, length);
381 ext::shared_ptr<Instrument> cap =
382 vars.makeCapFloor(type: CapFloor::Cap, leg, strike, volatility: vol);
383 ext::shared_ptr<Instrument> floor =
384 vars.makeCapFloor(type: CapFloor::Floor, leg, strike, volatility: vol);
385 Date maturity = vars.calendar.advance(startDate, n: length, unit: Years, convention: vars.convention);
386 Schedule schedule(startDate, maturity, Period(vars.frequency), vars.calendar,
387 vars.convention, vars.convention, DateGeneration::Forward, false);
388 VanillaSwap swap(Swap::Payer, vars.nominals[0], schedule, strike,
389 vars.index->dayCounter(), schedule, vars.index, 0.0,
390 vars.index->dayCounter());
391 swap.setPricingEngine(
392 ext::shared_ptr<PricingEngine>(new DiscountingSwapEngine(vars.termStructure)));
393 if (std::fabs(x: (cap->NPV() - floor->NPV()) - swap.NPV()) > 1.0e-10) {
394 BOOST_FAIL("put/call parity violated:\n"
395 << " length: " << length << " years\n"
396 << " volatility: " << io::volatility(vol) << "\n"
397 << " strike: " << io::rate(strike) << "\n"
398 << " cap value: " << cap->NPV() << "\n"
399 << " floor value: " << floor->NPV() << "\n"
400 << " swap value: " << swap.NPV());
401 }
402 }
403 }
404 }
405}
406
407void CapFloorTest::testATMRate() {
408
409 BOOST_TEST_MESSAGE("Testing cap/floor ATM rate...");
410
411 using namespace capfloor_test;
412
413 CommonVars vars;
414
415 Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 };
416 Rate strikes[] = { 0., 0.03, 0.04, 0.05, 0.06, 0.07 };
417 Volatility vols[] = { 0.01, 0.05, 0.10, 0.15, 0.20 };
418
419 Date startDate = vars.termStructure->referenceDate();
420
421 for (int& length : lengths) {
422 Leg leg = vars.makeLeg(startDate, length);
423 Date maturity = vars.calendar.advance(startDate, n: length, unit: Years, convention: vars.convention);
424 Schedule schedule(startDate,maturity,
425 Period(vars.frequency),vars.calendar,
426 vars.convention,vars.convention,
427 DateGeneration::Forward,false);
428
429 for (Real strike : strikes) {
430 for (Real vol : vols) {
431 ext::shared_ptr<CapFloor> cap = vars.makeCapFloor(type: CapFloor::Cap, leg, strike, volatility: vol);
432 ext::shared_ptr<CapFloor> floor =
433 vars.makeCapFloor(type: CapFloor::Floor, leg, strike, volatility: vol);
434 Rate capATMRate = cap->atmRate(discountCurve: **vars.termStructure);
435 Rate floorATMRate = floor->atmRate(discountCurve: **vars.termStructure);
436 if (!checkAbsError(x1: floorATMRate, x2: capATMRate, tolerance: 1.0e-10))
437 BOOST_FAIL("Cap ATM Rate and floor ATM Rate should be equal :\n"
438 << " length: " << length << " years\n"
439 << " volatility: " << io::volatility(vol) << "\n"
440 << " strike: " << io::rate(strike) << "\n"
441 << " cap ATM rate: " << capATMRate << "\n"
442 << " floor ATM rate:" << floorATMRate << "\n"
443 << " relative Error:"
444 << relativeError(capATMRate, floorATMRate, capATMRate) * 100 << "%");
445 VanillaSwap swap(Swap::Payer, vars.nominals[0],
446 schedule, floorATMRate,
447 vars.index->dayCounter(),
448 schedule, vars.index, 0.0,
449 vars.index->dayCounter());
450 swap.setPricingEngine(ext::shared_ptr<PricingEngine>(
451 new DiscountingSwapEngine(vars.termStructure)));
452 Real swapNPV = swap.NPV();
453 if (!checkAbsError(x1: swapNPV, x2: 0, tolerance: 1.0e-10))
454 BOOST_FAIL("the NPV of a Swap struck at ATM rate "
455 "should be equal to 0:\n"
456 << " length: " << length << " years\n"
457 << " volatility: " << io::volatility(vol) << "\n"
458 << " ATM rate: " << io::rate(floorATMRate) << "\n"
459 << " swap NPV: " << swapNPV);
460 }
461 }
462 }
463}
464
465
466void CapFloorTest::testImpliedVolatility() {
467
468 BOOST_TEST_MESSAGE("Testing implied term volatility for cap and floor...");
469
470 using namespace capfloor_test;
471
472 CommonVars vars;
473
474 Size maxEvaluations = 100;
475 Real tolerance = 1.0e-8;
476
477 CapFloor::Type types[] = { CapFloor::Cap, CapFloor::Floor };
478 Rate strikes[] = { 0.02, 0.03, 0.04 };
479 Integer lengths[] = { 1, 5, 10 };
480
481 // test data
482 Rate rRates[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 };
483 Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 };
484
485 for (int& length : lengths) {
486 Leg leg = vars.makeLeg(startDate: vars.settlement, length);
487
488 for (auto& type : types) {
489 for (Real strike : strikes) {
490
491 ext::shared_ptr<CapFloor> capfloor = vars.makeCapFloor(type, leg, strike, volatility: 0.0);
492
493 for (Real r : rRates) {
494 for (Real v : vols) {
495
496 vars.termStructure.linkTo(h: flatRate(today: vars.settlement, forward: r, dc: Actual360()));
497 capfloor->setPricingEngine(vars.makeEngine(volatility: v));
498
499 Real value = capfloor->NPV();
500 Volatility implVol = 0.0;
501 try {
502 implVol =
503 capfloor->impliedVolatility(price: value,
504 disc: vars.termStructure,
505 guess: 0.10,
506 accuracy: tolerance,
507 maxEvaluations,
508 minVol: 10.0e-7, maxVol: 4.0,
509 type: ShiftedLognormal, displacement: 0.0);
510 } catch (std::exception& e) {
511 // couldn't bracket?
512 capfloor->setPricingEngine(vars.makeEngine(volatility: 0.0));
513 Real value2 = capfloor->NPV();
514 if (std::fabs(x: value-value2) < tolerance) {
515 // ok, just skip:
516 continue;
517 }
518 // otherwise, report error
519 BOOST_ERROR("implied vol failure: "
520 << typeToString(type) << "\n strike: "
521 << io::rate(strike) << "\n risk-free: " << io::rate(r)
522 << "\n length: " << length << "Y"
523 << "\n volatility: " << io::volatility(v)
524 << "\n price: " << value << "\n"
525 << e.what());
526 }
527 if (std::fabs(x: implVol-v) > tolerance) {
528 // the difference might not matter
529 capfloor->setPricingEngine(
530 vars.makeEngine(volatility: implVol));
531 Real value2 = capfloor->NPV();
532 if (std::fabs(x: value-value2) > tolerance) {
533 BOOST_FAIL("implied vol failure: "
534 << typeToString(type)
535 << "\n strike: " << io::rate(strike)
536 << "\n risk-free: " << io::rate(r)
537 << "\n length: " << length << "Y"
538 << "\n volatility: " << io::volatility(v)
539 << "\n price: " << value
540 << "\n implied vol: " << io::volatility(implVol)
541 << "\n implied price: " << value2);
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549}
550
551void CapFloorTest::testCachedValue() {
552
553 BOOST_TEST_MESSAGE("Testing Black cap/floor price against cached values...");
554
555 using namespace capfloor_test;
556
557 CommonVars vars;
558
559 Date cachedToday(14,March,2002),
560 cachedSettlement(18,March,2002);
561 Settings::instance().evaluationDate() = cachedToday;
562 vars.termStructure.linkTo(h: flatRate(today: cachedSettlement, forward: 0.05, dc: Actual360()));
563 Date startDate = vars.termStructure->referenceDate();
564 Leg leg = vars.makeLeg(startDate,length: 20);
565 ext::shared_ptr<Instrument> cap = vars.makeCapFloor(type: CapFloor::Cap,leg,
566 strike: 0.07,volatility: 0.20);
567 ext::shared_ptr<Instrument> floor = vars.makeCapFloor(type: CapFloor::Floor,leg,
568 strike: 0.03,volatility: 0.20);
569
570 Real cachedCapNPV, cachedFloorNPV ;
571 if (!IborCoupon::Settings::instance().usingAtParCoupons()) {
572 // index fixing price
573 cachedCapNPV = 6.87630307745,
574 cachedFloorNPV = 2.65796764715;
575 } else {
576 // par coupon price
577 cachedCapNPV = 6.87570026732;
578 cachedFloorNPV = 2.65812927959;
579 }
580
581 // test Black cap price against cached value
582 if (std::fabs(x: cap->NPV()-cachedCapNPV) > 1.0e-11)
583 BOOST_ERROR(
584 "failed to reproduce cached cap value:\n"
585 << std::setprecision(12)
586 << " calculated: " << cap->NPV() << "\n"
587 << " expected: " << cachedCapNPV);
588 // test Black floor price against cached value
589 if (std::fabs(x: floor->NPV()-cachedFloorNPV) > 1.0e-11)
590 BOOST_ERROR(
591 "failed to reproduce cached floor value:\n"
592 << std::setprecision(12)
593 << " calculated: " << floor->NPV() << "\n"
594 << " expected: " << cachedFloorNPV);
595}
596
597void CapFloorTest::testCachedValueFromOptionLets() {
598
599 BOOST_TEST_MESSAGE("Testing Black cap/floor price as a sum of optionlets prices against cached values...");
600
601 using namespace capfloor_test;
602
603 CommonVars vars;
604
605 Date cachedToday(14,March,2002),
606 cachedSettlement(18,March,2002);
607 Settings::instance().evaluationDate() = cachedToday;
608 ext::shared_ptr<YieldTermStructure> baseCurve = flatRate(today: cachedSettlement,
609 forward: 0.05, dc: Actual360());
610 vars.termStructure.linkTo(h: baseCurve);
611 Date startDate = vars.termStructure->referenceDate();
612 Leg leg = vars.makeLeg(startDate,length: 20);
613
614 ext::shared_ptr<Instrument> cap = vars.makeCapFloor(type: CapFloor::Cap,leg,
615 strike: 0.07,volatility: 0.20);
616 ext::shared_ptr<Instrument> floor = vars.makeCapFloor(type: CapFloor::Floor,leg,
617 strike: 0.03,volatility: 0.20);
618 Real calculatedCapletsNPV = 0.0,
619 calculatedFloorletsNPV = 0.0;
620
621 Real cachedCapNPV, cachedFloorNPV;
622 if (IborCoupon::Settings::instance().usingAtParCoupons()) {
623 cachedCapNPV = 6.87570026732;
624 cachedFloorNPV = 2.65812927959;
625 } else {
626 cachedCapNPV = 6.87630307745;
627 cachedFloorNPV = 2.65796764715;
628 }
629
630 // test Black floor price against cached value
631 std::vector<Real> capletPrices;
632 std::vector<Real> floorletPrices;
633
634 capletPrices = cap->result<std::vector<Real> >(tag: "optionletsPrice");
635 floorletPrices = floor->result<std::vector<Real> >(tag: "optionletsPrice");
636
637 if (capletPrices.size() != 40)
638 BOOST_ERROR(
639 "failed to produce prices for all caplets:\n"
640 << " calculated: " << capletPrices.size() << " caplet prices\n"
641 << " expected: " << 40);
642
643 for (Real capletPrice : capletPrices) {
644 calculatedCapletsNPV += capletPrice;
645 }
646
647 for (Real floorletPrice : floorletPrices) {
648 calculatedFloorletsNPV += floorletPrice;
649 }
650
651 if (std::fabs(x: calculatedCapletsNPV-cachedCapNPV) > 1.0e-11)
652 BOOST_ERROR(
653 "failed to reproduce cached cap value from its caplets' values:\n"
654 << std::setprecision(12)
655 << " calculated: " << calculatedCapletsNPV << "\n"
656 << " expected: " << cachedCapNPV);
657 // test Black floor price against cached value
658 if (std::fabs(x: calculatedFloorletsNPV-cachedFloorNPV) > 1.0e-11)
659 BOOST_ERROR(
660 "failed to reproduce cached floor value from its floorlets' values:\n"
661 << std::setprecision(12)
662 << " calculated: " << calculatedFloorletsNPV << "\n"
663 << " expected: " << cachedFloorNPV);
664}
665
666void CapFloorTest::testOptionLetsDelta() {
667
668 BOOST_TEST_MESSAGE("Testing Black caplet/floorlet delta coefficients against finite difference values...");
669
670 using namespace capfloor_test;
671
672 CommonVars vars;
673
674 Date cachedToday(14,March,2002),
675 cachedSettlement(18,March,2002);
676 Settings::instance().evaluationDate() = cachedToday;
677 ext::shared_ptr<YieldTermStructure> baseCurve = flatRate(today: cachedSettlement,
678 forward: 0.05, dc: Actual360());
679 RelinkableHandle<YieldTermStructure> baseCurveHandle(baseCurve);
680
681 // Define spreaded curve with eps as spread used for FD sensitivities
682 Real eps = 1.0e-6;
683 ext::shared_ptr<SimpleQuote> spread(new SimpleQuote(0.0));
684 ext::shared_ptr<YieldTermStructure> spreadCurve(new ZeroSpreadedTermStructure(
685 baseCurveHandle,
686 Handle<Quote>(spread),
687 Continuous,
688 Annual,
689 Actual360()));
690 vars.termStructure.linkTo(h: spreadCurve);
691 Date startDate = vars.termStructure->referenceDate();
692 Leg leg = vars.makeLeg(startDate,length: 20);
693
694 ext::shared_ptr<CapFloor> cap = vars.makeCapFloor(type: CapFloor::Cap,leg,
695 strike: 0.05,volatility: 0.20);
696 ext::shared_ptr<CapFloor> floor = vars.makeCapFloor(type: CapFloor::Floor,leg,
697 strike: 0.05,volatility: 0.20);
698
699
700 //so far tests pass, now try to get additional results and it will fail
701 Size capletsNum = cap->capRates().size();
702 std::vector<Real> capletUpPrices,
703 capletDownPrices,
704 capletAnalyticDelta,
705 capletDiscountFactorsUp,
706 capletDiscountFactorsDown,
707 capletForwardsUp,
708 capletForwardsDown,
709 capletFDDelta(capletsNum, 0.0);
710 Size floorletNum = floor->floorRates().size();
711 std::vector<Real> floorletUpPrices,
712 floorletDownPrices,
713 floorletAnalyticDelta,
714 floorletDiscountFactorsUp,
715 floorletDiscountFactorsDown,
716 floorletForwardsUp,
717 floorletForwardsDown,
718 floorletFDDelta(floorletNum, 0.0);
719
720 capletAnalyticDelta = cap->result<std::vector<Real> >(tag: "optionletsDelta");
721 floorletAnalyticDelta = floor->result<std::vector<Real> >(tag: "optionletsDelta");
722
723 spread->setValue(eps);
724 capletUpPrices = cap->result<std::vector<Real> >(tag: "optionletsPrice");
725 floorletUpPrices = floor->result<std::vector<Real> >(tag: "optionletsPrice");
726 capletDiscountFactorsUp = cap->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
727 floorletDiscountFactorsUp = floor->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
728 capletForwardsUp = cap->result<std::vector<Real> >(tag: "optionletsAtmForward");
729 floorletForwardsUp = floor->result<std::vector<Real> >(tag: "optionletsAtmForward");
730
731 spread->setValue(-eps);
732 capletDownPrices = cap->result<std::vector<Real> >(tag: "optionletsPrice");
733 floorletDownPrices = floor->result<std::vector<Real> >(tag: "optionletsPrice");
734 capletDiscountFactorsDown = cap->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
735 floorletDiscountFactorsDown = floor->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
736 capletForwardsDown = cap->result<std::vector<Real> >(tag: "optionletsAtmForward");
737 floorletForwardsDown = floor->result<std::vector<Real> >(tag: "optionletsAtmForward");
738
739 Real accrualFactor;
740 Leg capLeg = cap->floatingLeg();
741 Leg floorLeg = floor->floatingLeg();
742
743 for (Size n=1; n < capletUpPrices.size(); n++){
744 // calculating only caplet's FD sensitivity w.r.t. forward rate
745 // without the effect of sensitivity related to changed discount factor
746 ext::shared_ptr<FloatingRateCoupon> c = ext::dynamic_pointer_cast<FloatingRateCoupon>(r: capLeg[n]);
747 accrualFactor = c->nominal() * c->accrualPeriod() * c->gearing();
748 capletFDDelta[n] = (capletUpPrices[n] / capletDiscountFactorsUp[n]
749 - capletDownPrices[n] / capletDiscountFactorsDown[n])
750 / (capletForwardsUp[n] - capletForwardsDown[n])
751 / accrualFactor;
752 }
753
754 for (Size n=0; n<floorletUpPrices.size(); n++){
755 // calculating only caplet's FD sensitivity w.r.t. forward rate
756 // without the effect of sensitivity related to changed discount factor
757 ext::shared_ptr<FloatingRateCoupon> c = ext::dynamic_pointer_cast<FloatingRateCoupon>(r: floorLeg[n]);
758 accrualFactor = c->nominal() * c->accrualPeriod() * c->gearing();
759 floorletFDDelta[n] = (floorletUpPrices[n] / floorletDiscountFactorsUp[n]
760 - floorletDownPrices[n] / floorletDiscountFactorsDown[n])
761 / (floorletForwardsUp[n] - floorletForwardsDown[n])
762 / accrualFactor;
763 }
764
765 for (Size n=0; n<capletAnalyticDelta.size(); n++){
766 if (std::fabs(x: capletAnalyticDelta[n]-capletFDDelta[n]) > 1.0e-6)
767 BOOST_ERROR(
768 "failed to compare analytical and finite difference caplet delta:\n"
769 << "caplet number:\t" << n << "\n"
770 << std::setprecision(12)
771 << " finite difference: " << capletFDDelta[n]<< "\n"
772 << " analytical value: " << capletAnalyticDelta[n] << "\n"
773 << " resulting ratio: " << capletFDDelta[n] / capletAnalyticDelta[n]);
774 }
775
776 for (Size n=0; n<floorletAnalyticDelta.size(); n++){
777 if (std::fabs(x: floorletAnalyticDelta[n]-floorletFDDelta[n]) > 1.0e-6)
778 BOOST_ERROR(
779 "failed to compare analytical and finite difference floorlet delta:\n"
780 << "floorlet number:\t" << n << "\n"
781 << std::setprecision(12)
782 << " finite difference: " << floorletFDDelta[n]<< "\n"
783 << " analytical value: " << floorletAnalyticDelta[n] << "\n"
784 << " resulting ratio: " << floorletFDDelta[n] / floorletAnalyticDelta[n]);
785 }
786
787}
788
789void CapFloorTest::testBachelierOptionLetsDelta() {
790
791 BOOST_TEST_MESSAGE("Testing Bachelier caplet/floorlet delta coefficients against finite difference values...");
792
793 using namespace capfloor_test;
794
795 CommonVars vars;
796
797 Date cachedToday(14,March,2002),
798 cachedSettlement(18,March,2002);
799 Settings::instance().evaluationDate() = cachedToday;
800 ext::shared_ptr<YieldTermStructure> baseCurve = flatRate(today: cachedSettlement,
801 forward: 0.05, dc: Actual360());
802 RelinkableHandle<YieldTermStructure> baseCurveHandle(baseCurve);
803
804 // Define spreaded curve with eps as spread used for FD sensitivities
805 Real eps = 1.0e-6;
806 ext::shared_ptr<SimpleQuote> spread(new SimpleQuote(0.0));
807 ext::shared_ptr<YieldTermStructure> spreadCurve(new ZeroSpreadedTermStructure(
808 baseCurveHandle,
809 Handle<Quote>(spread),
810 Continuous,
811 Annual,
812 Actual360()));
813 vars.termStructure.linkTo(h: spreadCurve);
814 Date startDate = vars.termStructure->referenceDate();
815 Leg leg = vars.makeLeg(startDate,length: 20);
816
817 // Use normal model (BachelierCapFloorEngine)
818 bool isLogNormal = false;
819
820 ext::shared_ptr<CapFloor> cap = vars.makeCapFloor(type: CapFloor::Cap,leg,
821 strike: 0.05, volatility: 0.01, isLogNormal);
822 ext::shared_ptr<CapFloor> floor = vars.makeCapFloor(type: CapFloor::Floor,leg,
823 strike: 0.05, volatility: 0.01, isLogNormal);
824
825
826 //so far tests pass, now try to get additional results and it will fail
827 Size capletsNum = cap->capRates().size();
828 std::vector<Real> capletUpPrices,
829 capletDownPrices,
830 capletAnalyticDelta,
831 capletDiscountFactorsUp,
832 capletDiscountFactorsDown,
833 capletForwardsUp,
834 capletForwardsDown,
835 capletFDDelta(capletsNum, 0.0);
836 Size floorletNum = floor->floorRates().size();
837 std::vector<Real> floorletUpPrices,
838 floorletDownPrices,
839 floorletAnalyticDelta,
840 floorletDiscountFactorsUp,
841 floorletDiscountFactorsDown,
842 floorletForwardsUp,
843 floorletForwardsDown,
844 floorletFDDelta(floorletNum, 0.0);
845
846 capletAnalyticDelta = cap->result<std::vector<Real> >(tag: "optionletsDelta");
847 floorletAnalyticDelta = floor->result<std::vector<Real> >(tag: "optionletsDelta");
848
849 spread->setValue(eps);
850 capletUpPrices = cap->result<std::vector<Real> >(tag: "optionletsPrice");
851 floorletUpPrices = floor->result<std::vector<Real> >(tag: "optionletsPrice");
852 capletDiscountFactorsUp = cap->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
853 floorletDiscountFactorsUp = floor->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
854 capletForwardsUp = cap->result<std::vector<Real> >(tag: "optionletsAtmForward");
855 floorletForwardsUp = floor->result<std::vector<Real> >(tag: "optionletsAtmForward");
856
857 spread->setValue(-eps);
858 capletDownPrices = cap->result<std::vector<Real> >(tag: "optionletsPrice");
859 floorletDownPrices = floor->result<std::vector<Real> >(tag: "optionletsPrice");
860 capletDiscountFactorsDown = cap->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
861 floorletDiscountFactorsDown = floor->result<std::vector<Real> >(tag: "optionletsDiscountFactor");
862 capletForwardsDown = cap->result<std::vector<Real> >(tag: "optionletsAtmForward");
863 floorletForwardsDown = floor->result<std::vector<Real> >(tag: "optionletsAtmForward");
864
865 Real accrualFactor;
866 Leg capLeg = cap->floatingLeg();
867 Leg floorLeg = floor->floatingLeg();
868
869 for (Size n=1; n < capletUpPrices.size(); n++){
870 // calculating only caplet's FD sensitivity w.r.t. forward rate
871 // without the effect of sensitivity related to changed discount factor
872 ext::shared_ptr<FloatingRateCoupon> c = ext::dynamic_pointer_cast<FloatingRateCoupon>(r: capLeg[n]);
873 accrualFactor = c->nominal() * c->accrualPeriod() * c->gearing();
874 capletFDDelta[n] = (capletUpPrices[n] / capletDiscountFactorsUp[n]
875 - capletDownPrices[n] / capletDiscountFactorsDown[n])
876 / (capletForwardsUp[n] - capletForwardsDown[n])
877 / accrualFactor;
878 }
879
880 for (Size n=0; n<floorletUpPrices.size(); n++){
881 // calculating only caplet's FD sensitivity w.r.t. forward rate
882 // without the effect of sensitivity related to changed discount factor
883 ext::shared_ptr<FloatingRateCoupon> c = ext::dynamic_pointer_cast<FloatingRateCoupon>(r: floorLeg[n]);
884 accrualFactor = c->nominal() * c->accrualPeriod() * c->gearing();
885 floorletFDDelta[n] = (floorletUpPrices[n] / floorletDiscountFactorsUp[n]
886 - floorletDownPrices[n] / floorletDiscountFactorsDown[n])
887 / (floorletForwardsUp[n] - floorletForwardsDown[n])
888 / accrualFactor;
889 }
890
891 for (Size n=0; n<capletAnalyticDelta.size(); n++){
892 if (std::fabs(x: capletAnalyticDelta[n]-capletFDDelta[n]) > 1.0e-6)
893 BOOST_ERROR(
894 "failed to compare analytical and finite difference caplet delta:\n"
895 << "caplet number:\t" << n << "\n"
896 << std::setprecision(12)
897 << " finite difference: " << capletFDDelta[n]<< "\n"
898 << " analytical value: " << capletAnalyticDelta[n] << "\n"
899 << " resulting ratio: " << capletFDDelta[n] / capletAnalyticDelta[n]);
900 }
901
902 for (Size n=0; n<floorletAnalyticDelta.size(); n++){
903 if (std::fabs(x: floorletAnalyticDelta[n]-floorletFDDelta[n]) > 1.0e-6)
904 BOOST_ERROR(
905 "failed to compare analytical and finite difference floorlet delta:\n"
906 << "floorlet number:\t" << n << "\n"
907 << std::setprecision(12)
908 << " finite difference: " << floorletFDDelta[n]<< "\n"
909 << " analytical value: " << floorletAnalyticDelta[n] << "\n"
910 << " resulting ratio: " << floorletFDDelta[n] / floorletAnalyticDelta[n]);
911 }
912
913}
914
915test_suite* CapFloorTest::suite() {
916 auto* suite = BOOST_TEST_SUITE("Cap and floor tests");
917 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testStrikeDependency));
918 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testConsistency));
919 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testParity));
920 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testVega));
921 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testATMRate));
922 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testImpliedVolatility));
923 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testCachedValue));
924 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testCachedValueFromOptionLets));
925 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testOptionLetsDelta));
926 suite->add(QUANTLIB_TEST_CASE(&CapFloorTest::testBachelierOptionLetsDelta));
927 return suite;
928}
929
930

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