1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2003, 2007 Ferdinando Ametrano
5 Copyright (C) 2003, 2007 StatPro Italia srl
6 Copyright (C) 2009 Klaus Spanderen
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 "europeanoption.hpp"
23#include "utilities.hpp"
24#include <ql/time/calendars/target.hpp>
25#include <ql/time/daycounters/actual360.hpp>
26#include <ql/instruments/europeanoption.hpp>
27#include <ql/math/randomnumbers/rngtraits.hpp>
28#include <ql/math/interpolations/bicubicsplineinterpolation.hpp>
29#include <ql/math/interpolations/bilinearinterpolation.hpp>
30#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
31#include <ql/pricingengines/vanilla/analyticdividendeuropeanengine.hpp>
32#include <ql/pricingengines/vanilla/binomialengine.hpp>
33#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
34#include <ql/experimental/variancegamma/fftvanillaengine.hpp>
35#include <ql/pricingengines/vanilla/mceuropeanengine.hpp>
36#include <ql/pricingengines/vanilla/integralengine.hpp>
37#include <ql/termstructures/yield/flatforward.hpp>
38#include <ql/termstructures/yield/zerocurve.hpp>
39#include <ql/termstructures/yield/forwardcurve.hpp>
40#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
41#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
42#include <ql/utilities/dataformatters.hpp>
43#include <map>
44
45using namespace QuantLib;
46using namespace boost::unit_test_framework;
47
48#undef REPORT_FAILURE
49#define REPORT_FAILURE(greekName, payoff, exercise, s, q, r, today, \
50 v, expected, calculated, error, tolerance) \
51 BOOST_ERROR(exerciseTypeToString(exercise) << " " \
52 << payoff->optionType() << " option with " \
53 << payoffTypeToString(payoff) << " payoff:\n" \
54 << " spot value: " << s << "\n" \
55 << " strike: " << payoff->strike() << "\n" \
56 << " dividend yield: " << io::rate(q) << "\n" \
57 << " risk-free rate: " << io::rate(r) << "\n" \
58 << " reference date: " << today << "\n" \
59 << " maturity: " << exercise->lastDate() << "\n" \
60 << " volatility: " << io::volatility(v) << "\n\n" \
61 << " expected " << greekName << ": " << expected << "\n" \
62 << " calculated " << greekName << ": " << calculated << "\n"\
63 << " error: " << error << "\n" \
64 << " tolerance: " << tolerance);
65
66namespace european_option_test {
67
68 // utilities
69
70 struct EuropeanOptionData {
71 Option::Type type;
72 Real strike;
73 Real s; // spot
74 Rate q; // dividend
75 Rate r; // risk-free rate
76 Time t; // time to maturity
77 Volatility v; // volatility
78 Real result; // expected result
79 Real tol; // tolerance
80 };
81
82 enum EngineType { Analytic,
83 JR, CRR, EQP, TGEO, TIAN, LR, JOSHI,
84 FiniteDifferences,
85 Integral,
86 PseudoMonteCarlo, QuasiMonteCarlo,
87 FFT };
88
89 ext::shared_ptr<GeneralizedBlackScholesProcess>
90 makeProcess(const ext::shared_ptr<Quote>& u,
91 const ext::shared_ptr<YieldTermStructure>& q,
92 const ext::shared_ptr<YieldTermStructure>& r,
93 const ext::shared_ptr<BlackVolTermStructure>& vol) {
94 return ext::make_shared<BlackScholesMertonProcess>(
95 args: Handle<Quote>(u),
96 args: Handle<YieldTermStructure>(q),
97 args: Handle<YieldTermStructure>(r),
98 args: Handle<BlackVolTermStructure>(vol));
99 }
100
101 ext::shared_ptr<VanillaOption>
102 makeOption(const ext::shared_ptr<StrikedTypePayoff>& payoff,
103 const ext::shared_ptr<Exercise>& exercise,
104 const ext::shared_ptr<Quote>& u,
105 const ext::shared_ptr<YieldTermStructure>& q,
106 const ext::shared_ptr<YieldTermStructure>& r,
107 const ext::shared_ptr<BlackVolTermStructure>& vol,
108 EngineType engineType,
109 Size binomialSteps,
110 Size samples) {
111
112 ext::shared_ptr<GeneralizedBlackScholesProcess> stochProcess =
113 makeProcess(u,q,r,vol);
114
115 ext::shared_ptr<PricingEngine> engine;
116 switch (engineType) {
117 case Analytic:
118 engine = ext::shared_ptr<PricingEngine>(
119 new AnalyticEuropeanEngine(stochProcess));
120 break;
121 case JR:
122 engine = ext::shared_ptr<PricingEngine>(
123 new BinomialVanillaEngine<JarrowRudd>(stochProcess,
124 binomialSteps));
125 break;
126 case CRR:
127 engine = ext::shared_ptr<PricingEngine>(
128 new BinomialVanillaEngine<CoxRossRubinstein>(stochProcess,
129 binomialSteps));
130 break;
131 case EQP:
132 engine = ext::shared_ptr<PricingEngine>(
133 new BinomialVanillaEngine<AdditiveEQPBinomialTree>(
134 stochProcess,
135 binomialSteps));
136 break;
137 case TGEO:
138 engine = ext::shared_ptr<PricingEngine>(
139 new BinomialVanillaEngine<Trigeorgis>(stochProcess,
140 binomialSteps));
141 break;
142 case TIAN:
143 engine = ext::shared_ptr<PricingEngine>(
144 new BinomialVanillaEngine<Tian>(stochProcess, binomialSteps));
145 break;
146 case LR:
147 engine = ext::shared_ptr<PricingEngine>(
148 new BinomialVanillaEngine<LeisenReimer>(stochProcess,
149 binomialSteps));
150 break;
151 case JOSHI:
152 engine = ext::shared_ptr<PricingEngine>(
153 new BinomialVanillaEngine<Joshi4>(stochProcess, binomialSteps));
154 break;
155 case FiniteDifferences:
156 engine = ext::shared_ptr<PricingEngine>(
157 new FdBlackScholesVanillaEngine(stochProcess,
158 binomialSteps,
159 samples));
160 break;
161 case Integral:
162 engine = ext::shared_ptr<PricingEngine>(
163 new IntegralEngine(stochProcess));
164 break;
165 case PseudoMonteCarlo:
166 engine = MakeMCEuropeanEngine<PseudoRandom>(stochProcess)
167 .withSteps(steps: 1)
168 .withSamples(samples)
169 .withSeed(seed: 42);
170 break;
171 case QuasiMonteCarlo:
172 engine = MakeMCEuropeanEngine<LowDiscrepancy>(stochProcess)
173 .withSteps(steps: 1)
174 .withSamples(samples);
175 break;
176 case FFT:
177 engine = ext::shared_ptr<PricingEngine>(
178 new FFTVanillaEngine(stochProcess));
179 break;
180 default:
181 QL_FAIL("unknown engine type");
182 }
183
184 ext::shared_ptr<VanillaOption> option(
185 new EuropeanOption(payoff, exercise));
186 option->setPricingEngine(engine);
187 return option;
188 }
189
190}
191
192
193void EuropeanOptionTest::testValues() {
194
195 BOOST_TEST_MESSAGE("Testing European option values...");
196
197 using namespace european_option_test;
198
199 /* The data below are from
200 "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
201 */
202 EuropeanOptionData values[] = {
203 // pag 2-8
204 // type, strike, spot, q, r, t, vol, value, tol
205 { .type: Option::Call, .strike: 65.00, .s: 60.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.30, .result: 2.1334, .tol: 1.0e-4},
206 { .type: Option::Put, .strike: 95.00, .s: 100.00, .q: 0.05, .r: 0.10, .t: 0.50, .v: 0.20, .result: 2.4648, .tol: 1.0e-4},
207 { .type: Option::Put, .strike: 19.00, .s: 19.00, .q: 0.10, .r: 0.10, .t: 0.75, .v: 0.28, .result: 1.7011, .tol: 1.0e-4},
208 { .type: Option::Call, .strike: 19.00, .s: 19.00, .q: 0.10, .r: 0.10, .t: 0.75, .v: 0.28, .result: 1.7011, .tol: 1.0e-4},
209 { .type: Option::Call, .strike: 1.60, .s: 1.56, .q: 0.08, .r: 0.06, .t: 0.50, .v: 0.12, .result: 0.0291, .tol: 1.0e-4},
210 { .type: Option::Put, .strike: 70.00, .s: 75.00, .q: 0.05, .r: 0.10, .t: 0.50, .v: 0.35, .result: 4.0870, .tol: 1.0e-4},
211 // pag 24
212 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 0.0205, .tol: 1.0e-4},
213 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 1.8734, .tol: 1.0e-4},
214 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 9.9413, .tol: 1.0e-4},
215 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 0.3150, .tol: 1.0e-4},
216 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 3.1217, .tol: 1.0e-4},
217 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 10.3556, .tol: 1.0e-4},
218 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 0.9474, .tol: 1.0e-4},
219 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 4.3693, .tol: 1.0e-4},
220 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 11.1381, .tol: 1.0e-4},
221 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 0.8069, .tol: 1.0e-4},
222 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 4.0232, .tol: 1.0e-4},
223 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 10.5769, .tol: 1.0e-4},
224 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 2.7026, .tol: 1.0e-4},
225 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 6.6997, .tol: 1.0e-4},
226 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 12.7857, .tol: 1.0e-4},
227 { .type: Option::Call, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 4.9329, .tol: 1.0e-4},
228 { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 9.3679, .tol: 1.0e-4},
229 { .type: Option::Call, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 15.3086, .tol: 1.0e-4},
230 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 9.9210, .tol: 1.0e-4},
231 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 1.8734, .tol: 1.0e-4},
232 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.15, .result: 0.0408, .tol: 1.0e-4},
233 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 10.2155, .tol: 1.0e-4},
234 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 3.1217, .tol: 1.0e-4},
235 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.25, .result: 0.4551, .tol: 1.0e-4},
236 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 10.8479, .tol: 1.0e-4},
237 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 4.3693, .tol: 1.0e-4},
238 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.10, .v: 0.35, .result: 1.2376, .tol: 1.0e-4},
239 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 10.3192, .tol: 1.0e-4},
240 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 4.0232, .tol: 1.0e-4},
241 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.15, .result: 1.0646, .tol: 1.0e-4},
242 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 12.2149, .tol: 1.0e-4},
243 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 6.6997, .tol: 1.0e-4},
244 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.25, .result: 3.2734, .tol: 1.0e-4},
245 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 14.4452, .tol: 1.0e-4},
246 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 9.3679, .tol: 1.0e-4},
247 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.10, .r: 0.10, .t: 0.50, .v: 0.35, .result: 5.7963, .tol: 1.0e-4},
248 // pag 27
249 { .type: Option::Call, .strike: 40.00, .s: 42.00, .q: 0.08, .r: 0.04, .t: 0.75, .v: 0.35, .result: 5.0975, .tol: 1.0e-4}
250 };
251
252 DayCounter dc = Actual360();
253 Date today = Date::todaysDate();
254
255 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
256 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
257 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
258 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
259 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
260 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
261 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
262
263 for (auto& value : values) {
264
265 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike));
266 Date exDate = today + timeToDays(t: value.t);
267 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
268
269 spot->setValue(value.s);
270 qRate->setValue(value.q);
271 rRate->setValue(value.r);
272 vol->setValue(value.v);
273
274 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
275 BlackScholesMertonProcess(Handle<Quote>(spot),
276 Handle<YieldTermStructure>(qTS),
277 Handle<YieldTermStructure>(rTS),
278 Handle<BlackVolTermStructure>(volTS)));
279 ext::shared_ptr<PricingEngine> engine(
280 new AnalyticEuropeanEngine(stochProcess));
281
282 EuropeanOption option(payoff, exercise);
283 option.setPricingEngine(engine);
284
285 Real calculated = option.NPV();
286 Real error = std::fabs(x: calculated - value.result);
287 Real tolerance = value.tol;
288 if (error>tolerance) {
289 REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today, value.v,
290 value.result, calculated, error, tolerance);
291 }
292
293 engine = ext::shared_ptr<PricingEngine>(
294 new FdBlackScholesVanillaEngine(stochProcess,200,400));
295 option.setPricingEngine(engine);
296
297 calculated = option.NPV();
298 error = std::fabs(x: calculated - value.result);
299 tolerance = 1.0e-3;
300 if (error>tolerance) {
301 REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today, value.v,
302 value.result, calculated, error, tolerance);
303 }
304 }
305}
306
307
308
309void EuropeanOptionTest::testGreekValues() {
310
311 BOOST_TEST_MESSAGE("Testing European option greek values...");
312
313 using namespace european_option_test;
314
315 /* The data below are from
316 "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
317 pag 11-16
318 */
319 EuropeanOptionData values[] = {
320 // type, strike, spot, q, r, t, vol, value
321 // delta
322 { .type: Option::Call, .strike: 100.00, .s: 105.00, .q: 0.10, .r: 0.10, .t: 0.500000, .v: 0.36, .result: 0.5946, .tol: 0 },
323 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.10, .r: 0.10, .t: 0.500000, .v: 0.36, .result: -0.3566, .tol: 0 },
324 // elasticity
325 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.10, .r: 0.10, .t: 0.500000, .v: 0.36, .result: -4.8775, .tol: 0 },
326 // gamma
327 { .type: Option::Call, .strike: 60.00, .s: 55.00, .q: 0.00, .r: 0.10, .t: 0.750000, .v: 0.30, .result: 0.0278, .tol: 0 },
328 { .type: Option::Put, .strike: 60.00, .s: 55.00, .q: 0.00, .r: 0.10, .t: 0.750000, .v: 0.30, .result: 0.0278, .tol: 0 },
329 // vega
330 { .type: Option::Call, .strike: 60.00, .s: 55.00, .q: 0.00, .r: 0.10, .t: 0.750000, .v: 0.30, .result: 18.9358, .tol: 0 },
331 { .type: Option::Put, .strike: 60.00, .s: 55.00, .q: 0.00, .r: 0.10, .t: 0.750000, .v: 0.30, .result: 18.9358, .tol: 0 },
332 // theta
333 { .type: Option::Put, .strike: 405.00, .s: 430.00, .q: 0.05, .r: 0.07, .t: 1.0/12.0, .v: 0.20,.result: -31.1924, .tol: 0 },
334 // theta per day
335 { .type: Option::Put, .strike: 405.00, .s: 430.00, .q: 0.05, .r: 0.07, .t: 1.0/12.0, .v: 0.20, .result: -0.0855, .tol: 0 },
336 // rho
337 { .type: Option::Call, .strike: 75.00, .s: 72.00, .q: 0.00, .r: 0.09, .t: 1.000000, .v: 0.19, .result: 38.7325, .tol: 0 },
338 // dividendRho
339 { .type: Option::Put, .strike: 490.00, .s: 500.00, .q: 0.05, .r: 0.08, .t: 0.250000, .v: 0.15, .result: 42.2254, .tol: 0 }
340 };
341
342 DayCounter dc = Actual360();
343 Date today = Date::todaysDate();
344
345 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
346 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
347 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
348 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
349 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
350 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
351 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
352 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
353 BlackScholesMertonProcess(Handle<Quote>(spot),
354 Handle<YieldTermStructure>(qTS),
355 Handle<YieldTermStructure>(rTS),
356 Handle<BlackVolTermStructure>(volTS)));
357 ext::shared_ptr<PricingEngine> engine(
358 new AnalyticEuropeanEngine(stochProcess));
359
360 ext::shared_ptr<StrikedTypePayoff> payoff;
361 Date exDate;
362 ext::shared_ptr<Exercise> exercise;
363 ext::shared_ptr<VanillaOption> option;
364 Real calculated;
365
366 Integer i = -1;
367
368 i++;
369 payoff = ext::shared_ptr<StrikedTypePayoff>(new
370 PlainVanillaPayoff(values[i].type, values[i].strike));
371 exDate = today + timeToDays(t: values[i].t);
372 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
373 spot ->setValue(values[i].s);
374 qRate->setValue(values[i].q);
375 rRate->setValue(values[i].r);
376 vol ->setValue(values[i].v);
377 option = ext::shared_ptr<VanillaOption>(
378 new EuropeanOption(payoff, exercise));
379 option->setPricingEngine(engine);
380 calculated = option->delta();
381 Real error = std::fabs(x: calculated-values[i].result);
382 Real tolerance = 1e-4;
383 if (error>tolerance)
384 REPORT_FAILURE("delta", payoff, exercise, values[i].s,
385 values[i].q, values[i].r, today,
386 values[i].v, values[i].result, calculated,
387 error, tolerance);
388
389 i++;
390 payoff = ext::shared_ptr<StrikedTypePayoff>(new
391 PlainVanillaPayoff(values[i].type, values[i].strike));
392 exDate = today + timeToDays(t: values[i].t);
393 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
394 spot ->setValue(values[i].s);
395 qRate->setValue(values[i].q);
396 rRate->setValue(values[i].r);
397 vol ->setValue(values[i].v);
398 option = ext::shared_ptr<VanillaOption>(
399 new EuropeanOption(payoff, exercise));
400 option->setPricingEngine(engine);
401 calculated = option->delta();
402 error = std::fabs(x: calculated-values[i].result);
403 if (error>tolerance)
404 REPORT_FAILURE("delta", payoff, exercise, values[i].s,
405 values[i].q, values[i].r, today,
406 values[i].v, values[i].result, calculated,
407 error, tolerance);
408
409 i++;
410 payoff = ext::shared_ptr<StrikedTypePayoff>(new
411 PlainVanillaPayoff(values[i].type, values[i].strike));
412 exDate = today + timeToDays(t: values[i].t);
413 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
414 spot ->setValue(values[i].s);
415 qRate->setValue(values[i].q);
416 rRate->setValue(values[i].r);
417 vol ->setValue(values[i].v);
418 option = ext::shared_ptr<VanillaOption>(
419 new EuropeanOption(payoff, exercise));
420 option->setPricingEngine(engine);
421 calculated = option->elasticity();
422 error = std::fabs(x: calculated-values[i].result);
423 if (error>tolerance)
424 REPORT_FAILURE("elasticity", payoff, exercise, values[i].s,
425 values[i].q, values[i].r, today,
426 values[i].v, values[i].result, calculated,
427 error, tolerance);
428
429
430 i++;
431 payoff = ext::shared_ptr<StrikedTypePayoff>(new
432 PlainVanillaPayoff(values[i].type, values[i].strike));
433 exDate = today + timeToDays(t: values[i].t);
434 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
435 spot ->setValue(values[i].s);
436 qRate->setValue(values[i].q);
437 rRate->setValue(values[i].r);
438 vol ->setValue(values[i].v);
439 option = ext::shared_ptr<VanillaOption>(
440 new EuropeanOption(payoff, exercise));
441 option->setPricingEngine(engine);
442 calculated = option->gamma();
443 error = std::fabs(x: calculated-values[i].result);
444 if (error>tolerance)
445 REPORT_FAILURE("gamma", payoff, exercise, values[i].s,
446 values[i].q, values[i].r, today,
447 values[i].v, values[i].result, calculated,
448 error, tolerance);
449
450 i++;
451 payoff = ext::shared_ptr<StrikedTypePayoff>(new
452 PlainVanillaPayoff(values[i].type, values[i].strike));
453 exDate = today + timeToDays(t: values[i].t);
454 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
455 spot ->setValue(values[i].s);
456 qRate->setValue(values[i].q);
457 rRate->setValue(values[i].r);
458 vol ->setValue(values[i].v);
459 option = ext::shared_ptr<VanillaOption>(
460 new EuropeanOption(payoff, exercise));
461 option->setPricingEngine(engine);
462 calculated = option->gamma();
463 error = std::fabs(x: calculated-values[i].result);
464 if (error>tolerance)
465 REPORT_FAILURE("gamma", payoff, exercise, values[i].s,
466 values[i].q, values[i].r, today,
467 values[i].v, values[i].result, calculated,
468 error, tolerance);
469
470
471 i++;
472 payoff = ext::shared_ptr<StrikedTypePayoff>(new
473 PlainVanillaPayoff(values[i].type, values[i].strike));
474 exDate = today + timeToDays(t: values[i].t);
475 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
476 spot ->setValue(values[i].s);
477 qRate->setValue(values[i].q);
478 rRate->setValue(values[i].r);
479 vol ->setValue(values[i].v);
480 option = ext::shared_ptr<VanillaOption>(
481 new EuropeanOption(payoff, exercise));
482 option->setPricingEngine(engine);
483 calculated = option->vega();
484 error = std::fabs(x: calculated-values[i].result);
485 if (error>tolerance)
486 REPORT_FAILURE("vega", payoff, exercise, values[i].s,
487 values[i].q, values[i].r, today,
488 values[i].v, values[i].result, calculated,
489 error, tolerance);
490
491
492 i++;
493 payoff = ext::shared_ptr<StrikedTypePayoff>(new
494 PlainVanillaPayoff(values[i].type, values[i].strike));
495 exDate = today + timeToDays(t: values[i].t);
496 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
497 spot ->setValue(values[i].s);
498 qRate->setValue(values[i].q);
499 rRate->setValue(values[i].r);
500 vol ->setValue(values[i].v);
501 option = ext::shared_ptr<VanillaOption>(
502 new EuropeanOption(payoff, exercise));
503 option->setPricingEngine(engine);
504 calculated = option->vega();
505 error = std::fabs(x: calculated-values[i].result);
506 if (error>tolerance)
507 REPORT_FAILURE("vega", payoff, exercise, values[i].s,
508 values[i].q, values[i].r, today,
509 values[i].v, values[i].result, calculated,
510 error, tolerance);
511
512
513 i++;
514 payoff = ext::shared_ptr<StrikedTypePayoff>(new
515 PlainVanillaPayoff(values[i].type, values[i].strike));
516 exDate = today + timeToDays(t: values[i].t);
517 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
518 spot ->setValue(values[i].s);
519 qRate->setValue(values[i].q);
520 rRate->setValue(values[i].r);
521 vol ->setValue(values[i].v);
522 option = ext::shared_ptr<VanillaOption>(
523 new EuropeanOption(payoff, exercise));
524 option->setPricingEngine(engine);
525 calculated = option->theta();
526 error = std::fabs(x: calculated-values[i].result);
527 if (error>tolerance)
528 REPORT_FAILURE("theta", payoff, exercise, values[i].s,
529 values[i].q, values[i].r, today,
530 values[i].v, values[i].result, calculated,
531 error, tolerance);
532
533
534 i++;
535 payoff = ext::shared_ptr<StrikedTypePayoff>(new
536 PlainVanillaPayoff(values[i].type, values[i].strike));
537 exDate = today + timeToDays(t: values[i].t);
538 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
539 spot ->setValue(values[i].s);
540 qRate->setValue(values[i].q);
541 rRate->setValue(values[i].r);
542 vol ->setValue(values[i].v);
543 option = ext::shared_ptr<VanillaOption>(
544 new EuropeanOption(payoff, exercise));
545 option->setPricingEngine(engine);
546 calculated = option->thetaPerDay();
547 error = std::fabs(x: calculated-values[i].result);
548 if (error>tolerance)
549 REPORT_FAILURE("thetaPerDay", payoff, exercise, values[i].s,
550 values[i].q, values[i].r, today,
551 values[i].v, values[i].result, calculated,
552 error, tolerance);
553
554
555 i++;
556 payoff = ext::shared_ptr<StrikedTypePayoff>(new
557 PlainVanillaPayoff(values[i].type, values[i].strike));
558 exDate = today + timeToDays(t: values[i].t);
559 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
560 spot ->setValue(values[i].s);
561 qRate->setValue(values[i].q);
562 rRate->setValue(values[i].r);
563 vol ->setValue(values[i].v);
564 option = ext::shared_ptr<VanillaOption>(
565 new EuropeanOption(payoff, exercise));
566 option->setPricingEngine(engine);
567 calculated = option->rho();
568 error = std::fabs(x: calculated-values[i].result);
569 if (error>tolerance)
570 REPORT_FAILURE("rho", payoff, exercise, values[i].s,
571 values[i].q, values[i].r, today,
572 values[i].v, values[i].result, calculated,
573 error, tolerance);
574
575
576 i++;
577 payoff = ext::shared_ptr<StrikedTypePayoff>(new
578 PlainVanillaPayoff(values[i].type, values[i].strike));
579 exDate = today + timeToDays(t: values[i].t);
580 exercise = ext::shared_ptr<Exercise>(new EuropeanExercise(exDate));
581 spot ->setValue(values[i].s);
582 qRate->setValue(values[i].q);
583 rRate->setValue(values[i].r);
584 vol ->setValue(values[i].v);
585 option = ext::shared_ptr<VanillaOption>(
586 new EuropeanOption(payoff, exercise));
587 option->setPricingEngine(engine);
588 calculated = option->dividendRho();
589 error = std::fabs(x: calculated-values[i].result);
590 if (error>tolerance)
591 REPORT_FAILURE("dividendRho", payoff, exercise, values[i].s,
592 values[i].q, values[i].r, today,
593 values[i].v, values[i].result, calculated,
594 error, tolerance);
595
596}
597
598void EuropeanOptionTest::testGreeks() {
599
600 BOOST_TEST_MESSAGE("Testing analytic European option greeks...");
601
602 using namespace european_option_test;
603
604 std::map<std::string,Real> calculated, expected, tolerance;
605 tolerance["delta"] = 1.0e-5;
606 tolerance["gamma"] = 1.0e-5;
607 tolerance["theta"] = 1.0e-5;
608 tolerance["rho"] = 1.0e-5;
609 tolerance["divRho"] = 1.0e-5;
610 tolerance["vega"] = 1.0e-5;
611
612 Option::Type types[] = { Option::Call, Option::Put };
613 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
614 Real underlyings[] = { 100.0 };
615 Rate qRates[] = { 0.04, 0.05, 0.06 };
616 Rate rRates[] = { 0.01, 0.05, 0.15 };
617 Time residualTimes[] = { 1.0, 2.0 };
618 Volatility vols[] = { 0.11, 0.50, 1.20 };
619
620 DayCounter dc = Actual360();
621 Date today = Date::todaysDate();
622 Settings::instance().evaluationDate() = today;
623
624 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
625 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
626 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
627 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
628 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
629 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
630 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
631
632 ext::shared_ptr<StrikedTypePayoff> payoff;
633
634 for (auto& type : types) {
635 for (Real strike : strikes) {
636 for (Real residualTime : residualTimes) {
637 Date exDate = today + timeToDays(t: residualTime);
638 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
639 for (Size kk = 0; kk < 4; kk++) {
640 // option to check
641 if (kk == 0) {
642 payoff = ext::shared_ptr<StrikedTypePayoff>(
643 new PlainVanillaPayoff(type, strike));
644 } else if (kk == 1) {
645 payoff = ext::shared_ptr<StrikedTypePayoff>(
646 new CashOrNothingPayoff(type, strike, 100.0));
647 } else if (kk == 2) {
648 payoff = ext::shared_ptr<StrikedTypePayoff>(
649 new AssetOrNothingPayoff(type, strike));
650 } else if (kk == 3) {
651 payoff =
652 ext::shared_ptr<StrikedTypePayoff>(new GapPayoff(type, strike, 100.0));
653 }
654
655 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
656 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
657 ext::shared_ptr<PricingEngine> engine(new AnalyticEuropeanEngine(stochProcess));
658 EuropeanOption option(payoff, exercise);
659 option.setPricingEngine(engine);
660
661 for (Real u : underlyings) {
662 for (Real m : qRates) {
663 for (Real n : rRates) {
664 for (Real v : vols) {
665 Rate q = m, r = n;
666 spot->setValue(u);
667 qRate->setValue(q);
668 rRate->setValue(r);
669 vol->setValue(v);
670
671 Real value = option.NPV();
672 calculated["delta"] = option.delta();
673 calculated["gamma"] = option.gamma();
674 calculated["theta"] = option.theta();
675 calculated["rho"] = option.rho();
676 calculated["divRho"] = option.dividendRho();
677 calculated["vega"] = option.vega();
678
679 if (value > spot->value() * 1.0e-5) {
680 // perturb spot and get delta and gamma
681 Real du = u * 1.0e-4;
682 spot->setValue(u + du);
683 Real value_p = option.NPV(), delta_p = option.delta();
684 spot->setValue(u - du);
685 Real value_m = option.NPV(), delta_m = option.delta();
686 spot->setValue(u);
687 expected["delta"] = (value_p - value_m) / (2 * du);
688 expected["gamma"] = (delta_p - delta_m) / (2 * du);
689
690 // perturb rates and get rho and dividend rho
691 Spread dr = r * 1.0e-4;
692 rRate->setValue(r + dr);
693 value_p = option.NPV();
694 rRate->setValue(r - dr);
695 value_m = option.NPV();
696 rRate->setValue(r);
697 expected["rho"] = (value_p - value_m) / (2 * dr);
698
699 Spread dq = q * 1.0e-4;
700 qRate->setValue(q + dq);
701 value_p = option.NPV();
702 qRate->setValue(q - dq);
703 value_m = option.NPV();
704 qRate->setValue(q);
705 expected["divRho"] = (value_p - value_m) / (2 * dq);
706
707 // perturb volatility and get vega
708 Volatility dv = v * 1.0e-4;
709 vol->setValue(v + dv);
710 value_p = option.NPV();
711 vol->setValue(v - dv);
712 value_m = option.NPV();
713 vol->setValue(v);
714 expected["vega"] = (value_p - value_m) / (2 * dv);
715
716 // perturb date and get theta
717 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
718 Settings::instance().evaluationDate() = today - 1;
719 value_m = option.NPV();
720 Settings::instance().evaluationDate() = today + 1;
721 value_p = option.NPV();
722 Settings::instance().evaluationDate() = today;
723 expected["theta"] = (value_p - value_m) / dT;
724
725 // compare
726 std::map<std::string, Real>::iterator it;
727 for (it = calculated.begin(); it != calculated.end();
728 ++it) {
729 std::string greek = it->first;
730 Real expct = expected[greek], calcl = calculated[greek],
731 tol = tolerance[greek];
732 Real error = relativeError(x1: expct, x2: calcl, reference: u);
733 if (error > tol) {
734 REPORT_FAILURE(greek, payoff, exercise, u, q, r,
735 today, v, expct, calcl, error, tol);
736 }
737 }
738 }
739 }
740 }
741 }
742 }
743 }
744 }
745 }
746 }
747}
748
749void EuropeanOptionTest::testImpliedVol() {
750
751 BOOST_TEST_MESSAGE("Testing European option implied volatility...");
752
753 using namespace european_option_test;
754
755 Size maxEvaluations = 100;
756 Real tolerance = 1.0e-6;
757
758 // test options
759 Option::Type types[] = { Option::Call, Option::Put };
760 Real strikes[] = { 90.0, 99.5, 100.0, 100.5, 110.0 };
761 Integer lengths[] = { 36, 180, 360, 1080 };
762
763 // test data
764 Real underlyings[] = { 90.0, 95.0, 99.9, 100.0, 100.1, 105.0, 110.0 };
765 Rate qRates[] = { 0.01, 0.05, 0.10 };
766 Rate rRates[] = { 0.01, 0.05, 0.10 };
767 Volatility vols[] = { 0.01, 0.20, 0.30, 0.70, 0.90 };
768
769 DayCounter dc = Actual360();
770 Date today = Date::todaysDate();
771
772 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
773 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
774 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
775 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
776 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
777 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
778 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
779
780 for (auto& type : types) {
781 for (Real& strike : strikes) {
782 for (int length : lengths) {
783 // option to check
784 Date exDate = today + length;
785 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
786 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
787 ext::shared_ptr<VanillaOption> option = makeOption(
788 payoff, exercise, u: spot, q: qTS, r: rTS, vol: volTS, engineType: Analytic, binomialSteps: Null<Size>(), samples: Null<Size>());
789
790 ext::shared_ptr<GeneralizedBlackScholesProcess> process =
791 makeProcess(u: spot, q: qTS, r: rTS, vol: volTS);
792
793 for (Real u : underlyings) {
794 for (Real m : qRates) {
795 for (Real n : rRates) {
796 for (Real v : vols) {
797 Rate q = m, r = n;
798 spot->setValue(u);
799 qRate->setValue(q);
800 rRate->setValue(r);
801 vol->setValue(v);
802
803 Real value = option->NPV();
804 Volatility implVol = 0.0; // just to remove a warning...
805 if (value != 0.0) {
806 // shift guess somehow
807 vol->setValue(v * 0.5);
808 if (std::fabs(x: value - option->NPV()) <= 1.0e-12) {
809 // flat price vs vol --- pointless (and
810 // numerically unstable) to solve
811 continue;
812 }
813 try {
814 implVol = option->impliedVolatility(
815 price: value, process, accuracy: tolerance, maxEvaluations);
816 } catch (std::exception& e) {
817 BOOST_ERROR("\nimplied vol calculation failed:"
818 << "\n option: " << type
819 << "\n strike: " << strike
820 << "\n spot value: " << u
821 << "\n dividend yield: " << io::rate(q)
822 << "\n risk-free rate: " << io::rate(r)
823 << "\n today: " << today
824 << "\n maturity: " << exDate
825 << "\n volatility: " << io::volatility(v)
826 << "\n option value: " << value << "\n"
827 << e.what());
828 }
829 if (std::fabs(x: implVol - v) > tolerance) {
830 // the difference might not matter
831 vol->setValue(implVol);
832 Real value2 = option->NPV();
833 Real error = relativeError(x1: value, x2: value2, reference: u);
834 if (error > tolerance) {
835 BOOST_ERROR(
836 type
837 << " option :\n"
838 << " spot value: " << u << "\n"
839 << " strike: " << strike << "\n"
840 << " dividend yield: " << io::rate(q)
841 << "\n"
842 << " risk-free rate: " << io::rate(r)
843 << "\n"
844 << " maturity: " << exDate << "\n\n"
845 << " original volatility: " << io::volatility(v)
846 << "\n"
847 << " price: " << value << "\n"
848 << " implied volatility: "
849 << io::volatility(implVol) << "\n"
850 << " corresponding price: " << value2 << "\n"
851 << " error: " << error);
852 }
853 }
854 }
855 }
856 }
857 }
858 }
859 }
860 }
861 }
862}
863
864
865void EuropeanOptionTest::testImpliedVolWithDividends() {
866
867 BOOST_TEST_MESSAGE("Testing European option implied volatility with dividends...");
868
869 using namespace european_option_test;
870
871 Size maxEvaluations = 100;
872 Real tolerance = 1.0e-6;
873
874 // test options
875 Option::Type types[] = { Option::Call, Option::Put };
876 Real strikes[] = { 90.0, 99.5, 100.0, 100.5, 110.0 };
877 Integer lengths[] = { 36, 180, 360, 1080 };
878
879 // test data
880 Real underlyings[] = { 90.0, 95.0, 99.9, 100.0, 100.1, 105.0, 110.0 };
881 Rate qRates[] = { 0.01, 0.05, 0.10 };
882 Rate rRates[] = { 0.01, 0.05, 0.10 };
883 Volatility vols[] = { 0.01, 0.20, 0.30, 0.70, 0.90 };
884
885 DayCounter dc = Actual360();
886 Date today = Date::todaysDate();
887
888 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
889 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
890 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
891 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
892 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
893 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
894 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
895
896 for (auto& type : types) {
897 for (Real& strike : strikes) {
898 for (int length : lengths) {
899 // option to check
900 Date exDate = today + length;
901 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
902 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
903 auto process = makeProcess(u: spot, q: qTS, r: rTS, vol: volTS);
904 auto dividends = DividendVector(dividendDates: { today + length/2 }, dividends: { 1.0 });
905 auto option = makeOption(
906 payoff, exercise, u: spot, q: qTS, r: rTS, vol: volTS, engineType: Analytic, binomialSteps: Null<Size>(), samples: Null<Size>());
907 auto divEngine = ext::make_shared<AnalyticDividendEuropeanEngine>(args&: process, args&: dividends);
908 option->setPricingEngine(divEngine);
909
910 for (Real u : underlyings) {
911 for (Real m : qRates) {
912 for (Real n : rRates) {
913 for (Real v : vols) {
914 Rate q = m, r = n;
915 spot->setValue(u);
916 qRate->setValue(q);
917 rRate->setValue(r);
918 vol->setValue(v);
919
920 Real value = option->NPV();
921 Volatility implVol = 0.0; // just to remove a warning...
922 if (value != 0.0) {
923 // shift guess somehow
924 vol->setValue(v * 0.5);
925 if (std::fabs(x: value - option->NPV()) <= 1.0e-12) {
926 // flat price vs vol --- pointless (and
927 // numerically unstable) to solve
928 continue;
929 }
930 try {
931 implVol = option->impliedVolatility(
932 price: value, process, dividends, accuracy: tolerance, maxEvaluations);
933 } catch (std::exception& e) {
934 BOOST_ERROR("\nimplied vol calculation failed:"
935 << "\n option: " << type
936 << "\n strike: " << strike
937 << "\n spot value: " << u
938 << "\n dividend yield: " << io::rate(q)
939 << "\n risk-free rate: " << io::rate(r)
940 << "\n today: " << today
941 << "\n maturity: " << exDate
942 << "\n volatility: " << io::volatility(v)
943 << "\n option value: " << value << "\n"
944 << e.what());
945 }
946 if (std::fabs(x: implVol - v) > tolerance) {
947 // the difference might not matter
948 vol->setValue(implVol);
949 Real value2 = option->NPV();
950 Real error = relativeError(x1: value, x2: value2, reference: u);
951 if (error > tolerance) {
952 BOOST_ERROR(
953 type
954 << " option :\n"
955 << " spot value: " << u << "\n"
956 << " strike: " << strike << "\n"
957 << " dividend yield: " << io::rate(q)
958 << "\n"
959 << " risk-free rate: " << io::rate(r)
960 << "\n"
961 << " maturity: " << exDate << "\n\n"
962 << " original volatility: " << io::volatility(v)
963 << "\n"
964 << " price: " << value << "\n"
965 << " implied volatility: "
966 << io::volatility(implVol) << "\n"
967 << " corresponding price: " << value2 << "\n"
968 << " error: " << error);
969 }
970 }
971 }
972 }
973 }
974 }
975 }
976 }
977 }
978 }
979}
980
981
982void EuropeanOptionTest::testImpliedVolContainment() {
983
984 BOOST_TEST_MESSAGE("Testing self-containment of "
985 "implied volatility calculation...");
986
987 Size maxEvaluations = 100;
988 Real tolerance = 1.0e-6;
989
990 // test options
991
992 DayCounter dc = Actual360();
993 Date today = Date::todaysDate();
994
995 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
996 Handle<Quote> underlying(spot);
997 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.05));
998 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
999 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.03));
1000 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
1001 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
1002 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
1003
1004 Date exerciseDate = today + 1*Years;
1005 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
1006 ext::shared_ptr<StrikedTypePayoff> payoff(
1007 new PlainVanillaPayoff(Option::Call, 100.0));
1008
1009 ext::shared_ptr<BlackScholesMertonProcess> process(
1010 new BlackScholesMertonProcess(underlying, qTS, rTS, volTS));
1011 ext::shared_ptr<PricingEngine> engine(
1012 new AnalyticEuropeanEngine(process));
1013 // link to the same stochastic process, which shouldn't be changed
1014 // by calling methods of either option
1015
1016 ext::shared_ptr<VanillaOption> option1(
1017 new EuropeanOption(payoff, exercise));
1018 option1->setPricingEngine(engine);
1019 ext::shared_ptr<VanillaOption> option2(
1020 new EuropeanOption(payoff, exercise));
1021 option2->setPricingEngine(engine);
1022
1023 // test
1024
1025 Real refValue = option2->NPV();
1026
1027 Flag f;
1028 f.registerWith(h: option2);
1029
1030 option1->impliedVolatility(price: refValue*1.5, process,
1031 accuracy: tolerance, maxEvaluations);
1032
1033 if (f.isUp())
1034 BOOST_ERROR("implied volatility calculation triggered a change "
1035 "in another instrument");
1036
1037 option2->recalculate();
1038 if (std::fabs(x: option2->NPV() - refValue) >= 1.0e-8)
1039 BOOST_ERROR("implied volatility calculation changed the value "
1040 << "of another instrument: \n"
1041 << std::setprecision(8)
1042 << "previous value: " << refValue << "\n"
1043 << "current value: " << option2->NPV());
1044
1045 vol->setValue(vol->value()*1.5);
1046
1047 if (!f.isUp())
1048 BOOST_ERROR("volatility change not notified");
1049
1050 if (std::fabs(x: option2->NPV() - refValue) <= 1.0e-8)
1051 BOOST_ERROR("volatility change did not cause the value to change");
1052
1053}
1054
1055
1056// different engines
1057
1058namespace {
1059
1060 void testEngineConsistency(european_option_test::EngineType engine,
1061 Size binomialSteps,
1062 Size samples,
1063 std::map<std::string,Real> tolerance,
1064 bool testGreeks = false) {
1065
1066 using namespace european_option_test;
1067
1068 std::map<std::string,Real> calculated, expected;
1069
1070 // test options
1071 Option::Type types[] = { Option::Call, Option::Put };
1072 Real strikes[] = { 75.0, 100.0, 125.0 };
1073 Integer lengths[] = { 1 };
1074
1075 // test data
1076 Real underlyings[] = { 100.0 };
1077 Rate qRates[] = { 0.00, 0.05 };
1078 Rate rRates[] = { 0.01, 0.05, 0.15 };
1079 Volatility vols[] = { 0.11, 0.50, 1.20 };
1080
1081 DayCounter dc = Actual360();
1082 Date today = Date::todaysDate();
1083
1084 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
1085 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
1086 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today,volatility: vol,dc);
1087 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
1088 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today,forward: qRate,dc);
1089 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
1090 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today,forward: rRate,dc);
1091
1092 for (auto& type : types) {
1093 for (Real strike : strikes) {
1094 for (int length : lengths) {
1095 Date exDate = today + length * 360;
1096 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
1097 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
1098 // reference option
1099 ext::shared_ptr<VanillaOption> refOption =
1100 makeOption(payoff, exercise, u: spot, q: qTS, r: rTS, vol: volTS, engineType: Analytic, binomialSteps: Null<Size>(),
1101 samples: Null<Size>());
1102 // option to check
1103 ext::shared_ptr<VanillaOption> option = makeOption(
1104 payoff, exercise, u: spot, q: qTS, r: rTS, vol: volTS, engineType: engine, binomialSteps, samples);
1105
1106 for (Real u : underlyings) {
1107 for (Real m : qRates) {
1108 for (Real n : rRates) {
1109 for (Real v : vols) {
1110 Rate q = m, r = n;
1111 spot->setValue(u);
1112 qRate->setValue(q);
1113 rRate->setValue(r);
1114 vol->setValue(v);
1115
1116 expected.clear();
1117 calculated.clear();
1118
1119 expected["value"] = refOption->NPV();
1120 calculated["value"] = option->NPV();
1121
1122 if (testGreeks && option->NPV() > spot->value() * 1.0e-5) {
1123 expected["delta"] = refOption->delta();
1124 expected["gamma"] = refOption->gamma();
1125 expected["theta"] = refOption->theta();
1126 calculated["delta"] = option->delta();
1127 calculated["gamma"] = option->gamma();
1128 calculated["theta"] = option->theta();
1129 }
1130 std::map<std::string, Real>::iterator it;
1131 for (it = calculated.begin(); it != calculated.end(); ++it) {
1132 std::string greek = it->first;
1133 Real expct = expected[greek], calcl = calculated[greek],
1134 tol = tolerance[greek];
1135 Real error = relativeError(x1: expct, x2: calcl, reference: u);
1136 if (error > tol) {
1137 REPORT_FAILURE(greek, payoff, exercise, u, q, r, today,
1138 v, expct, calcl, error, tol);
1139 }
1140 }
1141 }
1142 }
1143 }
1144 }
1145 }
1146 }
1147 }
1148 }
1149
1150}
1151
1152
1153void EuropeanOptionTest::testJRBinomialEngines() {
1154
1155 BOOST_TEST_MESSAGE("Testing JR binomial European engines "
1156 "against analytic results...");
1157
1158 using namespace european_option_test;
1159
1160 EngineType engine = JR;
1161 Size steps = 251;
1162 Size samples = Null<Size>();
1163 std::map<std::string,Real> relativeTol;
1164 relativeTol["value"] = 0.002;
1165 relativeTol["delta"] = 1.0e-3;
1166 relativeTol["gamma"] = 1.0e-4;
1167 relativeTol["theta"] = 0.03;
1168 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1169}
1170
1171void EuropeanOptionTest::testCRRBinomialEngines() {
1172
1173 BOOST_TEST_MESSAGE("Testing CRR binomial European engines "
1174 "against analytic results...");
1175
1176 using namespace european_option_test;
1177
1178 EngineType engine = CRR;
1179 Size steps = 501;
1180 Size samples = Null<Size>();
1181 std::map<std::string,Real> relativeTol;
1182 relativeTol["value"] = 0.02;
1183 relativeTol["delta"] = 1.0e-3;
1184 relativeTol["gamma"] = 1.0e-4;
1185 relativeTol["theta"] = 0.03;
1186 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1187}
1188
1189void EuropeanOptionTest::testEQPBinomialEngines() {
1190
1191 BOOST_TEST_MESSAGE("Testing EQP binomial European engines "
1192 "against analytic results...");
1193
1194 using namespace european_option_test;
1195
1196 EngineType engine = EQP;
1197 Size steps = 501;
1198 Size samples = Null<Size>();
1199 std::map<std::string,Real> relativeTol;
1200 relativeTol["value"] = 0.02;
1201 relativeTol["delta"] = 1.0e-3;
1202 relativeTol["gamma"] = 1.0e-4;
1203 relativeTol["theta"] = 0.03;
1204 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1205}
1206
1207void EuropeanOptionTest::testTGEOBinomialEngines() {
1208
1209 BOOST_TEST_MESSAGE("Testing TGEO binomial European engines "
1210 "against analytic results...");
1211
1212 using namespace european_option_test;
1213
1214 EngineType engine = TGEO;
1215 Size steps = 251;
1216 Size samples = Null<Size>();
1217 std::map<std::string,Real> relativeTol;
1218 relativeTol["value"] = 0.002;
1219 relativeTol["delta"] = 1.0e-3;
1220 relativeTol["gamma"] = 1.0e-4;
1221 relativeTol["theta"] = 0.03;
1222 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1223}
1224
1225void EuropeanOptionTest::testTIANBinomialEngines() {
1226
1227 BOOST_TEST_MESSAGE("Testing TIAN binomial European engines "
1228 "against analytic results...");
1229
1230 using namespace european_option_test;
1231
1232 EngineType engine = TIAN;
1233 Size steps = 251;
1234 Size samples = Null<Size>();
1235 std::map<std::string,Real> relativeTol;
1236 relativeTol["value"] = 0.002;
1237 relativeTol["delta"] = 1.0e-3;
1238 relativeTol["gamma"] = 1.0e-4;
1239 relativeTol["theta"] = 0.03;
1240 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1241}
1242
1243void EuropeanOptionTest::testLRBinomialEngines() {
1244
1245 BOOST_TEST_MESSAGE("Testing LR binomial European engines "
1246 "against analytic results...");
1247
1248 using namespace european_option_test;
1249
1250 EngineType engine = LR;
1251 Size steps = 251;
1252 Size samples = Null<Size>();
1253 std::map<std::string,Real> relativeTol;
1254 relativeTol["value"] = 1.0e-6;
1255 relativeTol["delta"] = 1.0e-3;
1256 relativeTol["gamma"] = 1.0e-4;
1257 relativeTol["theta"] = 0.03;
1258 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1259}
1260
1261void EuropeanOptionTest::testJOSHIBinomialEngines() {
1262
1263 BOOST_TEST_MESSAGE("Testing Joshi binomial European engines "
1264 "against analytic results...");
1265
1266 using namespace european_option_test;
1267
1268 EngineType engine = JOSHI;
1269 Size steps = 251;
1270 Size samples = Null<Size>();
1271 std::map<std::string,Real> relativeTol;
1272 relativeTol["value"] = 1.0e-7;
1273 relativeTol["delta"] = 1.0e-3;
1274 relativeTol["gamma"] = 1.0e-4;
1275 relativeTol["theta"] = 0.03;
1276 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol,testGreeks: true);
1277}
1278
1279void EuropeanOptionTest::testFdEngines() {
1280
1281 BOOST_TEST_MESSAGE("Testing finite-difference European engines "
1282 "against analytic results...");
1283
1284 using namespace european_option_test;
1285
1286 EngineType engine = FiniteDifferences;
1287 Size timeSteps = 500;
1288 Size gridPoints = 500;
1289 std::map<std::string,Real> relativeTol;
1290 relativeTol["value"] = 1.0e-4;
1291 relativeTol["delta"] = 1.0e-6;
1292 relativeTol["gamma"] = 1.0e-6;
1293 relativeTol["theta"] = 1.0e-3;
1294 testEngineConsistency(engine,binomialSteps: timeSteps,samples: gridPoints,tolerance: relativeTol,testGreeks: true);
1295}
1296
1297void EuropeanOptionTest::testIntegralEngines() {
1298
1299 BOOST_TEST_MESSAGE("Testing integral engines against analytic results...");
1300
1301 using namespace european_option_test;
1302
1303 EngineType engine = Integral;
1304 Size timeSteps = 300;
1305 Size gridPoints = 300;
1306 std::map<std::string,Real> relativeTol;
1307 relativeTol["value"] = 0.0001;
1308 testEngineConsistency(engine,binomialSteps: timeSteps,samples: gridPoints,tolerance: relativeTol);
1309}
1310
1311void EuropeanOptionTest::testMcEngines() {
1312
1313 BOOST_TEST_MESSAGE("Testing Monte Carlo European engines "
1314 "against analytic results...");
1315
1316 using namespace european_option_test;
1317
1318 EngineType engine = PseudoMonteCarlo;
1319 Size steps = Null<Size>();
1320 Size samples = 40000;
1321 std::map<std::string,Real> relativeTol;
1322 relativeTol["value"] = 0.01;
1323 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol);
1324}
1325
1326void EuropeanOptionTest::testQmcEngines() {
1327
1328 BOOST_TEST_MESSAGE("Testing Quasi Monte Carlo European engines "
1329 "against analytic results...");
1330
1331 using namespace european_option_test;
1332
1333 EngineType engine = QuasiMonteCarlo;
1334 Size steps = Null<Size>();
1335 Size samples = 4095; // 2^12-1
1336 std::map<std::string,Real> relativeTol;
1337 relativeTol["value"] = 0.01;
1338 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol);
1339}
1340
1341void EuropeanOptionTest::testFFTEngines() {
1342
1343 BOOST_TEST_MESSAGE("Testing FFT European engines "
1344 "against analytic results...");
1345
1346 using namespace european_option_test;
1347
1348 EngineType engine = FFT;
1349 Size steps = Null<Size>();
1350 Size samples = Null<Size>();
1351 std::map<std::string,Real> relativeTol;
1352 relativeTol["value"] = 0.01;
1353 testEngineConsistency(engine,binomialSteps: steps,samples,tolerance: relativeTol);
1354}
1355
1356
1357void EuropeanOptionTest::testLocalVolatility() {
1358 BOOST_TEST_MESSAGE("Testing finite-differences with local volatility...");
1359
1360 using namespace european_option_test;
1361
1362 const Date settlementDate(5, July, 2002);
1363 Settings::instance().evaluationDate() = settlementDate;
1364
1365 const DayCounter dayCounter = Actual365Fixed();
1366 const Calendar calendar = TARGET();
1367
1368 Integer t[] = { 13, 41, 75, 165, 256, 345, 524, 703 };
1369 Rate r[] = { 0.0357,0.0349,0.0341,0.0355,0.0359,0.0368,0.0386,0.0401 };
1370
1371 std::vector<Rate> rates(1, 0.0357);
1372 std::vector<Date> dates(1, settlementDate);
1373 for (Size i = 0; i < 8; ++i) {
1374 dates.push_back(x: settlementDate + t[i]);
1375 rates.push_back(x: r[i]);
1376 }
1377 const ext::shared_ptr<YieldTermStructure> rTS(
1378 new ZeroCurve(dates, rates, dayCounter));
1379 const ext::shared_ptr<YieldTermStructure> qTS(
1380 flatRate(today: settlementDate, forward: 0.0, dc: dayCounter));
1381
1382 const ext::shared_ptr<Quote> s0(new SimpleQuote(4500.00));
1383
1384 const std::vector<Real> strikes = { 100 ,500 ,2000,3400,3600,3800,4000,4200,4400,4500,
1385 4600,4800,5000,5200,5400,5600,7500,10000,20000,30000 };
1386
1387 Volatility v[] =
1388 { 1.015873, 1.015873, 1.015873, 0.89729, 0.796493, 0.730914, 0.631335, 0.568895,
1389 0.711309, 0.711309, 0.711309, 0.641309, 0.635593, 0.583653, 0.508045, 0.463182,
1390 0.516034, 0.500534, 0.500534, 0.500534, 0.448706, 0.416661, 0.375470, 0.353442,
1391 0.516034, 0.482263, 0.447713, 0.387703, 0.355064, 0.337438, 0.316966, 0.306859,
1392 0.497587, 0.464373, 0.430764, 0.374052, 0.344336, 0.328607, 0.310619, 0.301865,
1393 0.479511, 0.446815, 0.414194, 0.361010, 0.334204, 0.320301, 0.304664, 0.297180,
1394 0.461866, 0.429645, 0.398092, 0.348638, 0.324680, 0.312512, 0.299082, 0.292785,
1395 0.444801, 0.413014, 0.382634, 0.337026, 0.315788, 0.305239, 0.293855, 0.288660,
1396 0.428604, 0.397219, 0.368109, 0.326282, 0.307555, 0.298483, 0.288972, 0.284791,
1397 0.420971, 0.389782, 0.361317, 0.321274, 0.303697, 0.295302, 0.286655, 0.282948,
1398 0.413749, 0.382754, 0.354917, 0.316532, 0.300016, 0.292251, 0.284420, 0.281164,
1399 0.400889, 0.370272, 0.343525, 0.307904, 0.293204, 0.286549, 0.280189, 0.277767,
1400 0.390685, 0.360399, 0.334344, 0.300507, 0.287149, 0.281380, 0.276271, 0.274588,
1401 0.383477, 0.353434, 0.327580, 0.294408, 0.281867, 0.276746, 0.272655, 0.271617,
1402 0.379106, 0.349214, 0.323160, 0.289618, 0.277362, 0.272641, 0.269332, 0.268846,
1403 0.377073, 0.347258, 0.320776, 0.286077, 0.273617, 0.269057, 0.266293, 0.266265,
1404 0.399925, 0.369232, 0.338895, 0.289042, 0.265509, 0.255589, 0.249308, 0.249665,
1405 0.423432, 0.406891, 0.373720, 0.314667, 0.281009, 0.263281, 0.246451, 0.242166,
1406 0.453704, 0.453704, 0.453704, 0.381255, 0.334578, 0.305527, 0.268909, 0.251367,
1407 0.517748, 0.517748, 0.517748, 0.416577, 0.364770, 0.331595, 0.287423, 0.264285 };
1408
1409 Matrix blackVolMatrix(strikes.size(), dates.size()-1);
1410 for (Size i=0; i < strikes.size(); ++i)
1411 for (Size j=1; j < dates.size(); ++j) {
1412 blackVolMatrix[i][j-1] = v[i*(dates.size()-1)+j-1];
1413 }
1414
1415 const ext::shared_ptr<BlackVarianceSurface> volTS(
1416 new BlackVarianceSurface(settlementDate, calendar,
1417 std::vector<Date>(dates.begin()+1, dates.end()),
1418 strikes, blackVolMatrix,
1419 dayCounter));
1420 volTS->setInterpolation<Bicubic>();
1421 const ext::shared_ptr<GeneralizedBlackScholesProcess> process =
1422 makeProcess(u: s0, q: qTS, r: rTS,vol: volTS);
1423
1424 const std::pair<FdmSchemeDesc, std::string> schemeDescs[]= {
1425 std::make_pair(x: FdmSchemeDesc::Douglas(), y: "Douglas"),
1426 std::make_pair(x: FdmSchemeDesc::CrankNicolson(), y: "Crank-Nicolson"),
1427 std::make_pair(x: FdmSchemeDesc::ModifiedCraigSneyd(), y: "Mod. Craig-Sneyd")
1428 };
1429
1430 for (Size i=2; i < dates.size(); i+=2) {
1431 for (Size j=3; j < strikes.size()-5; j+=5) {
1432 const Date& exDate = dates[i];
1433 const ext::shared_ptr<StrikedTypePayoff> payoff(new
1434 PlainVanillaPayoff(Option::Call, strikes[j]));
1435
1436 const ext::shared_ptr<Exercise> exercise(
1437 new EuropeanExercise(exDate));
1438
1439 EuropeanOption option(payoff, exercise);
1440 option.setPricingEngine(ext::shared_ptr<PricingEngine>(
1441 new AnalyticEuropeanEngine(process)));
1442
1443 const Real tol = 0.001;
1444 const Real expectedNPV = option.NPV();
1445 const Real expectedDelta = option.delta();
1446 const Real expectedGamma = option.gamma();
1447
1448 option.setPricingEngine(ext::shared_ptr<PricingEngine>(
1449 new FdBlackScholesVanillaEngine(process, 200, 400)));
1450
1451 Real calculatedNPV = option.NPV();
1452 const Real calculatedDelta = option.delta();
1453 const Real calculatedGamma = option.gamma();
1454
1455 // check implied pricing first
1456 if (std::fabs(x: expectedNPV - calculatedNPV) > tol*expectedNPV) {
1457 BOOST_FAIL("Failed to reproduce option price for "
1458 << "\n strike: " << payoff->strike()
1459 << "\n maturity: " << exDate
1460 << "\n calculated: " << calculatedNPV
1461 << "\n expected: " << expectedNPV);
1462 }
1463 if (std::fabs(x: expectedDelta - calculatedDelta) >tol*expectedDelta) {
1464 BOOST_FAIL("Failed to reproduce option delta for "
1465 << "\n strike: " << payoff->strike()
1466 << "\n maturity: " << exDate
1467 << "\n calculated: " << calculatedDelta
1468 << "\n expected: " << expectedDelta);
1469 }
1470 if (std::fabs(x: expectedGamma - calculatedGamma) >tol*expectedGamma) {
1471 BOOST_FAIL("Failed to reproduce option gamma for "
1472 << "\n strike: " << payoff->strike()
1473 << "\n maturity: " << exDate
1474 << "\n calculated: " << calculatedGamma
1475 << "\n expected: " << expectedGamma);
1476 }
1477
1478 // check local vol pricing
1479 // delta/gamma are not the same by definition (model implied greeks)
1480 for (const auto& schemeDesc : schemeDescs) {
1481 option.setPricingEngine(ext::make_shared<FdBlackScholesVanillaEngine>(
1482 args: process, args: 25, args: 100, args: 0, args: schemeDesc.first, args: true, args: 0.35));
1483
1484 calculatedNPV = option.NPV();
1485 if (std::fabs(x: expectedNPV - calculatedNPV) > tol*expectedNPV) {
1486 BOOST_FAIL("Failed to reproduce local vol option price for "
1487 << "\n strike: " << payoff->strike() << "\n maturity: "
1488 << exDate << "\n calculated: " << calculatedNPV
1489 << "\n expected: " << expectedNPV
1490 << "\n scheme: " << schemeDesc.second);
1491 }
1492 }
1493 }
1494 }
1495}
1496
1497void EuropeanOptionTest::testAnalyticEngineDiscountCurve() {
1498 BOOST_TEST_MESSAGE(
1499 "Testing separate discount curve for analytic European engine...");
1500
1501 DayCounter dc = Actual360();
1502 Date today = Date::todaysDate();
1503
1504 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(1000.0));
1505 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.01));
1506 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
1507 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.015));
1508 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
1509 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.02));
1510 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
1511 ext::shared_ptr<SimpleQuote> discRate(new SimpleQuote(0.015));
1512 ext::shared_ptr<YieldTermStructure> discTS = flatRate(today, forward: discRate, dc);
1513
1514 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
1515 BlackScholesMertonProcess(Handle<Quote>(spot),
1516 Handle<YieldTermStructure>(qTS),
1517 Handle<YieldTermStructure>(rTS),
1518 Handle<BlackVolTermStructure>(volTS)));
1519 ext::shared_ptr<PricingEngine> engineSingleCurve(
1520 new AnalyticEuropeanEngine(stochProcess));
1521 ext::shared_ptr<PricingEngine> engineMultiCurve(
1522 new AnalyticEuropeanEngine(stochProcess,
1523 Handle<YieldTermStructure>(discTS)));
1524
1525 ext::shared_ptr<StrikedTypePayoff> payoff(new
1526 PlainVanillaPayoff(Option::Call, 1025.0));
1527 Date exDate = today + Period(1, Years);
1528 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
1529 EuropeanOption option(payoff, exercise);
1530 Real npvSingleCurve, npvMultiCurve;
1531 option.setPricingEngine(engineSingleCurve);
1532 npvSingleCurve = option.NPV();
1533 option.setPricingEngine(engineMultiCurve);
1534 npvMultiCurve = option.NPV();
1535 // check that NPV is the same regardless of engine interface
1536 BOOST_CHECK_EQUAL(npvSingleCurve, npvMultiCurve);
1537 // check that NPV changes if discount rate is changed
1538 discRate->setValue(0.023);
1539 npvMultiCurve = option.NPV();
1540 BOOST_CHECK_NE(npvSingleCurve, npvMultiCurve);
1541}
1542
1543
1544void EuropeanOptionTest::testPDESchemes() {
1545 BOOST_TEST_MESSAGE("Testing different PDE schemes to solve Black-Scholes PDEs...");
1546
1547 const DayCounter dc = Actual365Fixed();
1548 const Date today = Date(18, February, 2018);
1549
1550 Settings::instance().evaluationDate() = today;
1551
1552 const Handle<Quote> spot(ext::make_shared<SimpleQuote>(args: 100.0));
1553 const Handle<YieldTermStructure> qTS(flatRate(today, forward: 0.06, dc));
1554 const Handle<YieldTermStructure> rTS(flatRate(today, forward: 0.10, dc));
1555 const Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: 0.35, dc));
1556
1557 const Date maturity = today + Period(6, Months);
1558
1559 const ext::shared_ptr<BlackScholesMertonProcess> process =
1560 ext::make_shared<BlackScholesMertonProcess>(
1561 args: spot, args: qTS, args: rTS, args: volTS);
1562
1563 const ext::shared_ptr<PricingEngine> analytic =
1564 ext::make_shared<AnalyticEuropeanEngine>(args: process);
1565
1566 // Crank-Nicolson and Douglas scheme are the same in one dimension
1567 const ext::shared_ptr<PricingEngine> douglas =
1568 ext::make_shared<FdBlackScholesVanillaEngine>(
1569 args: process, args: 15, args: 100, args: 0, args: FdmSchemeDesc::Douglas());
1570
1571 const ext::shared_ptr<PricingEngine> crankNicolson =
1572 ext::make_shared<FdBlackScholesVanillaEngine>(
1573 args: process, args: 15, args: 100, args: 0, args: FdmSchemeDesc::CrankNicolson());
1574
1575 const ext::shared_ptr<PricingEngine> implicitEuler =
1576 ext::make_shared<FdBlackScholesVanillaEngine>(
1577 args: process, args: 500, args: 100, args: 0, args: FdmSchemeDesc::ImplicitEuler());
1578
1579 const ext::shared_ptr<PricingEngine> explicitEuler =
1580 ext::make_shared<FdBlackScholesVanillaEngine>(
1581 args: process, args: 1000, args: 100, args: 0, args: FdmSchemeDesc::ExplicitEuler());
1582
1583 const ext::shared_ptr<PricingEngine> methodOfLines =
1584 ext::make_shared<FdBlackScholesVanillaEngine>(
1585 args: process, args: 1, args: 100, args: 0, args: FdmSchemeDesc::MethodOfLines());
1586
1587 const ext::shared_ptr<PricingEngine> hundsdorfer =
1588 ext::make_shared<FdBlackScholesVanillaEngine>(
1589 args: process, args: 10, args: 100, args: 0, args: FdmSchemeDesc::Hundsdorfer());
1590
1591 const ext::shared_ptr<PricingEngine> craigSneyd =
1592 ext::make_shared<FdBlackScholesVanillaEngine>(
1593 args: process, args: 10, args: 100, args: 0, args: FdmSchemeDesc::CraigSneyd());
1594
1595 const ext::shared_ptr<PricingEngine> modCraigSneyd =
1596 ext::make_shared<FdBlackScholesVanillaEngine>(
1597 args: process, args: 15, args: 100, args: 0, args: FdmSchemeDesc::ModifiedCraigSneyd());
1598
1599 const ext::shared_ptr<PricingEngine> trBDF2 =
1600 ext::make_shared<FdBlackScholesVanillaEngine>(
1601 args: process, args: 15, args: 100, args: 0, args: FdmSchemeDesc::TrBDF2());
1602
1603
1604 const std::pair<ext::shared_ptr<PricingEngine>, std::string> engines[]= {
1605 std::make_pair(x: douglas, y: "Douglas"),
1606 std::make_pair(x: crankNicolson, y: "Crank-Nicolson"),
1607 std::make_pair(x: implicitEuler, y: "Implicit-Euler"),
1608 std::make_pair(x: explicitEuler, y: "Explicit-Euler"),
1609 std::make_pair(x: methodOfLines, y: "Method-of-Lines"),
1610 std::make_pair(x: hundsdorfer, y: "Hundsdorfer"),
1611 std::make_pair(x: craigSneyd, y: "Craig-Sneyd"),
1612 std::make_pair(x: modCraigSneyd, y: "Modified Craig-Sneyd"),
1613 std::make_pair(x: trBDF2, y: "TR-BDF2")
1614 };
1615
1616 const ext::shared_ptr<PlainVanillaPayoff> payoff(
1617 ext::make_shared<PlainVanillaPayoff>(args: Option::Put, args: spot->value()));
1618
1619 const ext::shared_ptr<Exercise> exercise(
1620 ext::make_shared<EuropeanExercise>(args: maturity));
1621
1622 VanillaOption option(payoff, exercise);
1623
1624 option.setPricingEngine(analytic);
1625 const Real expected = option.NPV();
1626
1627 const Real tol = 0.006;
1628 for (const auto& engine : engines) {
1629 option.setPricingEngine(engine.first);
1630 const Real calculated = option.NPV();
1631
1632 const Real diff = std::fabs(x: expected - calculated);
1633
1634 if (diff > tol) {
1635 BOOST_FAIL("Failed to reproduce European option values with the "
1636 << engine.second << " PDE scheme"
1637 << "\n calculated: " << calculated << "\n expected: " << expected
1638 << "\n difference: " << diff << "\n tolerance: " << tol);
1639 }
1640 }
1641}
1642
1643void EuropeanOptionTest::testFdEngineWithNonConstantParameters() {
1644 BOOST_TEST_MESSAGE("Testing finite-difference European engine "
1645 "with non-constant parameters...");
1646
1647 Real u = 190.0;
1648 Volatility v = 0.20;
1649
1650 DayCounter dc = Actual360();
1651 Date today = Settings::instance().evaluationDate();
1652
1653 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(u));
1654 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today,volatility: v,dc);
1655
1656 std::vector<Date> dates(5);
1657 std::vector<Rate> rates(5);
1658 dates[0] = today; rates[0] = 0.0;
1659 dates[1] = today+90; rates[1] = 0.001;
1660 dates[2] = today+180; rates[2] = 0.002;
1661 dates[3] = today+270; rates[3] = 0.005;
1662 dates[4] = today+360; rates[4] = 0.01;
1663 ext::shared_ptr<YieldTermStructure> rTS =
1664 ext::make_shared<ForwardCurve>(args&: dates, args&: rates, args&: dc);
1665 Rate r = rTS->zeroRate(d: dates[4], resultDayCounter: dc, comp: Continuous);
1666
1667 ext::shared_ptr<BlackScholesProcess> process =
1668 ext::make_shared<BlackScholesProcess>(args: Handle<Quote>(spot),
1669 args: Handle<YieldTermStructure>(rTS),
1670 args: Handle<BlackVolTermStructure>(volTS));
1671
1672 ext::shared_ptr<Exercise> exercise =
1673 ext::make_shared<EuropeanExercise>(args: today + 360);
1674 ext::shared_ptr<StrikedTypePayoff> payoff =
1675 ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args: 190.0);
1676
1677 EuropeanOption option(payoff, exercise);
1678
1679 option.setPricingEngine(ext::make_shared<AnalyticEuropeanEngine>(args&: process));
1680 Real expected = option.NPV();
1681
1682 Size timeSteps = 200;
1683 Size gridPoints = 201;
1684 option.setPricingEngine(ext::make_shared<FdBlackScholesVanillaEngine>(
1685 args&: process, args&: timeSteps, args&: gridPoints));
1686 Real calculated = option.NPV();
1687
1688 Real tolerance = 0.01;
1689 Real error = std::fabs(x: expected-calculated);
1690 if (error > tolerance) {
1691 REPORT_FAILURE("value", payoff, exercise,
1692 u, 0.0, r, today, v,
1693 expected, calculated,
1694 error, tolerance);
1695 }
1696}
1697
1698void EuropeanOptionTest::testDouglasVsCrankNicolson() {
1699 BOOST_TEST_MESSAGE("Testing Douglas vs Crank-Nicolson scheme "
1700 "for finite-difference European PDE engines...");
1701
1702 const DayCounter dc = Actual365Fixed();
1703 const Date today = Date(5, October, 2018);
1704
1705 Settings::instance().evaluationDate() = today;
1706
1707 const Handle<Quote> spot(ext::make_shared<SimpleQuote>(args: 100.0));
1708 const Handle<YieldTermStructure> qTS(flatRate(today, forward: 0.02, dc));
1709 const Handle<YieldTermStructure> rTS(flatRate(today, forward: 0.075, dc));
1710 const Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: 0.25, dc));
1711
1712 const ext::shared_ptr<BlackScholesMertonProcess> process =
1713 ext::make_shared<BlackScholesMertonProcess>(
1714 args: spot, args: qTS, args: rTS, args: volTS);
1715
1716 VanillaOption option(
1717 ext::make_shared<PlainVanillaPayoff>(args: Option::Put, args: spot->value()+2),
1718 ext::make_shared<EuropeanExercise>(args: today + Period(6, Months)));
1719
1720 option.setPricingEngine(
1721 ext::make_shared<AnalyticEuropeanEngine>(args: process));
1722
1723 const Real npv = option.NPV();
1724 const Real schemeTol = 1e-12;
1725 const Real npvTol = 1e-2;
1726
1727 for (Real theta = 0.2; theta < 0.81; theta+=0.1) {
1728 option.setPricingEngine(
1729 ext::make_shared<FdBlackScholesVanillaEngine>(
1730 args: process, args: 500, args: 100, args: 0,
1731 args: FdmSchemeDesc(FdmSchemeDesc::CrankNicolsonType, theta, 0.0)));
1732 const Real crankNicolsonNPV = option.NPV();
1733
1734 const Real npvDiff = std::fabs(x: crankNicolsonNPV - npv);
1735 if (npvDiff > npvTol) {
1736 BOOST_FAIL("Failed to reproduce european option values "
1737 "with the Crank-Nicolson PDE scheme "
1738 << "\n Analytic NPV: " << npv
1739 << "\n Crank-Nicolson NPV: " << crankNicolsonNPV
1740 << "\n theta: " << theta
1741 << "\n difference: " << npvDiff
1742 << "\n tolerance: " << npvTol);
1743 }
1744
1745 option.setPricingEngine(
1746 ext::make_shared<FdBlackScholesVanillaEngine>(
1747 args: process, args: 500, args: 100, args: 0,
1748 args: FdmSchemeDesc(FdmSchemeDesc::DouglasType, theta, 0.0)));
1749 const Real douglasNPV = option.NPV();
1750
1751 const Real schemeDiff = std::fabs(x: crankNicolsonNPV - douglasNPV);
1752
1753 if (schemeDiff > schemeTol) {
1754 BOOST_FAIL("Failed to reproduce Douglas scheme option values "
1755 "with the Crank-Nicolson PDE scheme "
1756 << "\n Dougles NPV: " << douglasNPV
1757 << "\n Crank-Nicolson NPV: " << crankNicolsonNPV
1758 << "\n difference: " << schemeDiff
1759 << "\n tolerance: " << schemeTol);
1760 }
1761 }
1762}
1763
1764void EuropeanOptionTest::testVanillaAndDividendEngine() {
1765 BOOST_TEST_MESSAGE("Testing the use of a single engine for vanilla and dividend options...");
1766
1767 auto today = Date(1, January, 2023);
1768 Settings::instance().evaluationDate() = today;
1769
1770 auto u = Handle<Quote>(ext::make_shared<SimpleQuote>(args: 100.0));
1771 auto r = Handle<YieldTermStructure>(ext::make_shared<FlatForward>(args&: today, args: 0.01, args: Actual360()));
1772 auto sigma = Handle<BlackVolTermStructure>(
1773 ext::make_shared<BlackConstantVol>(args&: today, args: TARGET(), args: 0.20, args: Actual360()));
1774 auto process = ext::make_shared<BlackScholesProcess>(args&: u, args&: r, args&: sigma);
1775
1776 auto engine = ext::make_shared<FdBlackScholesVanillaEngine>(args&: process);
1777
1778 auto payoff = ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args: 100.0);
1779
1780 auto option1 =
1781 VanillaOption(payoff, ext::make_shared<AmericanExercise>(args&: today, args: Date(1, June, 2023)));
1782 QL_DEPRECATED_DISABLE_WARNING
1783 auto option2 = DividendVanillaOption(
1784 payoff, ext::make_shared<AmericanExercise>(args&: today, args: Date(1, June, 2023)),
1785 {Date(1, February, 2023)}, {1.0});
1786 QL_DEPRECATED_ENABLE_WARNING
1787
1788 option1.setPricingEngine(engine);
1789 option2.setPricingEngine(engine);
1790
1791 auto npv_before = option1.NPV();
1792 option2.NPV();
1793
1794 option1.recalculate();
1795 auto npv_after = option1.NPV();
1796
1797 if (npv_after != npv_before) {
1798 BOOST_FAIL("Failed to price vanilla option correctly "
1799 "after using the engine on a dividend option: "
1800 << "\n before usage: " << npv_before
1801 << "\n after usage: " << npv_after);
1802 }
1803}
1804
1805test_suite* EuropeanOptionTest::suite() {
1806 auto* suite = BOOST_TEST_SUITE("European option tests");
1807 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testValues));
1808 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testGreekValues));
1809 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testGreeks));
1810 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testImpliedVol));
1811 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testImpliedVolWithDividends));
1812 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testImpliedVolContainment));
1813 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testJRBinomialEngines));
1814 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testCRRBinomialEngines));
1815 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testEQPBinomialEngines));
1816 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testTGEOBinomialEngines));
1817 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testTIANBinomialEngines));
1818 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testLRBinomialEngines));
1819 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testJOSHIBinomialEngines));
1820 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testFdEngines));
1821 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testIntegralEngines));
1822 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testMcEngines));
1823 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testQmcEngines));
1824 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testLocalVolatility));
1825 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testAnalyticEngineDiscountCurve));
1826 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testPDESchemes));
1827 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testFdEngineWithNonConstantParameters));
1828 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testDouglasVsCrankNicolson));
1829 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testVanillaAndDividendEngine));
1830
1831 return suite;
1832}
1833
1834test_suite* EuropeanOptionTest::experimental() {
1835 auto* suite = BOOST_TEST_SUITE("European option experimental tests");
1836 suite->add(QUANTLIB_TEST_CASE(&EuropeanOptionTest::testFFTEngines));
1837 return suite;
1838}
1839

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