1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2004, 2005, 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// TODO: Figure out why tests for options with both continuous and discrete
21// dividends fail.
22
23#include "dividendoption.hpp"
24#include "utilities.hpp"
25#include <ql/instruments/dividendvanillaoption.hpp>
26#include <ql/instruments/vanillaoption.hpp>
27#include <ql/pricingengines/vanilla/analyticdividendeuropeanengine.hpp>
28#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
29#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
30#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
31#include <ql/termstructures/yield/flatforward.hpp>
32#include <ql/time/daycounters/actual360.hpp>
33#include <ql/utilities/dataformatters.hpp>
34#include <map>
35
36using namespace QuantLib;
37using namespace boost::unit_test_framework;
38
39#undef REPORT_FAILURE
40#define REPORT_FAILURE(greekName, payoff, exercise, s, q, r, today, \
41 v, expected, calculated, error, tolerance) \
42 BOOST_ERROR(exerciseTypeToString(exercise) << " " \
43 << payoff->optionType() << " option with " \
44 << payoffTypeToString(payoff) << " payoff:\n" \
45 << " spot value: " << s << "\n" \
46 << " strike: " << payoff->strike() << "\n" \
47 << " dividend yield: " << io::rate(q) << "\n" \
48 << " risk-free rate: " << io::rate(r) << "\n" \
49 << " reference date: " << today << "\n" \
50 << " maturity: " << exercise->lastDate() << "\n" \
51 << " volatility: " << io::volatility(v) << "\n\n" \
52 << " expected " << greekName << ": " << expected << "\n" \
53 << " calculated " << greekName << ": " << calculated << "\n"\
54 << " error: " << error << "\n" \
55 << " tolerance: " << tolerance);
56
57// tests
58
59void DividendOptionTest::testEuropeanValues() {
60
61 BOOST_TEST_MESSAGE(
62 "Testing dividend European option values with no dividends...");
63
64 Real tolerance = 1.0e-5;
65
66 Option::Type types[] = { Option::Call, Option::Put };
67 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
68 Real underlyings[] = { 100.0 };
69 Rate qRates[] = { 0.00, 0.10, 0.30 };
70 Rate rRates[] = { 0.01, 0.05, 0.15 };
71 Integer lengths[] = { 1, 2 };
72 Volatility vols[] = { 0.05, 0.20, 0.70 };
73
74 DayCounter dc = Actual360();
75 Date today = Date::todaysDate();
76 Settings::instance().evaluationDate() = today;
77
78 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
79 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
80 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
81 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
82 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
83 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
84 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
85
86 for (auto& type : types) {
87 for (Real strike : strikes) {
88 for (int length : lengths) {
89 Date exDate = today + length * Years;
90 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
91
92 std::vector<Date> dividendDates;
93 std::vector<Real> dividends;
94 for (Date d = today + 3 * Months; d < exercise->lastDate(); d += 6 * Months) {
95 dividendDates.push_back(x: d);
96 dividends.push_back(x: 0.0);
97 }
98
99 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
100
101 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
102 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
103
104 ext::shared_ptr<PricingEngine> ref_engine(new AnalyticEuropeanEngine(stochProcess));
105
106 VanillaOption ref_option(payoff, exercise);
107 ref_option.setPricingEngine(ref_engine);
108
109 QL_DEPRECATED_DISABLE_WARNING
110 ext::shared_ptr<PricingEngine> engine1(new AnalyticDividendEuropeanEngine(stochProcess));
111
112 DividendVanillaOption option1(payoff, exercise, dividendDates, dividends);
113 QL_DEPRECATED_ENABLE_WARNING
114 option1.setPricingEngine(engine1);
115
116 auto engine2 =
117 ext::make_shared<AnalyticDividendEuropeanEngine>(args&: stochProcess, args: DividendVector(dividendDates, dividends));
118
119 VanillaOption option2(payoff, exercise);
120 option2.setPricingEngine(engine2);
121
122 auto engine3 =
123 ext::make_shared<AnalyticDividendEuropeanEngine>(args&: stochProcess, args: DividendVector(dividendDates: { exDate + 6*Months }, dividends: { 1.0 }));
124
125 VanillaOption option3(payoff, exercise);
126 option3.setPricingEngine(engine3);
127
128 for (Real u : underlyings) {
129 for (Real m : qRates) {
130 for (Real n : rRates) {
131 for (Real v : vols) {
132 Rate q = m, r = n;
133 spot->setValue(u);
134 qRate->setValue(q);
135 rRate->setValue(r);
136 vol->setValue(v);
137
138 Real expected = ref_option.NPV();
139 Real calculated1 = option1.NPV();
140 Real calculated2 = option2.NPV();
141 Real calculated3 = option2.NPV();
142 Real error1 = std::fabs(x: calculated1 - expected);
143 Real error2 = std::fabs(x: calculated2 - expected);
144 Real error3 = std::fabs(x: calculated3 - expected);
145 if (error1 > tolerance) {
146 REPORT_FAILURE("value start limit", payoff, exercise, u, q, r,
147 today, v, expected, calculated1, error1,
148 tolerance);
149 }
150 if (error2 > tolerance) {
151 REPORT_FAILURE("value start limit", payoff, exercise, u, q, r,
152 today, v, expected, calculated2, error2,
153 tolerance);
154 }
155 if (error3 > tolerance) {
156 REPORT_FAILURE("value start limit", payoff, exercise, u, q, r,
157 today, v, expected, calculated3, error3,
158 tolerance);
159 }
160 }
161 }
162 }
163 }
164 }
165 }
166 }
167}
168
169// Reference pg. 253 - Hull - Options, Futures, and Other Derivatives 5th ed
170// Exercise 12.8
171
172void DividendOptionTest::testEuropeanKnownValue() {
173
174 BOOST_TEST_MESSAGE("Testing dividend European option against known value...");
175
176 Real tolerance = 1.0e-2;
177 Real expected = 3.67;
178
179 DayCounter dc = Actual360();
180 Date today = Date::todaysDate();
181 Settings::instance().evaluationDate() = today;
182
183 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
184 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
185 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
186 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
187 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
188 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
189 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
190
191 Date exDate = today + 180 * Days;
192 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
193
194 std::vector<Date> dividendDates = {today + 2 * 30 * Days, today + 5 * 30 * Days};
195 std::vector<Real> dividends = {0.50, 0.50};
196
197 ext::shared_ptr<StrikedTypePayoff> payoff(
198 new PlainVanillaPayoff(Option::Call, 40.0));
199
200 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
201 new BlackScholesMertonProcess(Handle<Quote>(spot),
202 qTS, rTS, volTS));
203
204 QL_DEPRECATED_DISABLE_WARNING
205 ext::shared_ptr<PricingEngine> engine1(
206 new AnalyticDividendEuropeanEngine(stochProcess));
207
208 DividendVanillaOption option1(payoff, exercise,
209 dividendDates, dividends);
210 QL_DEPRECATED_ENABLE_WARNING
211 option1.setPricingEngine(engine1);
212
213 auto engine2 = ext::make_shared<AnalyticDividendEuropeanEngine>(
214 args&: stochProcess, args: DividendVector(dividendDates, dividends));
215
216 VanillaOption option2(payoff, exercise);
217 option2.setPricingEngine(engine2);
218
219 Real u = 40.0;
220 Rate q = 0.0, r = 0.09;
221 Volatility v = 0.30;
222 spot->setValue(u);
223 qRate->setValue(q);
224 rRate->setValue(r);
225 vol->setValue(v);
226
227 Real calculated = option1.NPV();
228 Real error = std::fabs(x: calculated-expected);
229 if (error > tolerance) {
230 REPORT_FAILURE("value start limit",
231 payoff, exercise,
232 u, q, r, today, v,
233 expected, calculated,
234 error, tolerance);
235 }
236
237 calculated = option1.NPV();
238 error = std::fabs(x: calculated-expected);
239 if (error > tolerance) {
240 REPORT_FAILURE("value start limit",
241 payoff, exercise,
242 u, q, r, today, v,
243 expected, calculated,
244 error, tolerance);
245 }
246}
247
248
249void DividendOptionTest::testEuropeanStartLimit() {
250
251 BOOST_TEST_MESSAGE(
252 "Testing dividend European option with a dividend on today's date...");
253
254 Real tolerance = 1.0e-5;
255 Real dividendValue = 10.0;
256
257 Option::Type types[] = { Option::Call, Option::Put };
258 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
259 Real underlyings[] = { 100.0 };
260 Rate qRates[] = { 0.00, 0.10, 0.30 };
261 Rate rRates[] = { 0.01, 0.05, 0.15 };
262 Integer lengths[] = { 1, 2 };
263 Volatility vols[] = { 0.05, 0.20, 0.70 };
264
265 DayCounter dc = Actual360();
266 Date today = Date::todaysDate();
267 Settings::instance().evaluationDate() = today;
268
269 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
270 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
271 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
272 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
273 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
274 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
275 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
276
277 for (auto& type : types) {
278 for (Real strike : strikes) {
279 for (int length : lengths) {
280 Date exDate = today + length * Years;
281 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
282
283 std::vector<Date> dividendDates = {today};
284 std::vector<Real> dividends = {dividendValue};
285
286 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
287
288 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
289 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
290
291 ext::shared_ptr<PricingEngine> ref_engine(new AnalyticEuropeanEngine(stochProcess));
292
293 VanillaOption ref_option(payoff, exercise);
294 ref_option.setPricingEngine(ref_engine);
295
296 QL_DEPRECATED_DISABLE_WARNING
297 ext::shared_ptr<PricingEngine> engine1(
298 new AnalyticDividendEuropeanEngine(stochProcess));
299
300 DividendVanillaOption option1(payoff, exercise, dividendDates, dividends);
301 QL_DEPRECATED_ENABLE_WARNING
302 option1.setPricingEngine(engine1);
303
304 auto engine2 = ext::make_shared<AnalyticDividendEuropeanEngine>(
305 args&: stochProcess, args: DividendVector(dividendDates, dividends));
306
307 VanillaOption option2(payoff, exercise);
308 option2.setPricingEngine(engine2);
309
310 for (Real u : underlyings) {
311 for (Real m : qRates) {
312 for (Real n : rRates) {
313 for (Real v : vols) {
314 Rate q = m, r = n;
315 spot->setValue(u);
316 qRate->setValue(q);
317 rRate->setValue(r);
318 vol->setValue(v);
319
320 Real calculated1 = option1.NPV();
321 Real calculated2 = option2.NPV();
322 spot->setValue(u - dividendValue);
323 Real expected = ref_option.NPV();
324 Real error = std::fabs(x: calculated1 - expected);
325 if (error > tolerance) {
326 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
327 expected, calculated1, error, tolerance);
328 }
329 error = std::fabs(x: calculated2 - expected);
330 if (error > tolerance) {
331 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
332 expected, calculated2, error, tolerance);
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340 }
341}
342
343void DividendOptionTest::testEuropeanEndLimit() {
344
345 BOOST_TEST_MESSAGE(
346 "Testing dividend European option values with end limits...");
347
348 Real tolerance = 1.0e-5;
349 Real dividendValue = 10.0;
350
351 Option::Type types[] = { Option::Call, Option::Put };
352 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
353 Real underlyings[] = { 100.0 };
354 Rate qRates[] = { 0.00, 0.10, 0.30 };
355 Rate rRates[] = { 0.01, 0.05, 0.15 };
356 Integer lengths[] = { 1, 2 };
357 Volatility vols[] = { 0.05, 0.20, 0.70 };
358
359 DayCounter dc = Actual360();
360 Date today = Date::todaysDate();
361 Settings::instance().evaluationDate() = today;
362
363 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
364 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
365 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
366 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
367 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
368 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
369 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
370
371 for (auto& type : types) {
372 for (Real strike : strikes) {
373 for (int length : lengths) {
374 Date exDate = today + length * Years;
375 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
376
377 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
378 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
379
380 std::vector<Date> dividendDates = {exercise->lastDate()};
381 std::vector<Real> dividends = {dividendValue};
382
383 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
384
385 ext::shared_ptr<StrikedTypePayoff> refPayoff(
386 new PlainVanillaPayoff(type, strike + dividendValue));
387
388 ext::shared_ptr<PricingEngine> ref_engine(new AnalyticEuropeanEngine(stochProcess));
389
390 VanillaOption ref_option(refPayoff, exercise);
391 ref_option.setPricingEngine(ref_engine);
392
393 QL_DEPRECATED_DISABLE_WARNING
394 ext::shared_ptr<PricingEngine> engine1(
395 new AnalyticDividendEuropeanEngine(stochProcess));
396
397 DividendVanillaOption option1(payoff, exercise, dividendDates, dividends);
398 QL_DEPRECATED_ENABLE_WARNING
399 option1.setPricingEngine(engine1);
400
401 auto engine2 = ext::make_shared<AnalyticDividendEuropeanEngine>(
402 args&: stochProcess, args: DividendVector(dividendDates, dividends));
403
404 VanillaOption option2(payoff, exercise);
405 option2.setPricingEngine(engine2);
406
407 for (Real u : underlyings) {
408 for (Real m : qRates) {
409 for (Real n : rRates) {
410 for (Real v : vols) {
411 Rate q = m, r = n;
412 spot->setValue(u);
413 qRate->setValue(q);
414 rRate->setValue(r);
415 vol->setValue(v);
416
417 Real expected = ref_option.NPV();
418 Real calculated = option1.NPV();
419 Real error = std::fabs(x: calculated - expected);
420 if (error > tolerance) {
421 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
422 expected, calculated, error, tolerance);
423 }
424 calculated = option2.NPV();
425 error = std::fabs(x: calculated - expected);
426 if (error > tolerance) {
427 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
428 expected, calculated, error, tolerance);
429 }
430 }
431 }
432 }
433 }
434 }
435 }
436 }
437}
438
439
440void DividendOptionTest::testOldEuropeanGreeks() {
441
442 BOOST_TEST_MESSAGE("Testing old-style dividend European option greeks...");
443
444 std::map<std::string,Real> calculated, expected, tolerance;
445 tolerance["delta"] = 1.0e-5;
446 tolerance["gamma"] = 1.0e-5;
447 tolerance["theta"] = 1.0e-5;
448 tolerance["rho"] = 1.0e-5;
449 tolerance["vega"] = 1.0e-5;
450
451 Option::Type types[] = { Option::Call, Option::Put };
452 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
453 Real underlyings[] = { 100.0 };
454 Rate qRates[] = { 0.00, 0.10, 0.30 };
455 Rate rRates[] = { 0.01, 0.05, 0.15 };
456 Integer lengths[] = { 1, 2 };
457 Volatility vols[] = { 0.05, 0.20, 0.40 };
458
459 DayCounter dc = Actual360();
460 Date today = Date::todaysDate();
461 Settings::instance().evaluationDate() = today;
462
463 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
464 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
465 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
466 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
467 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
468 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
469 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
470
471 for (auto& type : types) {
472 for (Real strike : strikes) {
473 for (int length : lengths) {
474 Date exDate = today + length * Years;
475 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
476
477 std::vector<Date> dividendDates;
478 std::vector<Real> dividends;
479 for (Date d = today + 3 * Months; d < exercise->lastDate(); d += 6 * Months) {
480 dividendDates.push_back(x: d);
481 dividends.push_back(x: 5.0);
482 }
483
484 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
485
486 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
487 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
488
489 QL_DEPRECATED_DISABLE_WARNING
490 ext::shared_ptr<PricingEngine> engine(
491 new AnalyticDividendEuropeanEngine(stochProcess));
492
493 DividendVanillaOption option(payoff, exercise, dividendDates, dividends);
494 QL_DEPRECATED_ENABLE_WARNING
495 option.setPricingEngine(engine);
496
497 for (Real u : underlyings) {
498 for (Real m : qRates) {
499 for (Real n : rRates) {
500 for (Real v : vols) {
501 Rate q = m, r = n;
502 spot->setValue(u);
503 qRate->setValue(q);
504 rRate->setValue(r);
505 vol->setValue(v);
506
507 Real value = option.NPV();
508 calculated["delta"] = option.delta();
509 calculated["gamma"] = option.gamma();
510 calculated["theta"] = option.theta();
511 calculated["rho"] = option.rho();
512 calculated["vega"] = option.vega();
513
514 if (value > spot->value() * 1.0e-5) {
515 // perturb spot and get delta and gamma
516 Real du = u * 1.0e-4;
517 spot->setValue(u + du);
518 Real value_p = option.NPV(), delta_p = option.delta();
519 spot->setValue(u - du);
520 Real value_m = option.NPV(), delta_m = option.delta();
521 spot->setValue(u);
522 expected["delta"] = (value_p - value_m) / (2 * du);
523 expected["gamma"] = (delta_p - delta_m) / (2 * du);
524
525 // perturb risk-free rate and get rho
526 Spread dr = r * 1.0e-4;
527 rRate->setValue(r + dr);
528 value_p = option.NPV();
529 rRate->setValue(r - dr);
530 value_m = option.NPV();
531 rRate->setValue(r);
532 expected["rho"] = (value_p - value_m) / (2 * dr);
533
534 // perturb volatility and get vega
535 Spread dv = v * 1.0e-4;
536 vol->setValue(v + dv);
537 value_p = option.NPV();
538 vol->setValue(v - dv);
539 value_m = option.NPV();
540 vol->setValue(v);
541 expected["vega"] = (value_p - value_m) / (2 * dv);
542
543 // perturb date and get theta
544 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
545 Settings::instance().evaluationDate() = today - 1;
546 value_m = option.NPV();
547 Settings::instance().evaluationDate() = today + 1;
548 value_p = option.NPV();
549 Settings::instance().evaluationDate() = today;
550 expected["theta"] = (value_p - value_m) / dT;
551
552 // compare
553 std::map<std::string, Real>::iterator it;
554 for (it = calculated.begin(); it != calculated.end(); ++it) {
555 std::string greek = it->first;
556 Real expct = expected[greek], calcl = calculated[greek],
557 tol = tolerance[greek];
558 Real error = relativeError(x1: expct, x2: calcl, reference: u);
559 if (error > tol) {
560 REPORT_FAILURE(greek, payoff, exercise, u, q, r, today,
561 v, expct, calcl, error, tol);
562 }
563 }
564 }
565 }
566 }
567 }
568 }
569 }
570 }
571 }
572}
573
574void DividendOptionTest::testEuropeanGreeks() {
575
576 BOOST_TEST_MESSAGE("Testing dividend European option greeks...");
577
578 std::map<std::string,Real> calculated, expected, tolerance;
579 tolerance["delta"] = 1.0e-5;
580 tolerance["gamma"] = 1.0e-5;
581 tolerance["theta"] = 1.0e-5;
582 tolerance["rho"] = 1.0e-5;
583 tolerance["vega"] = 1.0e-5;
584
585 Option::Type types[] = { Option::Call, Option::Put };
586 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
587 Real underlyings[] = { 100.0 };
588 Rate qRates[] = { 0.00, 0.10, 0.30 };
589 Rate rRates[] = { 0.01, 0.05, 0.15 };
590 Integer lengths[] = { 1, 2 };
591 Volatility vols[] = { 0.05, 0.20, 0.40 };
592
593 DayCounter dc = Actual360();
594 Date today = Date::todaysDate();
595 Settings::instance().evaluationDate() = today;
596
597 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
598 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
599 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
600 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
601 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
602 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
603 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
604
605 for (auto& type : types) {
606 for (Real strike : strikes) {
607 for (int length : lengths) {
608 Date exDate = today + length * Years;
609 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
610
611 std::vector<Date> dividendDates;
612 std::vector<Real> dividends;
613 for (Date d = today + 3 * Months; d < exercise->lastDate(); d += 6 * Months) {
614 dividendDates.push_back(x: d);
615 dividends.push_back(x: 5.0);
616 }
617
618 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
619
620 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
621 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
622
623 auto engine = ext::make_shared<AnalyticDividendEuropeanEngine>(
624 args&: stochProcess, args: DividendVector(dividendDates, dividends));
625
626 VanillaOption option(payoff, exercise);
627 option.setPricingEngine(engine);
628
629 for (Real u : underlyings) {
630 for (Real m : qRates) {
631 for (Real n : rRates) {
632 for (Real v : vols) {
633 Rate q = m, r = n;
634 spot->setValue(u);
635 qRate->setValue(q);
636 rRate->setValue(r);
637 vol->setValue(v);
638
639 Real value = option.NPV();
640 calculated["delta"] = option.delta();
641 calculated["gamma"] = option.gamma();
642 calculated["theta"] = option.theta();
643 calculated["rho"] = option.rho();
644 calculated["vega"] = option.vega();
645
646 if (value > spot->value() * 1.0e-5) {
647 // perturb spot and get delta and gamma
648 Real du = u * 1.0e-4;
649 spot->setValue(u + du);
650 Real value_p = option.NPV(), delta_p = option.delta();
651 spot->setValue(u - du);
652 Real value_m = option.NPV(), delta_m = option.delta();
653 spot->setValue(u);
654 expected["delta"] = (value_p - value_m) / (2 * du);
655 expected["gamma"] = (delta_p - delta_m) / (2 * du);
656
657 // perturb risk-free rate and get rho
658 Spread dr = r * 1.0e-4;
659 rRate->setValue(r + dr);
660 value_p = option.NPV();
661 rRate->setValue(r - dr);
662 value_m = option.NPV();
663 rRate->setValue(r);
664 expected["rho"] = (value_p - value_m) / (2 * dr);
665
666 // perturb volatility and get vega
667 Spread dv = v * 1.0e-4;
668 vol->setValue(v + dv);
669 value_p = option.NPV();
670 vol->setValue(v - dv);
671 value_m = option.NPV();
672 vol->setValue(v);
673 expected["vega"] = (value_p - value_m) / (2 * dv);
674
675 // perturb date and get theta
676 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
677 Settings::instance().evaluationDate() = today - 1;
678 value_m = option.NPV();
679 Settings::instance().evaluationDate() = today + 1;
680 value_p = option.NPV();
681 Settings::instance().evaluationDate() = today;
682 expected["theta"] = (value_p - value_m) / dT;
683
684 // compare
685 std::map<std::string, Real>::iterator it;
686 for (it = calculated.begin(); it != calculated.end(); ++it) {
687 std::string greek = it->first;
688 Real expct = expected[greek], calcl = calculated[greek],
689 tol = tolerance[greek];
690 Real error = relativeError(x1: expct, x2: calcl, reference: u);
691 if (error > tol) {
692 REPORT_FAILURE(greek, payoff, exercise, u, q, r, today,
693 v, expct, calcl, error, tol);
694 }
695 }
696 }
697 }
698 }
699 }
700 }
701 }
702 }
703 }
704}
705
706
707void DividendOptionTest::testFdEuropeanValues() {
708
709 BOOST_TEST_MESSAGE(
710 "Testing finite-difference dividend European option values...");
711
712 Real tolerance = 1.0e-2;
713 Size gridPoints = 400;
714 Size timeSteps = 40;
715
716 Option::Type types[] = { Option::Call, Option::Put };
717 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
718 Real underlyings[] = { 100.0 };
719 Rate qRates[] = { 0.00, 0.10, 0.30 };
720 Rate rRates[] = { 0.01, 0.05, 0.15 };
721 Integer lengths[] = { 1, 2 };
722 Volatility vols[] = { 0.05, 0.20, 0.40 };
723
724 DayCounter dc = Actual360();
725 Date today = Date::todaysDate();
726 Settings::instance().evaluationDate() = today;
727
728 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
729 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
730 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
731 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
732 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
733 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
734 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
735
736 for (auto& type : types) {
737 for (Real strike : strikes) {
738 for (int length : lengths) {
739 Date exDate = today + length * Years;
740 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
741
742 std::vector<Date> dividendDates;
743 std::vector<Real> dividends;
744 for (Date d = today + 3 * Months; d < exercise->lastDate(); d += 6 * Months) {
745 dividendDates.push_back(x: d);
746 dividends.push_back(x: 5.0);
747 }
748
749 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
750
751 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
752 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
753
754 auto ref_engine = ext::make_shared<AnalyticDividendEuropeanEngine>(
755 args&: stochProcess, args: DividendVector(dividendDates, dividends));
756
757 VanillaOption ref_option(payoff, exercise);
758 ref_option.setPricingEngine(ref_engine);
759
760 ext::shared_ptr<PricingEngine> engine1 =
761 MakeFdBlackScholesVanillaEngine(stochProcess)
762 .withTGrid(tGrid: timeSteps)
763 .withXGrid(xGrid: gridPoints)
764 .withCashDividendModel(cashDividendModel: FdBlackScholesVanillaEngine::Escrowed);
765
766 QL_DEPRECATED_DISABLE_WARNING
767 DividendVanillaOption option1(payoff, exercise, dividendDates, dividends);
768 QL_DEPRECATED_ENABLE_WARNING
769 option1.setPricingEngine(engine1);
770
771 ext::shared_ptr<PricingEngine> engine2 =
772 MakeFdBlackScholesVanillaEngine(stochProcess)
773 .withTGrid(tGrid: timeSteps)
774 .withXGrid(xGrid: gridPoints)
775 .withCashDividends(dividendDates, dividendAmounts: dividends)
776 .withCashDividendModel(cashDividendModel: FdBlackScholesVanillaEngine::Escrowed);
777
778 VanillaOption option2(payoff, exercise);
779 option2.setPricingEngine(engine2);
780
781 for (Real u : underlyings) {
782 for (Real m : qRates) {
783 for (Real n : rRates) {
784 for (Real v : vols) {
785 Rate q = m, r = n;
786 spot->setValue(u);
787 qRate->setValue(q);
788 rRate->setValue(r);
789 vol->setValue(v);
790 Real calculated = option1.NPV();
791 if (calculated > spot->value() * 1.0e-5) {
792 Real expected = ref_option.NPV();
793 Real error = std::fabs(x: calculated - expected);
794 if (error > tolerance) {
795 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
796 expected, calculated, error, tolerance);
797 }
798 }
799 calculated = option2.NPV();
800 if (calculated > spot->value() * 1.0e-5) {
801 Real expected = ref_option.NPV();
802 Real error = std::fabs(x: calculated - expected);
803 if (error > tolerance) {
804 REPORT_FAILURE("value", payoff, exercise, u, q, r, today, v,
805 expected, calculated, error, tolerance);
806 }
807 }
808 }
809 }
810 }
811 }
812 }
813 }
814 }
815}
816
817
818namespace {
819
820 void testFdGreeks(const Date& today,
821 const ext::shared_ptr<Exercise>& exercise,
822 FdBlackScholesVanillaEngine::CashDividendModel model) {
823
824 std::map<std::string,Real> calculated, expected, tolerance;
825 tolerance["delta"] = 5.0e-3;
826 tolerance["gamma"] = 7.0e-3;
827
828 Option::Type types[] = { Option::Call, Option::Put };
829 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
830 Real underlyings[] = { 100.0 };
831 Rate qRates[] = { 0.00, 0.10, 0.20 };
832 Rate rRates[] = { 0.01, 0.05, 0.15 };
833 Volatility vols[] = { 0.05, 0.20, 0.50 };
834
835 DayCounter dc = Actual365Fixed();
836
837 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
838 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
839 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
840 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
841 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
842 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
843 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
844
845 for (auto& type : types) {
846 for (Real strike : strikes) {
847
848 std::vector<Date> dividendDates;
849 std::vector<Real> dividends;
850 for (Date d = today + 3*Months;
851 d < exercise->lastDate();
852 d += 6*Months) {
853 dividendDates.push_back(x: d);
854 dividends.push_back(x: 5.0);
855 }
856
857 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
858
859 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
860 new BlackScholesMertonProcess(Handle<Quote>(spot),
861 qTS, rTS, volTS));
862
863 ext::shared_ptr<PricingEngine> engine =
864 MakeFdBlackScholesVanillaEngine(stochProcess)
865 .withCashDividends(dividendDates, dividendAmounts: dividends)
866 .withCashDividendModel(cashDividendModel: model);
867
868 VanillaOption option(payoff, exercise);
869 option.setPricingEngine(engine);
870
871 ext::shared_ptr<PricingEngine> engine2 =
872 MakeFdBlackScholesVanillaEngine(stochProcess)
873 .withCashDividendModel(cashDividendModel: model);
874
875 QL_DEPRECATED_DISABLE_WARNING
876 DividendVanillaOption option2(payoff, exercise,
877 dividendDates, dividends);
878 QL_DEPRECATED_ENABLE_WARNING
879 option2.setPricingEngine(engine2);
880
881 for (Real u : underlyings) {
882 for (Real m : qRates) {
883 for (Real n : rRates) {
884 for (Real v : vols) {
885 Rate q = m, r = n;
886 spot->setValue(u);
887 qRate->setValue(q);
888 rRate->setValue(r);
889 vol->setValue(v);
890
891 Real value = option.NPV();
892 calculated["delta"] = option.delta();
893 calculated["gamma"] = option.gamma();
894
895 Real value2 = option2.NPV();
896 Real delta2 = option2.delta();
897 Real gamma2 = option2.gamma();
898
899 if (std::fabs(x: value - value2) > 1e-8) {
900 BOOST_ERROR("discrepancy between old- and new-style engine"
901 << "\n old value: " << value2
902 << "\n new value: " << value);
903 }
904 if (std::fabs(x: calculated["delta"] - delta2) > 1e-8) {
905 BOOST_ERROR("discrepancy between old- and new-style engine"
906 << "\n old delta: " << delta2
907 << "\n new delta: " << calculated["delta"]);
908 }
909 if (std::fabs(x: calculated["gamma"] - gamma2) > 1e-8) {
910 BOOST_ERROR("discrepancy between old- and new-style engine"
911 << "\n old gamma: " << gamma2
912 << "\n new gamma: " << calculated["gamma"]);
913 }
914
915 if (value > spot->value() * 1.0e-5) {
916 // perturb spot and get delta and gamma
917 Real du = u * 1.0e-4;
918 spot->setValue(u + du);
919 Real value_p = option.NPV(), delta_p = option.delta();
920 spot->setValue(u - du);
921 Real value_m = option.NPV(), delta_m = option.delta();
922 spot->setValue(u);
923 expected["delta"] = (value_p - value_m) / (2 * du);
924 expected["gamma"] = (delta_p - delta_m) / (2 * du);
925
926 // compare
927 std::map<std::string, Real>::iterator it;
928 for (it = calculated.begin(); it != calculated.end(); ++it) {
929 std::string greek = it->first;
930 Real expct = expected[greek], calcl = calculated[greek],
931 tol = tolerance[greek];
932 Real error = relativeError(x1: expct, x2: calcl, reference: u);
933 if (error > tol) {
934 REPORT_FAILURE(greek, payoff, exercise, u, q, r, today,
935 v, expct, calcl, error, tol);
936 }
937 }
938 }
939 }
940 }
941 }
942 }
943 }
944 }
945 }
946
947}
948
949
950void DividendOptionTest::testFdEuropeanGreeks() {
951 BOOST_TEST_MESSAGE("Testing finite-differences dividend European option greeks...");
952
953 Date today = Date::todaysDate();
954 Settings::instance().evaluationDate() = today;
955 Integer lengths[] = { 1, 2 };
956
957 for (int length : lengths) {
958 Date exDate = today + length * Years;
959 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
960 testFdGreeks(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
961 testFdGreeks(today,exercise,model: FdBlackScholesVanillaEngine::Escrowed);
962 }
963}
964
965void DividendOptionTest::testFdAmericanGreeks() {
966 BOOST_TEST_MESSAGE(
967 "Testing finite-differences dividend American option greeks...");
968
969 Date today = Date::todaysDate();
970 Settings::instance().evaluationDate() = today;
971 Integer lengths[] = { 1, 2 };
972
973 for (int length : lengths) {
974 Date exDate = today + length * Years;
975 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today,exDate));
976 testFdGreeks(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
977 }
978}
979
980
981namespace {
982
983 void testFdDegenerate(const Date& today,
984 const ext::shared_ptr<Exercise>& exercise,
985 FdBlackScholesVanillaEngine::CashDividendModel model) {
986
987 DayCounter dc = Actual360();
988 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(54.625));
989 Handle<YieldTermStructure> rTS(flatRate(forward: 0.052706, dc));
990 Handle<YieldTermStructure> qTS(flatRate(forward: 0.0, dc));
991 Handle<BlackVolTermStructure> volTS(flatVol(volatility: 0.282922, dc));
992
993 ext::shared_ptr<BlackScholesMertonProcess> process(
994 new BlackScholesMertonProcess(Handle<Quote>(spot),
995 qTS, rTS, volTS));
996
997 Size timeSteps = 100;
998 Size gridPoints = 300;
999
1000 ext::shared_ptr<StrikedTypePayoff> payoff(
1001 new PlainVanillaPayoff(Option::Call, 55.0));
1002
1003 Real tolerance = 1.0e-6;
1004
1005 VanillaOption option(payoff, exercise);
1006 ext::shared_ptr<PricingEngine> engine =
1007 MakeFdBlackScholesVanillaEngine(process)
1008 .withTGrid(tGrid: timeSteps)
1009 .withXGrid(xGrid: gridPoints)
1010 .withCashDividendModel(cashDividendModel: model);
1011 option.setPricingEngine(engine);
1012
1013 Real refValue = option.NPV();
1014
1015 std::vector<Rate> dividends;
1016 std::vector<Date> dividendDates;
1017
1018 engine =
1019 MakeFdBlackScholesVanillaEngine(process)
1020 .withTGrid(tGrid: timeSteps)
1021 .withXGrid(xGrid: gridPoints)
1022 .withCashDividends(dividendDates, dividendAmounts: dividends)
1023 .withCashDividendModel(cashDividendModel: model);
1024 option.setPricingEngine(engine);
1025 Real value = option.NPV();
1026
1027 if (std::fabs(x: refValue-value) > tolerance)
1028 BOOST_FAIL("NPV changed by empty dividend set:\n"
1029 << " previous value: " << value << "\n"
1030 << " current value: " << refValue << "\n"
1031 << " change: " << value-refValue);
1032
1033 for (Size i=1; i<=6; i++) {
1034
1035 dividends.push_back(x: 0.0);
1036 dividendDates.push_back(x: today+i);
1037
1038 engine =
1039 MakeFdBlackScholesVanillaEngine(process)
1040 .withTGrid(tGrid: timeSteps)
1041 .withXGrid(xGrid: gridPoints)
1042 .withCashDividends(dividendDates, dividendAmounts: dividends)
1043 .withCashDividendModel(cashDividendModel: model);
1044 option.setPricingEngine(engine);
1045 value = option.NPV();
1046
1047 if (std::fabs(x: refValue-value) > tolerance)
1048 BOOST_FAIL("NPV changed by null dividend :\n"
1049 << " previous value: " << value << "\n"
1050 << " current value: " << refValue << "\n"
1051 << " change: " << value-refValue);
1052 }
1053 }
1054
1055}
1056
1057
1058void DividendOptionTest::testFdEuropeanDegenerate() {
1059
1060 BOOST_TEST_MESSAGE(
1061 "Testing degenerate finite-differences dividend European option...");
1062
1063 Date today = Date(27,February,2005);
1064 Settings::instance().evaluationDate() = today;
1065 Date exDate(13,April,2005);
1066
1067 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
1068
1069 testFdDegenerate(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
1070 testFdDegenerate(today,exercise,model: FdBlackScholesVanillaEngine::Escrowed);
1071}
1072
1073void DividendOptionTest::testFdAmericanDegenerate() {
1074
1075 BOOST_TEST_MESSAGE(
1076 "Testing degenerate finite-differences dividend American option...");
1077
1078 Date today = Date(27,February,2005);
1079 Settings::instance().evaluationDate() = today;
1080 Date exDate(13,April,2005);
1081
1082 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today,exDate));
1083
1084 testFdDegenerate(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
1085 testFdDegenerate(today,exercise,model: FdBlackScholesVanillaEngine::Escrowed);
1086}
1087
1088
1089namespace {
1090
1091 void testFdDividendAtTZero(const Date& today,
1092 const ext::shared_ptr<Exercise>& exercise,
1093 FdBlackScholesVanillaEngine::CashDividendModel model) {
1094
1095 DayCounter dc = Actual360();
1096 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(54.625));
1097 Handle<YieldTermStructure> rTS(flatRate(forward: 0.0, dc));
1098 Handle<BlackVolTermStructure> volTS(flatVol(volatility: 0.282922, dc));
1099
1100 ext::shared_ptr<BlackScholesMertonProcess> process(
1101 new BlackScholesMertonProcess(Handle<Quote>(spot),
1102 rTS, rTS, volTS));
1103
1104 Size timeSteps = 50;
1105 Size gridPoints = 400;
1106
1107 ext::shared_ptr<StrikedTypePayoff> payoff(
1108 new PlainVanillaPayoff(Option::Call, 55.0));
1109
1110 // today's dividend must by taken into account
1111 std::vector<Rate> dividends(1, 1.0);
1112 std::vector<Date> dividendDates(1, today);
1113
1114 QL_DEPRECATED_DISABLE_WARNING
1115 DividendVanillaOption option1(payoff, exercise,
1116 dividendDates, dividends);
1117 QL_DEPRECATED_ENABLE_WARNING
1118 ext::shared_ptr<PricingEngine> engine1 =
1119 MakeFdBlackScholesVanillaEngine(process)
1120 .withTGrid(tGrid: timeSteps)
1121 .withXGrid(xGrid: gridPoints)
1122 .withCashDividendModel(cashDividendModel: model);
1123 option1.setPricingEngine(engine1);
1124 Real calculated1 = option1.NPV();
1125
1126 switch(model) {
1127 case FdBlackScholesVanillaEngine::Spot:
1128 BOOST_CHECK_THROW(option1.theta(), QuantLib::Error);
1129 break;
1130 case FdBlackScholesVanillaEngine::Escrowed:
1131 BOOST_CHECK_NO_THROW(option1.theta());
1132 break;
1133 default:
1134 QL_FAIL("unknown dividend model type");
1135 }
1136
1137 VanillaOption option2(payoff, exercise);
1138 ext::shared_ptr<PricingEngine> engine2 =
1139 MakeFdBlackScholesVanillaEngine(process)
1140 .withTGrid(tGrid: timeSteps)
1141 .withXGrid(xGrid: gridPoints)
1142 .withCashDividends(dividendDates, dividendAmounts: dividends)
1143 .withCashDividendModel(cashDividendModel: model);
1144 option2.setPricingEngine(engine2);
1145 Real calculated2 = option2.NPV();
1146
1147 switch(model) {
1148 case FdBlackScholesVanillaEngine::Spot:
1149 BOOST_CHECK_THROW(option2.theta(), QuantLib::Error);
1150 break;
1151 case FdBlackScholesVanillaEngine::Escrowed:
1152 BOOST_CHECK_NO_THROW(option2.theta());
1153 break;
1154 default:
1155 QL_FAIL("unknown dividend model type");
1156 }
1157
1158 ext::shared_ptr<Exercise> europeanExercise =
1159 ext::make_shared<EuropeanExercise>(args: exercise->lastDate());
1160 VanillaOption europeanOption(payoff, europeanExercise);
1161
1162 europeanOption.setPricingEngine(
1163 ext::make_shared<AnalyticDividendEuropeanEngine>(args&: process, args: DividendVector(dividendDates, dividends)));
1164
1165 Real expected = europeanOption.NPV();
1166
1167 const Real tol = 1e-4;
1168
1169 if (std::fabs(x: calculated1-expected) > tol) {
1170 BOOST_ERROR("Can not reproduce reference values "
1171 "from analytic dividend engine :\n"
1172 << " calculated: " << calculated1 << "\n"
1173 << " expected : " << expected << "\n"
1174 << " diff: " << tol);
1175 }
1176 if (std::fabs(x: calculated2-expected) > tol) {
1177 BOOST_ERROR("Can not reproduce reference values "
1178 "from analytic dividend engine :\n"
1179 << " calculated: " << calculated2 << "\n"
1180 << " expected : " << expected << "\n"
1181 << " diff: " << tol);
1182 }
1183 }
1184}
1185
1186
1187void DividendOptionTest::testFdEuropeanWithDividendToday() {
1188
1189 BOOST_TEST_MESSAGE(
1190 "Testing finite-differences dividend European option with dividend on today's date...");
1191
1192 Date today = Date(27,February,2005);
1193 Settings::instance().evaluationDate() = today;
1194 Date exDate(13,April,2005);
1195
1196 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
1197
1198 testFdDividendAtTZero(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
1199 testFdDividendAtTZero(today,exercise,model: FdBlackScholesVanillaEngine::Escrowed);
1200}
1201
1202void DividendOptionTest::testFdAmericanWithDividendToday() {
1203
1204 BOOST_TEST_MESSAGE(
1205 "Testing finite-differences dividend American option with dividend on today's date...");
1206
1207 Date today = Date(27,February,2005);
1208 Settings::instance().evaluationDate() = today;
1209 Date exDate(13,April,2005);
1210
1211 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today,exDate));
1212
1213 testFdDividendAtTZero(today,exercise,model: FdBlackScholesVanillaEngine::Spot);
1214}
1215
1216
1217void DividendOptionTest::testEscrowedDividendModel() {
1218 BOOST_TEST_MESSAGE("Testing finite-difference European engine "
1219 "with the escrowed dividend model...");
1220
1221 const DayCounter dc = Actual365Fixed();
1222 const Date today = Date(12, October, 2019);
1223
1224 Settings::instance().evaluationDate() = today;
1225
1226 const Handle<Quote> spot(ext::make_shared<SimpleQuote>(args: 100.0));
1227 const Handle<YieldTermStructure> qTS(flatRate(today, forward: 0.063, dc));
1228 const Handle<YieldTermStructure> rTS(flatRate(today, forward: 0.094, dc));
1229 const Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: 0.3, dc));
1230
1231 const Date maturity = today + Period(1, Years);
1232
1233 const ext::shared_ptr<BlackScholesMertonProcess> process =
1234 ext::make_shared<BlackScholesMertonProcess>(
1235 args: spot, args: qTS, args: rTS, args: volTS);
1236
1237 const ext::shared_ptr<PlainVanillaPayoff> payoff(
1238 ext::make_shared<PlainVanillaPayoff>(args: Option::Put, args: spot->value()));
1239
1240 const ext::shared_ptr<Exercise> exercise(
1241 ext::make_shared<EuropeanExercise>(args: maturity));
1242
1243 std::vector<Date> dividendDates = {today + Period(3, Months), today + Period(9, Months)};
1244 std::vector<Real> dividendAmounts = {8.3, 6.8};
1245
1246 VanillaOption ref_option(payoff, exercise);
1247
1248 ref_option.setPricingEngine(
1249 ext::make_shared<AnalyticDividendEuropeanEngine>(args: process, args: DividendVector(dividendDates, dividends: dividendAmounts)));
1250
1251 const Real analyticNPV = ref_option.NPV();
1252 const Real analyticDelta = ref_option.delta();
1253
1254 QL_DEPRECATED_DISABLE_WARNING
1255 DividendVanillaOption option(payoff, exercise, dividendDates, dividendAmounts);
1256 QL_DEPRECATED_ENABLE_WARNING
1257 option.setPricingEngine(
1258 MakeFdBlackScholesVanillaEngine(process)
1259 .withTGrid(tGrid: 50)
1260 .withXGrid(xGrid: 200)
1261 .withDampingSteps(dampingSteps: 1)
1262 .withCashDividendModel(cashDividendModel: FdBlackScholesVanillaEngine::Escrowed)
1263 );
1264
1265 Real pdeNPV = option.NPV();
1266 Real pdeDelta = option.delta();
1267
1268 const Real tol = 0.0025;
1269 if (std::fabs(x: pdeNPV - analyticNPV) > tol) {
1270 BOOST_FAIL("Failed to reproduce European option values "
1271 "with the escrowed dividend model and the "
1272 "FdBlackScholesVanillaEngine engine"
1273 << "\n calculated: " << pdeNPV
1274 << "\n expected: " << analyticNPV
1275 << "\n difference: " << std::fabs(pdeNPV - analyticNPV)
1276 << "\n tolerance: " << tol);
1277 }
1278
1279 if (std::fabs(x: pdeDelta - analyticDelta) > tol) {
1280 BOOST_FAIL("Failed to reproduce European option deltas "
1281 "with the escrowed dividend model and the "
1282 "FdBlackScholesVanillaEngine engine"
1283 << "\n calculated: " << pdeNPV
1284 << "\n expected: " << analyticNPV
1285 << "\n difference: " << std::fabs(pdeNPV - analyticNPV)
1286 << "\n tolerance: " << tol);
1287 }
1288
1289 VanillaOption option2(payoff, exercise);
1290 option2.setPricingEngine(
1291 MakeFdBlackScholesVanillaEngine(process)
1292 .withTGrid(tGrid: 50)
1293 .withXGrid(xGrid: 200)
1294 .withDampingSteps(dampingSteps: 1)
1295 .withCashDividends(dividendDates, dividendAmounts)
1296 .withCashDividendModel(cashDividendModel: FdBlackScholesVanillaEngine::Escrowed)
1297 );
1298
1299 pdeNPV = option2.NPV();
1300 pdeDelta = option2.delta();
1301
1302 if (std::fabs(x: pdeNPV - analyticNPV) > tol) {
1303 BOOST_FAIL("Failed to reproduce European option values "
1304 "with the escrowed dividend model and the "
1305 "FdBlackScholesVanillaEngine engine"
1306 << "\n calculated: " << pdeNPV
1307 << "\n expected: " << analyticNPV
1308 << "\n difference: " << std::fabs(pdeNPV - analyticNPV)
1309 << "\n tolerance: " << tol);
1310 }
1311
1312 if (std::fabs(x: pdeDelta - analyticDelta) > tol) {
1313 BOOST_FAIL("Failed to reproduce European option deltas "
1314 "with the escrowed dividend model and the "
1315 "FdBlackScholesVanillaEngine engine"
1316 << "\n calculated: " << pdeNPV
1317 << "\n expected: " << analyticNPV
1318 << "\n difference: " << std::fabs(pdeNPV - analyticNPV)
1319 << "\n tolerance: " << tol);
1320 }
1321}
1322
1323test_suite* DividendOptionTest::suite(SpeedLevel speed) {
1324 auto* suite = BOOST_TEST_SUITE("Dividend European option tests");
1325 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEuropeanValues));
1326 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEuropeanKnownValue));
1327 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEuropeanStartLimit));
1328 // Doesn't quite work. Need to use discounted values
1329 //suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEuropeanEndLimit));
1330 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testOldEuropeanGreeks));
1331 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEuropeanGreeks));
1332 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdEuropeanValues));
1333 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdAmericanGreeks));
1334 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdEuropeanDegenerate));
1335 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdAmericanDegenerate));
1336 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdEuropeanWithDividendToday));
1337 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdAmericanWithDividendToday));
1338 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testEscrowedDividendModel));
1339
1340 if (speed <= Fast) {
1341 suite->add(QUANTLIB_TEST_CASE(&DividendOptionTest::testFdEuropeanGreeks));
1342 }
1343
1344 return suite;
1345}
1346
1347

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