[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, 2004 Ferdinando Ametrano
5 Copyright (C) 2003 Neil Firth
6 Copyright (C) 2003 RiskMap srl
7 Copyright (C) 2007 StatPro Italia srl
8
9 This file is part of QuantLib, a free-software/open-source library
10 for financial quantitative analysts and developers - http://quantlib.org/
11
12 QuantLib is free software: you can redistribute it and/or modify it
13 under the terms of the QuantLib license. You should have received a
14 copy of the license along with this program; if not, please email
15 <quantlib-dev@lists.sf.net>. The license is also available online at
16 <http://quantlib.org/license.shtml>.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the license for more details.
21*/
22
23#include "digitaloption.hpp"
24#include "utilities.hpp"
25#include <ql/time/daycounters/actual360.hpp>
26#include <ql/instruments/vanillaoption.hpp>
27#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
28#include <ql/pricingengines/vanilla/analyticdigitalamericanengine.hpp>
29#include <ql/pricingengines/vanilla/mcdigitalengine.hpp>
30#include <ql/processes/blackscholesprocess.hpp>
31#include <ql/termstructures/yield/flatforward.hpp>
32#include <ql/termstructures/volatility/equityfx/blackconstantvol.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, knockin) \
42 BOOST_FAIL(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 << "\n" \
56 << " knock_in: " << knockin);
57
58namespace {
59
60 struct DigitalOptionData {
61 Option::Type type;
62 Real strike;
63 Real s; // spot
64 Rate q; // dividend
65 Rate r; // risk-free rate
66 Time t; // time to maturity
67 Volatility v; // volatility
68 Real result; // expected result
69 Real tol; // tolerance
70 bool knockin; // true if knock-in
71 };
72
73}
74
75
76void DigitalOptionTest::testCashOrNothingEuropeanValues() {
77
78 BOOST_TEST_MESSAGE("Testing European cash-or-nothing digital option...");
79
80 DigitalOptionData values[] = {
81 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 88
82 // type, strike, spot, q, r, t, vol, value, tol
83 { .type: Option::Put, .strike: 80.00, .s: 100.0, .q: 0.06, .r: 0.06, .t: 0.75, .v: 0.35, .result: 2.6710, .tol: 1e-4, .knockin: true }
84 };
85
86 DayCounter dc = Actual360();
87 Date today = Date::todaysDate();
88
89 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
90 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
91 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
92 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
93 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
94 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
95 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
96
97 for (auto& value : values) {
98
99 ext::shared_ptr<StrikedTypePayoff> payoff(
100 new CashOrNothingPayoff(value.type, value.strike, 10.0));
101
102 Date exDate = today + timeToDays(t: value.t);
103 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
104
105 spot->setValue(value.s);
106 qRate->setValue(value.q);
107 rRate->setValue(value.r);
108 vol->setValue(value.v);
109
110 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
111 BlackScholesMertonProcess(Handle<Quote>(spot),
112 Handle<YieldTermStructure>(qTS),
113 Handle<YieldTermStructure>(rTS),
114 Handle<BlackVolTermStructure>(volTS)));
115 ext::shared_ptr<PricingEngine> engine(
116 new AnalyticEuropeanEngine(stochProcess));
117
118 VanillaOption opt(payoff, exercise);
119 opt.setPricingEngine(engine);
120
121 Real calculated = opt.NPV();
122 Real error = std::fabs(x: calculated - value.result);
123 if (error > value.tol) {
124 REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today, value.v,
125 value.result, calculated, error, value.tol, value.knockin);
126 }
127 }
128}
129
130void DigitalOptionTest::testAssetOrNothingEuropeanValues() {
131
132 BOOST_TEST_MESSAGE("Testing European asset-or-nothing digital option...");
133
134 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 90
135 DigitalOptionData values[] = {
136 // type, strike, spot, q, r, t, vol, value, tol
137 { .type: Option::Put, .strike: 65.00, .s: 70.0, .q: 0.05, .r: 0.07, .t: 0.50, .v: 0.27, .result: 20.2069, .tol: 1e-4, .knockin: true }
138 };
139
140 DayCounter dc = Actual360();
141 Date today = Date::todaysDate();
142
143 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
144 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
145 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
146 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
147 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
148 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
149 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
150
151 for (auto& value : values) {
152
153 ext::shared_ptr<StrikedTypePayoff> payoff(
154 new AssetOrNothingPayoff(value.type, value.strike));
155
156 Date exDate = today + timeToDays(t: value.t);
157 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
158
159 spot->setValue(value.s);
160 qRate->setValue(value.q);
161 rRate->setValue(value.r);
162 vol->setValue(value.v);
163
164 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
165 BlackScholesMertonProcess(Handle<Quote>(spot),
166 Handle<YieldTermStructure>(qTS),
167 Handle<YieldTermStructure>(rTS),
168 Handle<BlackVolTermStructure>(volTS)));
169 ext::shared_ptr<PricingEngine> engine(
170 new AnalyticEuropeanEngine(stochProcess));
171
172 VanillaOption opt(payoff, exercise);
173 opt.setPricingEngine(engine);
174
175 Real calculated = opt.NPV();
176 Real error = std::fabs(x: calculated - value.result);
177 if (error > value.tol) {
178 REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today, value.v,
179 value.result, calculated, error, value.tol, value.knockin);
180 }
181 }
182}
183
184void DigitalOptionTest::testGapEuropeanValues() {
185
186 BOOST_TEST_MESSAGE("Testing European gap digital option...");
187
188 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 88
189 DigitalOptionData values[] = {
190 // type, strike, spot, q, r, t, vol, value, tol
191 { .type: Option::Call, .strike: 50.00, .s: 50.0, .q: 0.00, .r: 0.09, .t: 0.50, .v: 0.20, .result: -0.0053, .tol: 1e-4, .knockin: true }
192 };
193
194 DayCounter dc = Actual360();
195 Date today = Date::todaysDate();
196
197 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
198 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
199 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
200 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
201 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
202 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
203 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
204
205 for (auto& value : values) {
206
207 ext::shared_ptr<StrikedTypePayoff> payoff(new GapPayoff(value.type, value.strike, 57.00));
208
209 Date exDate = today + timeToDays(t: value.t);
210 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
211
212 spot->setValue(value.s);
213 qRate->setValue(value.q);
214 rRate->setValue(value.r);
215 vol->setValue(value.v);
216
217 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
218 BlackScholesMertonProcess(Handle<Quote>(spot),
219 Handle<YieldTermStructure>(qTS),
220 Handle<YieldTermStructure>(rTS),
221 Handle<BlackVolTermStructure>(volTS)));
222 ext::shared_ptr<PricingEngine> engine(
223 new AnalyticEuropeanEngine(stochProcess));
224
225 VanillaOption opt(payoff, exercise);
226 opt.setPricingEngine(engine);
227
228 Real calculated = opt.NPV();
229 Real error = std::fabs(x: calculated - value.result);
230 if (error > value.tol) {
231 REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today, value.v,
232 value.result, calculated, error, value.tol, value.knockin);
233 }
234 }
235}
236
237void DigitalOptionTest::testCashAtHitOrNothingAmericanValues() {
238
239 BOOST_TEST_MESSAGE("Testing American cash-(at-hit)-or-nothing "
240 "digital option...");
241
242 DigitalOptionData values[] = {
243 // type, strike, spot, q, r, t, vol, value, tol
244 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 95, case 1,2
245 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 9.7264, .tol: 1e-4, .knockin: true},
246 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 11.6553, .tol: 1e-4, .knockin: true},
247
248 // the following cases are not taken from a reference paper or book
249 // in the money options (guaranteed immediate payoff)
250 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000, .tol: 1e-16, .knockin: true},
251 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000, .tol: 1e-16, .knockin: true},
252 // non null dividend (cross-tested with MC simulation)
253 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 12.2715, .tol: 1e-4, .knockin: true},
254 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 8.9109, .tol: 1e-4, .knockin: true},
255 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000, .tol: 1e-16, .knockin: true},
256 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000, .tol: 1e-16, .knockin: true}
257 };
258
259 DayCounter dc = Actual360();
260 Date today = Date::todaysDate();
261
262 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
263 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
264 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
265 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
266 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
267 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
268 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
269
270 for (auto& value : values) {
271
272 ext::shared_ptr<StrikedTypePayoff> payoff(
273 new CashOrNothingPayoff(value.type, value.strike, 15.00));
274
275 Date exDate = today + timeToDays(t: value.t);
276 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
277 exDate));
278
279 spot->setValue(value.s);
280 qRate->setValue(value.q);
281 rRate->setValue(value.r);
282 vol->setValue(value.v);
283
284 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
285 BlackScholesMertonProcess(Handle<Quote>(spot),
286 Handle<YieldTermStructure>(qTS),
287 Handle<YieldTermStructure>(rTS),
288 Handle<BlackVolTermStructure>(volTS)));
289 ext::shared_ptr<PricingEngine> engine(
290 new AnalyticDigitalAmericanEngine(stochProcess));
291
292 VanillaOption opt(payoff, amExercise);
293 opt.setPricingEngine(engine);
294
295 Real calculated = opt.NPV();
296 Real error = std::fabs(x: calculated - value.result);
297 if (error > value.tol) {
298 REPORT_FAILURE("value", payoff, amExercise, value.s, value.q, value.r, today, value.v,
299 value.result, calculated, error, value.tol, value.knockin);
300 }
301 }
302}
303
304void DigitalOptionTest::testAssetAtHitOrNothingAmericanValues() {
305
306 BOOST_TEST_MESSAGE("Testing American asset-(at-hit)-or-nothing "
307 "digital option...");
308
309 DigitalOptionData values[] = {
310 // type, strike, spot, q, r, t, vol, value, tol
311 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 95, case 3,4
312 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 64.8426, .tol: 1e-04, .knockin: true }, // Haug value is wrong here, Haug VBA code is right
313 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 77.7017, .tol: 1e-04, .knockin: true }, // Haug value is wrong here, Haug VBA code is right
314 // data from Haug VBA code results
315 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 65.7811, .tol: 1e-04, .knockin: true },
316 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 76.8858, .tol: 1e-04, .knockin: true },
317 // in the money options (guaranteed immediate payoff = spot)
318 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20,.result: 105.0000, .tol: 1e-16, .knockin: true },
319 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 95.0000, .tol: 1e-16, .knockin: true },
320 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20,.result: 105.0000, .tol: 1e-16, .knockin: true },
321 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 95.0000, .tol: 1e-16, .knockin: true }
322 };
323
324 DayCounter dc = Actual360();
325 Date today = Date::todaysDate();
326
327 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
328 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
329 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
330 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
331 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
332 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
333 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
334
335 for (auto& value : values) {
336
337 ext::shared_ptr<StrikedTypePayoff> payoff(
338 new AssetOrNothingPayoff(value.type, value.strike));
339
340 Date exDate = today + timeToDays(t: value.t);
341 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
342 exDate));
343
344 spot->setValue(value.s);
345 qRate->setValue(value.q);
346 rRate->setValue(value.r);
347 vol->setValue(value.v);
348
349 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
350 BlackScholesMertonProcess(Handle<Quote>(spot),
351 Handle<YieldTermStructure>(qTS),
352 Handle<YieldTermStructure>(rTS),
353 Handle<BlackVolTermStructure>(volTS)));
354 ext::shared_ptr<PricingEngine> engine(
355 new AnalyticDigitalAmericanEngine(stochProcess));
356
357 VanillaOption opt(payoff, amExercise);
358 opt.setPricingEngine(engine);
359
360 Real calculated = opt.NPV();
361 Real error = std::fabs(x: calculated - value.result);
362 if (error > value.tol) {
363 REPORT_FAILURE("value", payoff, amExercise, value.s, value.q, value.r, today, value.v,
364 value.result, calculated, error, value.tol, value.knockin);
365 }
366 }
367}
368
369void DigitalOptionTest::testCashAtExpiryOrNothingAmericanValues() {
370
371 BOOST_TEST_MESSAGE("Testing American cash-(at-expiry)-or-nothing "
372 "digital option...");
373
374 DigitalOptionData values[] = {
375 // type, strike, spot, q, r, t, vol, value, tol
376 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 95, case 5,6,9,10
377 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 9.3604, .tol: 1e-4, .knockin: true },
378 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 11.2223, .tol: 1e-4, .knockin: true },
379 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 4.9081, .tol: 1e-4, .knockin: false },
380 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 3.0461, .tol: 1e-4, .knockin: false },
381 // in the money options (guaranteed discounted payoff)
382 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000*std::exp(x: -0.05), .tol: 1e-12, .knockin: true },
383 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 15.0000*std::exp(x: -0.05), .tol: 1e-12, .knockin: true },
384 // out of bonds case
385 { .type: Option::Call, .strike: 2.37, .s: 2.33, .q: 0.07, .r: 0.43,.t: 0.19,.v: 0.005, .result: 0.0000, .tol: 1e-4, .knockin: false },
386 };
387
388 DayCounter dc = Actual360();
389 Date today = Date::todaysDate();
390
391 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
392 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
393 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
394 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
395 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
396 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
397 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
398
399 for (auto& value : values) {
400
401 ext::shared_ptr<StrikedTypePayoff> payoff(
402 new CashOrNothingPayoff(value.type, value.strike, 15.0));
403
404 Date exDate = today + timeToDays(t: value.t);
405 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
406 exDate,
407 true));
408
409 spot->setValue(value.s);
410 qRate->setValue(value.q);
411 rRate->setValue(value.r);
412 vol->setValue(value.v);
413
414 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
415 BlackScholesMertonProcess(Handle<Quote>(spot),
416 Handle<YieldTermStructure>(qTS),
417 Handle<YieldTermStructure>(rTS),
418 Handle<BlackVolTermStructure>(volTS)));
419 ext::shared_ptr<PricingEngine> engine;
420 if (value.knockin)
421 engine.reset(p: new AnalyticDigitalAmericanEngine(stochProcess));
422 else
423 engine.reset(p: new AnalyticDigitalAmericanKOEngine(stochProcess));
424
425 VanillaOption opt(payoff, amExercise);
426 opt.setPricingEngine(engine);
427
428 Real calculated = opt.NPV();
429 Real error = std::fabs(x: calculated - value.result);
430 if (error > value.tol) {
431 REPORT_FAILURE("value", payoff, amExercise, value.s, value.q, value.r, today, value.v,
432 value.result, calculated, error, value.tol, value.knockin);
433 }
434 }
435}
436
437void DigitalOptionTest::testAssetAtExpiryOrNothingAmericanValues() {
438
439 BOOST_TEST_MESSAGE("Testing American asset-(at-expiry)-or-nothing "
440 "digital option...");
441
442 DigitalOptionData values[] = {
443 // type, strike, spot, q, r, t, vol, value, tol
444 // "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 95, case 7,8,11,12
445 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 64.8426, .tol: 1e-04, .knockin: true },
446 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 77.7017, .tol: 1e-04, .knockin: true },
447 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 40.1574, .tol: 1e-04, .knockin: false },
448 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 17.2983, .tol: 1e-04, .knockin: false },
449 // data from Haug VBA code results
450 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 65.5291, .tol: 1e-04, .knockin: true },
451 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 76.5951, .tol: 1e-04, .knockin: true },
452 // in the money options (guaranteed discounted payoff = forward * riskFreeDiscount
453 // = spot * dividendDiscount)
454 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20,.result: 105.0000, .tol: 1e-12, .knockin: true },
455 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 95.0000, .tol: 1e-12, .knockin: true },
456 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20,.result: 105.0000*std::exp(x: -0.005), .tol: 1e-12, .knockin: true },
457 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.01, .r: 0.10, .t: 0.5, .v: 0.20, .result: 95.0000*std::exp(x: -0.005), .tol: 1e-12, .knockin: true }
458 };
459
460 DayCounter dc = Actual360();
461 Date today = Date::todaysDate();
462
463 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
464 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
465 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
466 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
467 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
468 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
469 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
470
471 for (auto& value : values) {
472
473 ext::shared_ptr<StrikedTypePayoff> payoff(
474 new AssetOrNothingPayoff(value.type, value.strike));
475
476 Date exDate = today + timeToDays(t: value.t);
477 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
478 exDate,
479 true));
480
481 spot->setValue(value.s);
482 qRate->setValue(value.q);
483 rRate->setValue(value.r);
484 vol->setValue(value.v);
485
486 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
487 BlackScholesMertonProcess(Handle<Quote>(spot),
488 Handle<YieldTermStructure>(qTS),
489 Handle<YieldTermStructure>(rTS),
490 Handle<BlackVolTermStructure>(volTS)));
491 ext::shared_ptr<PricingEngine> engine;
492 if (value.knockin)
493 engine.reset(p: new AnalyticDigitalAmericanEngine(stochProcess));
494 else
495 engine.reset(p: new AnalyticDigitalAmericanKOEngine(stochProcess));
496
497 VanillaOption opt(payoff, amExercise);
498 opt.setPricingEngine(engine);
499
500 Real calculated = opt.NPV();
501 Real error = std::fabs(x: calculated - value.result);
502 if (error > value.tol) {
503 REPORT_FAILURE("value", payoff, amExercise, value.s, value.q, value.r, today, value.v,
504 value.result, calculated, error, value.tol, value.knockin);
505 }
506 }
507}
508
509void DigitalOptionTest::testCashAtHitOrNothingAmericanGreeks() {
510
511 BOOST_TEST_MESSAGE("Testing American cash-(at-hit)-or-nothing "
512 "digital option greeks...");
513
514 std::map<std::string,Real> calculated, expected, tolerance;
515 tolerance["delta"] = 5.0e-5;
516 tolerance["gamma"] = 5.0e-5;
517 // tolerance["theta"] = 5.0e-5;
518 tolerance["rho"] = 5.0e-5;
519 // tolerance["divRho"] = 5.0e-5;
520 // tolerance["vega"] = 5.0e-5;
521
522 Option::Type types[] = { Option::Call, Option::Put };
523 Real strikes[] = { 50.0, 99.5, 100.5, 150.0 };
524 Real cashPayoff = 100.0;
525 Real underlyings[] = { 100 };
526 Rate qRates[] = { 0.04, 0.05, 0.06 };
527 Rate rRates[] = { 0.01, 0.05, 0.15 };
528 Volatility vols[] = { 0.11, 0.5, 1.2 };
529
530 DayCounter dc = Actual360();
531 Date today = Date::todaysDate();
532 Settings::instance().evaluationDate() = today;
533
534 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
535 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
536 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
537 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
538 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
539 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
540 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
541
542 // there is no cycling on different residual times
543 Date exDate = today + 360;
544 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
545 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
546 exDate,
547 false));
548 ext::shared_ptr<Exercise> exercises[] = { exercise, amExercise };
549
550 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
551 new BlackScholesMertonProcess(Handle<Quote>(spot),
552 qTS, rTS, volTS));
553
554 ext::shared_ptr<PricingEngine> euroEngine(
555 new AnalyticEuropeanEngine(stochProcess));
556
557 ext::shared_ptr<PricingEngine> amEngine(
558 new AnalyticDigitalAmericanEngine(stochProcess));
559
560 ext::shared_ptr<PricingEngine> engines[] = { euroEngine, amEngine };
561
562 bool knockin=true;
563 for (Size j=0; j<LENGTH(engines); j++) {
564 for (auto& type : types) {
565 for (Real strike : strikes) {
566 ext::shared_ptr<StrikedTypePayoff> payoff(
567 new CashOrNothingPayoff(type, strike, cashPayoff));
568
569 VanillaOption opt(payoff, exercises[j]);
570 opt.setPricingEngine(engines[j]);
571
572 for (Real u : underlyings) {
573 for (Real q : qRates) {
574 for (Real r : rRates) {
575 for (Real v : vols) {
576 // test data
577 spot->setValue(u);
578 qRate->setValue(q);
579 rRate->setValue(r);
580 vol->setValue(v);
581
582 // theta, dividend rho and vega are not available for
583 // digital option with american exercise. Greeks of
584 // digital options with european payoff are tested
585 // in the europeanoption.cpp test
586 Real value = opt.NPV();
587 calculated["delta"] = opt.delta();
588 calculated["gamma"] = opt.gamma();
589 // calculated["theta"] = opt.theta();
590 calculated["rho"] = opt.rho();
591 // calculated["divRho"] = opt.dividendRho();
592 // calculated["vega"] = opt.vega();
593
594 if (value > 1.0e-6) {
595 // perturb spot and get delta and gamma
596 Real du = u * 1.0e-4;
597 spot->setValue(u + du);
598 Real value_p = opt.NPV(), delta_p = opt.delta();
599 spot->setValue(u - du);
600 Real value_m = opt.NPV(), delta_m = opt.delta();
601 spot->setValue(u);
602 expected["delta"] = (value_p - value_m) / (2 * du);
603 expected["gamma"] = (delta_p - delta_m) / (2 * du);
604
605 // perturb rates and get rho and dividend rho
606 Spread dr = r * 1.0e-4;
607 rRate->setValue(r + dr);
608 value_p = opt.NPV();
609 rRate->setValue(r - dr);
610 value_m = opt.NPV();
611 rRate->setValue(r);
612 expected["rho"] = (value_p - value_m) / (2 * dr);
613
614 /*
615 Spread dq = q*1.0e-4;
616 qRate->setValue(q+dq);
617 value_p = opt.NPV();
618 qRate->setValue(q-dq);
619 value_m = opt.NPV();
620 qRate->setValue(q);
621 expected["divRho"] = (value_p - value_m)/(2*dq);
622
623 // perturb volatility and get vega
624 Volatility dv = v*1.0e-4;
625 vol->setValue(v+dv);
626 value_p = opt.NPV();
627 vol->setValue(v-dv);
628 value_m = opt.NPV();
629 vol->setValue(v);
630 expected["vega"] = (value_p - value_m)/(2*dv);
631
632 // perturb date and get theta
633 Time dT = dc.yearFraction(today-1, today+1);
634 Settings::instance().setEvaluationDate(today-1);
635 value_m = opt.NPV();
636 Settings::instance().setEvaluationDate(today+1);
637 value_p = opt.NPV();
638 Settings::instance().setEvaluationDate(today);
639 expected["theta"] = (value_p - value_m)/dT;
640 */
641
642 // check
643 std::map<std::string, Real>::iterator it;
644 for (it = calculated.begin(); it != calculated.end(); ++it) {
645 std::string greek = it->first;
646 Real expct = expected[greek], calcl = calculated[greek],
647 tol = tolerance[greek];
648 Real error = relativeError(x1: expct, x2: calcl, reference: value);
649 if (error > tol) {
650 REPORT_FAILURE(greek, payoff, exercise, u, q, r, today,
651 v, expct, calcl, error, tol, knockin);
652 }
653 }
654 }
655 }
656 }
657 }
658 }
659 }
660 }
661 }
662}
663
664
665void DigitalOptionTest::testMCCashAtHit() {
666
667 BOOST_TEST_MESSAGE("Testing Monte Carlo cash-(at-hit)-or-nothing "
668 "American engine...");
669
670 DigitalOptionData values[] = {
671 // type, strike, spot, q, r, t, vol, value, tol
672 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 12.2715, .tol: 1e-2, .knockin: true },
673 { .type: Option::Call, .strike: 100.00, .s: 95.00, .q: 0.20, .r: 0.10, .t: 0.5, .v: 0.20, .result: 8.9109, .tol: 1e-2, .knockin: true }
674 };
675
676 DayCounter dc = Actual360();
677 Date today = Date::todaysDate();
678
679 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
680 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
681 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
682 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
683 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
684 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
685 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
686
687 Size timeStepsPerYear = 90;
688 Size maxSamples = 1000000;
689 BigNatural seed = 1;
690
691 for (auto& value : values) {
692
693 ext::shared_ptr<StrikedTypePayoff> payoff(
694 new CashOrNothingPayoff(value.type, value.strike, 15.0));
695 Date exDate = today + timeToDays(t: value.t);
696 ext::shared_ptr<Exercise> amExercise(
697 new AmericanExercise(today, exDate));
698
699 spot->setValue(value.s);
700 qRate->setValue(value.q);
701 rRate->setValue(value.r);
702 vol->setValue(value.v);
703
704 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
705 BlackScholesMertonProcess(Handle<Quote>(spot),
706 Handle<YieldTermStructure>(qTS),
707 Handle<YieldTermStructure>(rTS),
708 Handle<BlackVolTermStructure>(volTS)));
709
710 Size requiredSamples = Size(std::pow(x: 2.0, y: 14)-1);
711 ext::shared_ptr<PricingEngine> mcldEngine =
712 MakeMCDigitalEngine<LowDiscrepancy>(stochProcess)
713 .withStepsPerYear(steps: timeStepsPerYear)
714 .withBrownianBridge()
715 .withSamples(samples: requiredSamples)
716 .withMaxSamples(samples: maxSamples)
717 .withSeed(seed);
718
719 VanillaOption opt(payoff, amExercise);
720 opt.setPricingEngine(mcldEngine);
721
722 Real calculated = opt.NPV();
723 Real error = std::fabs(x: calculated - value.result);
724 if (error > value.tol) {
725 REPORT_FAILURE("value", payoff, amExercise, value.s, value.q, value.r, today, value.v,
726 value.result, calculated, error, value.tol, value.knockin);
727 }
728 }
729}
730
731
732test_suite* DigitalOptionTest::suite() {
733 auto* suite = BOOST_TEST_SUITE("Digital option tests");
734 suite->add(QUANTLIB_TEST_CASE(
735 &DigitalOptionTest::testCashOrNothingEuropeanValues));
736 suite->add(QUANTLIB_TEST_CASE(
737 &DigitalOptionTest::testAssetOrNothingEuropeanValues));
738 suite->add(QUANTLIB_TEST_CASE(&DigitalOptionTest::testGapEuropeanValues));
739 suite->add(QUANTLIB_TEST_CASE(
740 &DigitalOptionTest::testCashAtHitOrNothingAmericanValues));
741 suite->add(QUANTLIB_TEST_CASE(
742 &DigitalOptionTest::testCashAtHitOrNothingAmericanGreeks));
743 suite->add(QUANTLIB_TEST_CASE(
744 &DigitalOptionTest::testAssetAtHitOrNothingAmericanValues));
745 suite->add(QUANTLIB_TEST_CASE(
746 &DigitalOptionTest::testCashAtExpiryOrNothingAmericanValues));
747 suite->add(QUANTLIB_TEST_CASE(
748 &DigitalOptionTest::testAssetAtExpiryOrNothingAmericanValues));
749 suite->add(QUANTLIB_TEST_CASE(&DigitalOptionTest::testMCCashAtHit));
750 return suite;
751}
752
753

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