[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) 2005, 2007, 2008, 2017 StatPro Italia srl
6 Copyright (C) 2009, 2011 Master IMAFA - Polytech'Nice Sophia - Université de Nice Sophia Antipolis
7 Copyright (C) 2014 Bernd Lewerenz
8 Copyright (C) 2020, 2021 Jack Gillett
9 Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ)
10
11 This file is part of QuantLib, a free-software/open-source library
12 for financial quantitative analysts and developers - http://quantlib.org/
13
14 QuantLib is free software: you can redistribute it and/or modify it
15 under the terms of the QuantLib license. You should have received a
16 copy of the license along with this program; if not, please email
17 <quantlib-dev@lists.sf.net>. The license is also available online at
18 <http://quantlib.org/license.shtml>.
19
20 This program is distributed in the hope that it will be useful, but WITHOUT
21 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22 FOR A PARTICULAR PURPOSE. See the license for more details.
23*/
24
25#include "asianoptions.hpp"
26#include "utilities.hpp"
27#include <ql/time/daycounters/actual360.hpp>
28#include <ql/time/daycounters/actual365fixed.hpp>
29#include <ql/instruments/asianoption.hpp>
30#include <ql/pricingengines/asian/analytic_discr_geom_av_price.hpp>
31#include <ql/pricingengines/asian/analytic_discr_geom_av_strike.hpp>
32#include <ql/pricingengines/asian/analytic_cont_geom_av_price.hpp>
33#include <ql/pricingengines/asian/mc_discr_geom_av_price.hpp>
34#include <ql/pricingengines/asian/mc_discr_geom_av_price_heston.hpp>
35#include <ql/pricingengines/asian/mc_discr_arith_av_price.hpp>
36#include <ql/pricingengines/asian/mc_discr_arith_av_price_heston.hpp>
37#include <ql/pricingengines/asian/mc_discr_arith_av_strike.hpp>
38#include <ql/pricingengines/asian/fdblackscholesasianengine.hpp>
39#include <ql/experimental/exoticoptions/continuousarithmeticasianlevyengine.hpp>
40#include <ql/experimental/exoticoptions/continuousarithmeticasianvecerengine.hpp>
41#include <ql/experimental/asian/analytic_cont_geom_av_price_heston.hpp>
42#include <ql/experimental/asian/analytic_discr_geom_av_price_heston.hpp>
43#include <ql/pricingengines/asian/turnbullwakemanasianengine.hpp>
44#include <ql/termstructures/yield/flatforward.hpp>
45#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
46#include <ql/utilities/dataformatters.hpp>
47#include <map>
48
49using namespace QuantLib;
50using namespace boost::unit_test_framework;
51
52#undef REPORT_FAILURE
53#define REPORT_FAILURE(greekName, averageType, \
54 runningAccumulator, pastFixings, \
55 fixingDates, payoff, exercise, s, q, r, today, v, \
56 expected, calculated, tolerance) \
57 BOOST_ERROR( \
58 exerciseTypeToString(exercise) \
59 << " Asian option with " \
60 << averageTypeToString(averageType) << " and " \
61 << payoffTypeToString(payoff) << " payoff:\n" \
62 << " running variable: " \
63 << io::checknull(runningAccumulator) << "\n" \
64 << " past fixings: " \
65 << io::checknull(pastFixings) << "\n" \
66 << " future fixings: " << fixingDates.size() << "\n" \
67 << " underlying value: " << s << "\n" \
68 << " strike: " << payoff->strike() << "\n" \
69 << " dividend yield: " << io::rate(q) << "\n" \
70 << " risk-free rate: " << io::rate(r) << "\n" \
71 << " reference date: " << today << "\n" \
72 << " maturity: " << exercise->lastDate() << "\n" \
73 << " volatility: " << io::volatility(v) << "\n\n" \
74 << " expected " << greekName << ": " << expected << "\n" \
75 << " calculated " << greekName << ": " << calculated << "\n"\
76 << " error: " << std::fabs(expected-calculated) \
77 << "\n" \
78 << " tolerance: " << tolerance);
79
80namespace {
81
82 std::string averageTypeToString(Average::Type averageType) {
83
84 if (averageType == Average::Geometric)
85 return "Geometric Averaging";
86 else if (averageType == Average::Arithmetic)
87 return "Arithmetic Averaging";
88 else
89 QL_FAIL("unknown averaging");
90 }
91
92}
93
94
95void AsianOptionTest::testAnalyticContinuousGeometricAveragePrice() {
96
97 BOOST_TEST_MESSAGE(
98 "Testing analytic continuous geometric average-price Asians...");
99
100 // data from "Option Pricing Formulas", Haug, pag.96-97
101
102 DayCounter dc = Actual360();
103 Date today = Settings::instance().evaluationDate();
104
105 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(80.0));
106 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(-0.03));
107 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
108 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
109 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
110 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
111 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
112
113 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
114 BlackScholesMertonProcess(Handle<Quote>(spot),
115 Handle<YieldTermStructure>(qTS),
116 Handle<YieldTermStructure>(rTS),
117 Handle<BlackVolTermStructure>(volTS)));
118
119 ext::shared_ptr<PricingEngine> engine(new
120 AnalyticContinuousGeometricAveragePriceAsianEngine(stochProcess));
121
122 Average::Type averageType = Average::Geometric;
123 Option::Type type = Option::Put;
124 Real strike = 85.0;
125 Date exerciseDate = today + 90;
126
127 Size pastFixings = Null<Size>();
128 Real runningAccumulator = Null<Real>();
129
130 ext::shared_ptr<StrikedTypePayoff> payoff(
131 new PlainVanillaPayoff(type, strike));
132
133 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
134
135 ContinuousAveragingAsianOption option(averageType, payoff, exercise);
136 option.setPricingEngine(engine);
137
138 Real calculated = option.NPV();
139 Real expected = 4.6922;
140 Real tolerance = 1.0e-4;
141 if (std::fabs(x: calculated-expected) > tolerance) {
142 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings,
143 std::vector<Date>(), payoff, exercise, spot->value(),
144 qRate->value(), rRate->value(), today,
145 vol->value(), expected, calculated, tolerance);
146 }
147
148 // trying to approximate the continuous version with the discrete version
149 runningAccumulator = 1.0;
150 pastFixings = 0;
151 std::vector<Date> fixingDates(exerciseDate-today+1);
152 for (Size i=0; i<fixingDates.size(); i++) {
153 fixingDates[i] = today + i;
154 }
155 ext::shared_ptr<PricingEngine> engine2(new
156 AnalyticDiscreteGeometricAveragePriceAsianEngine(stochProcess));
157 DiscreteAveragingAsianOption option2(averageType,
158 runningAccumulator, pastFixings,
159 fixingDates,
160 payoff,
161 exercise);
162 option2.setPricingEngine(engine2);
163
164 calculated = option2.NPV();
165 tolerance = 3.0e-3;
166 if (std::fabs(x: calculated-expected) > tolerance) {
167 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings,
168 fixingDates, payoff, exercise, spot->value(),
169 qRate->value(), rRate->value(), today,
170 vol->value(), expected, calculated, tolerance);
171 }
172
173}
174
175
176void AsianOptionTest::testAnalyticContinuousGeometricAveragePriceGreeks() {
177
178 BOOST_TEST_MESSAGE(
179 "Testing analytic continuous geometric average-price Asian greeks...");
180
181 std::map<std::string,Real> calculated, expected, tolerance;
182 tolerance["delta"] = 1.0e-5;
183 tolerance["gamma"] = 1.0e-5;
184 tolerance["theta"] = 1.0e-5;
185 tolerance["rho"] = 1.0e-5;
186 tolerance["divRho"] = 1.0e-5;
187 tolerance["vega"] = 1.0e-5;
188
189 Option::Type types[] = { Option::Call, Option::Put };
190 Real underlyings[] = { 100.0 };
191 Real strikes[] = { 90.0, 100.0, 110.0 };
192 Rate qRates[] = { 0.04, 0.05, 0.06 };
193 Rate rRates[] = { 0.01, 0.05, 0.15 };
194 Integer lengths[] = { 1, 2 };
195 Volatility vols[] = { 0.11, 0.50, 1.20 };
196
197 DayCounter dc = Actual360();
198 Date today = Settings::instance().evaluationDate();
199 Settings::instance().evaluationDate() = today;
200
201 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
202 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
203 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
204 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
205 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
206 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
207 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
208
209 ext::shared_ptr<BlackScholesMertonProcess> process(
210 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
211
212 for (auto& type : types) {
213 for (Real strike : strikes) {
214 for (int length : lengths) {
215
216 ext::shared_ptr<EuropeanExercise> maturity(
217 new EuropeanExercise(today + length * Years));
218
219 ext::shared_ptr<PlainVanillaPayoff> payoff(new PlainVanillaPayoff(type, strike));
220
221 ext::shared_ptr<PricingEngine> engine(
222 new AnalyticContinuousGeometricAveragePriceAsianEngine(process));
223
224 ContinuousAveragingAsianOption option(Average::Geometric, payoff, maturity);
225 option.setPricingEngine(engine);
226
227 Size pastFixings = Null<Size>();
228 Real runningAverage = Null<Real>();
229
230 for (Real u : underlyings) {
231 for (Real m : qRates) {
232 for (Real n : rRates) {
233 for (Real v : vols) {
234
235 Rate q = m, r = n;
236 spot->setValue(u);
237 qRate->setValue(q);
238 rRate->setValue(r);
239 vol->setValue(v);
240
241 Real value = option.NPV();
242 calculated["delta"] = option.delta();
243 calculated["gamma"] = option.gamma();
244 calculated["theta"] = option.theta();
245 calculated["rho"] = option.rho();
246 calculated["divRho"] = option.dividendRho();
247 calculated["vega"] = option.vega();
248
249 if (value > spot->value() * 1.0e-5) {
250 // perturb spot and get delta and gamma
251 Real du = u * 1.0e-4;
252 spot->setValue(u + du);
253 Real value_p = option.NPV(), delta_p = option.delta();
254 spot->setValue(u - du);
255 Real value_m = option.NPV(), delta_m = option.delta();
256 spot->setValue(u);
257 expected["delta"] = (value_p - value_m) / (2 * du);
258 expected["gamma"] = (delta_p - delta_m) / (2 * du);
259
260 // perturb rates and get rho and dividend rho
261 Spread dr = r * 1.0e-4;
262 rRate->setValue(r + dr);
263 value_p = option.NPV();
264 rRate->setValue(r - dr);
265 value_m = option.NPV();
266 rRate->setValue(r);
267 expected["rho"] = (value_p - value_m) / (2 * dr);
268
269 Spread dq = q * 1.0e-4;
270 qRate->setValue(q + dq);
271 value_p = option.NPV();
272 qRate->setValue(q - dq);
273 value_m = option.NPV();
274 qRate->setValue(q);
275 expected["divRho"] = (value_p - value_m) / (2 * dq);
276
277 // perturb volatility and get vega
278 Volatility dv = v * 1.0e-4;
279 vol->setValue(v + dv);
280 value_p = option.NPV();
281 vol->setValue(v - dv);
282 value_m = option.NPV();
283 vol->setValue(v);
284 expected["vega"] = (value_p - value_m) / (2 * dv);
285
286 // perturb date and get theta
287 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
288 Settings::instance().evaluationDate() = today - 1;
289 value_m = option.NPV();
290 Settings::instance().evaluationDate() = today + 1;
291 value_p = option.NPV();
292 Settings::instance().evaluationDate() = today;
293 expected["theta"] = (value_p - value_m) / dT;
294
295 // compare
296 std::map<std::string, Real>::iterator it;
297 for (it = calculated.begin(); it != calculated.end(); ++it) {
298 std::string greek = it->first;
299 Real expct = expected[greek], calcl = calculated[greek],
300 tol = tolerance[greek];
301 Real error = relativeError(x1: expct, x2: calcl, reference: u);
302 if (error > tol) {
303 REPORT_FAILURE(greek, Average::Geometric,
304 runningAverage, pastFixings,
305 std::vector<Date>(), payoff, maturity, u,
306 q, r, today, v, expct, calcl, tol);
307 }
308 }
309 }
310 }
311 }
312 }
313 }
314 }
315 }
316 }
317}
318
319
320void AsianOptionTest::testAnalyticDiscreteGeometricAveragePrice() {
321
322 BOOST_TEST_MESSAGE(
323 "Testing analytic discrete geometric average-price Asians...");
324
325 // data from "Implementing Derivatives Model",
326 // Clewlow, Strickland, p.118-123
327
328 DayCounter dc = Actual360();
329 Date today = Settings::instance().evaluationDate();
330
331 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
332 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
333 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
334 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
335 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
336 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
337 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
338
339 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
340 BlackScholesMertonProcess(Handle<Quote>(spot),
341 Handle<YieldTermStructure>(qTS),
342 Handle<YieldTermStructure>(rTS),
343 Handle<BlackVolTermStructure>(volTS)));
344
345 ext::shared_ptr<PricingEngine> engine(
346 new AnalyticDiscreteGeometricAveragePriceAsianEngine(stochProcess));
347
348 Average::Type averageType = Average::Geometric;
349 Real runningAccumulator = 1.0;
350 Size pastFixings = 0;
351 Size futureFixings = 10;
352 Option::Type type = Option::Call;
353 Real strike = 100.0;
354 ext::shared_ptr<StrikedTypePayoff> payoff(
355 new PlainVanillaPayoff(type, strike));
356
357 Date exerciseDate = today + 360;
358 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
359
360 std::vector<Date> fixingDates(futureFixings);
361 auto dt = (Integer)std::lround(x: 360.0 / futureFixings);
362 fixingDates[0] = today + dt;
363 for (Size j=1; j<futureFixings; j++)
364 fixingDates[j] = fixingDates[j-1] + dt;
365
366 DiscreteAveragingAsianOption option(averageType, runningAccumulator,
367 pastFixings, fixingDates,
368 payoff, exercise);
369 option.setPricingEngine(engine);
370
371 Real calculated = option.NPV();
372 Real expected = 5.3425606635;
373 Real tolerance = 1e-10;
374 if (std::fabs(x: calculated-expected) > tolerance) {
375 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings,
376 fixingDates, payoff, exercise, spot->value(),
377 qRate->value(), rRate->value(), today,
378 vol->value(), expected, calculated, tolerance);
379 }
380}
381
382void AsianOptionTest::testAnalyticDiscreteGeometricAverageStrike() {
383
384 BOOST_TEST_MESSAGE(
385 "Testing analytic discrete geometric average-strike Asians...");
386
387 DayCounter dc = Actual360();
388 Date today = Settings::instance().evaluationDate();
389
390 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
391 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
392 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
393 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
394 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
395 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
396 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
397
398 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
399 BlackScholesMertonProcess(Handle<Quote>(spot),
400 Handle<YieldTermStructure>(qTS),
401 Handle<YieldTermStructure>(rTS),
402 Handle<BlackVolTermStructure>(volTS)));
403
404 ext::shared_ptr<PricingEngine> engine(
405 new AnalyticDiscreteGeometricAverageStrikeAsianEngine(stochProcess));
406
407 Average::Type averageType = Average::Geometric;
408 Real runningAccumulator = 1.0;
409 Size pastFixings = 0;
410 Size futureFixings = 10;
411 Option::Type type = Option::Call;
412 Real strike = 100.0;
413 ext::shared_ptr<StrikedTypePayoff> payoff(
414 new PlainVanillaPayoff(type, strike));
415
416 Date exerciseDate = today + 360;
417 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
418
419 std::vector<Date> fixingDates(futureFixings);
420 auto dt = (Integer)std::lround(x: 360.0 / futureFixings);
421 fixingDates[0] = today + dt;
422 for (Size j=1; j<futureFixings; j++)
423 fixingDates[j] = fixingDates[j-1] + dt;
424
425 DiscreteAveragingAsianOption option(averageType, runningAccumulator,
426 pastFixings, fixingDates,
427 payoff, exercise);
428 option.setPricingEngine(engine);
429
430 Real calculated = option.NPV();
431 Real expected = 4.97109;
432 Real tolerance = 1e-5;
433 if (std::fabs(x: calculated-expected) > tolerance) {
434 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings,
435 fixingDates, payoff, exercise, spot->value(),
436 qRate->value(), rRate->value(), today,
437 vol->value(), expected, calculated, tolerance);
438 }
439}
440
441
442void AsianOptionTest::testMCDiscreteGeometricAveragePrice() {
443
444 BOOST_TEST_MESSAGE(
445 "Testing Monte Carlo discrete geometric average-price Asians...");
446
447 // data from "Implementing Derivatives Model",
448 // Clewlow, Strickland, p.118-123
449
450 DayCounter dc = Actual360();
451 Date today = Settings::instance().evaluationDate();
452
453 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
454 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
455 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
456 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
457 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
458 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
459 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
460
461 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
462 BlackScholesMertonProcess(Handle<Quote>(spot),
463 Handle<YieldTermStructure>(qTS),
464 Handle<YieldTermStructure>(rTS),
465 Handle<BlackVolTermStructure>(volTS)));
466
467 Real tolerance = 4.0e-3;
468
469 ext::shared_ptr<PricingEngine> engine =
470 MakeMCDiscreteGeometricAPEngine<LowDiscrepancy>(stochProcess)
471 .withSamples(samples: 8191);
472
473 Average::Type averageType = Average::Geometric;
474 Real runningAccumulator = 1.0;
475 Size pastFixings = 0;
476 Size futureFixings = 10;
477 Option::Type type = Option::Call;
478 Real strike = 100.0;
479 ext::shared_ptr<StrikedTypePayoff> payoff(
480 new PlainVanillaPayoff(type, strike));
481
482 Date exerciseDate = today + 360;
483 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
484
485 std::vector<Date> fixingDates(futureFixings);
486 auto dt = (Integer)std::lround(x: 360.0 / futureFixings);
487 fixingDates[0] = today + dt;
488 for (Size j=1; j<futureFixings; j++)
489 fixingDates[j] = fixingDates[j-1] + dt;
490
491 DiscreteAveragingAsianOption option(averageType, runningAccumulator,
492 pastFixings, fixingDates,
493 payoff, exercise);
494 option.setPricingEngine(engine);
495
496 Real calculated = option.NPV();
497
498 ext::shared_ptr<PricingEngine> engine2(
499 new AnalyticDiscreteGeometricAveragePriceAsianEngine(stochProcess));
500 option.setPricingEngine(engine2);
501 Real expected = option.NPV();
502
503 if (std::fabs(x: calculated-expected) > tolerance) {
504 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings,
505 fixingDates, payoff, exercise, spot->value(),
506 qRate->value(), rRate->value(), today,
507 vol->value(), expected, calculated, tolerance);
508 }
509}
510
511
512void testDiscreteGeometricAveragePriceHeston(const ext::shared_ptr<PricingEngine>& engine,
513 const Real tol[]) {
514
515 // data from "A Recursive Method for Discretely Monitored Geometric Asian Option
516 // Prices", Kim, Kim, Kim & Wee, Bull. Korean Math. Soc. 53, 733-749, 2016
517 int days[] = {
518 30, 91, 182, 365, 730, 1095,
519 30, 91, 182, 365, 730, 1095,
520 30, 91, 182, 365, 730, 1095
521 };
522 Real strikes[] = {
523 90, 90, 90, 90, 90, 90,
524 100, 100, 100, 100, 100, 100,
525 110, 110, 110, 110, 110, 110
526 };
527
528 // Prices from Tables 1, 2 and 3
529 Real prices[] = {
530 10.2732, 10.9554, 11.9916, 13.6950, 16.1773, 18.0146,
531 2.4389, 3.7881, 5.2132, 7.2243, 9.9948, 12.0639,
532 0.1012, 0.5949, 1.4444, 2.9479, 5.3531, 7.3315
533 };
534
535 DayCounter dc = Actual365Fixed();
536 Date today = Settings::instance().evaluationDate();
537
538 Handle<Quote> spot(ext::shared_ptr<Quote>(new SimpleQuote(100)));
539 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
540 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
541
542 Real v0 = 0.09;
543
544 Option::Type type(Option::Call);
545 Average::Type averageType = Average::Geometric;
546
547 Real runningAccumulator = 1.0;
548 Size pastFixings = 0;
549
550 for (Size i=0; i<LENGTH(strikes); i++) {
551 Real strike = strikes[i];
552 int day = days[i];
553 Real expected = prices[i];
554 Real tolerance = tol[i];
555
556 Size futureFixings = int(std::floor(x: day/7.0));
557 std::vector<Date> fixingDates(futureFixings);
558
559 Date expiryDate = today + day*Days;
560
561 // I suppose "weekly fixings" roughly means this?
562 for (int i=futureFixings-1; i>=0; i--) {
563 fixingDates[i] = expiryDate - i * 7;
564 }
565
566 ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(expiryDate));
567 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
568
569 DiscreteAveragingAsianOption option(averageType, runningAccumulator, pastFixings,
570 fixingDates, payoff, europeanExercise);
571 option.setPricingEngine(engine);
572
573 Real calculated = option.NPV();
574
575 if (std::fabs(x: calculated-expected) > tolerance) {
576 REPORT_FAILURE("value", averageType, 1.0, 0.0,
577 std::vector<Date>(), payoff, europeanExercise, spot->value(),
578 qRate->value(), rRate->value(), today,
579 std::sqrt(v0), expected, calculated, tolerance);
580 }
581 }
582}
583
584
585void AsianOptionTest::testAnalyticDiscreteGeometricAveragePriceHeston() {
586
587 BOOST_TEST_MESSAGE("Testing analytic discrete geometric average-price Asians under Heston...");
588
589 // 30-day options need wider tolerance due to uncertainty around what "weekly
590 // fixing" dates mean over a 30-day month!
591 Real tol[] = {3.0e-2, 2.0e-2, 2.0e-2, 2.0e-2, 3.0e-2, 4.0e-2, 8.0e-2, 1.0e-2,
592 2.0e-2, 3.0e-2, 3.0e-2, 4.0e-2, 2.0e-2, 1.0e-2, 1.0e-2, 2.0e-2,
593 3.0e-2, 4.0e-2};
594
595 DayCounter dc = Actual365Fixed();
596 Date today = Settings::instance().evaluationDate();
597
598 Handle<Quote> spot(ext::shared_ptr<Quote>(new SimpleQuote(100)));
599 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
600 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
601 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
602 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
603
604 Real v0 = 0.09;
605 Real kappa = 1.15;
606 Real theta = 0.0348;
607 Real sigma = 0.39;
608 Real rho = -0.64;
609
610 ext::shared_ptr<HestonProcess> hestonProcess(new
611 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
612 spot, v0, kappa, theta, sigma, rho));
613
614 ext::shared_ptr<AnalyticDiscreteGeometricAveragePriceAsianHestonEngine> engine(new
615 AnalyticDiscreteGeometricAveragePriceAsianHestonEngine(hestonProcess));
616
617 testDiscreteGeometricAveragePriceHeston(engine, tol);
618}
619
620
621void AsianOptionTest::testMCDiscreteGeometricAveragePriceHeston() {
622
623 BOOST_TEST_MESSAGE("Testing MC discrete geometric average-price Asians under Heston...");
624
625 // 30-day options need wider tolerance due to uncertainty around what "weekly
626 // fixing" dates mean over a 30-day month!
627 Real tol[] = {
628 4.0e-2, 2.0e-2, 2.0e-2, 4.0e-2, 8.0e-2, 2.0e-1,
629 1.0e-1, 4.0e-2, 3.0e-2, 2.0e-2, 9.0e-2, 2.0e-1,
630 2.0e-2, 1.0e-2, 2.0e-2, 2.0e-2, 7.0e-2, 2.0e-1
631 };
632
633 DayCounter dc = Actual365Fixed();
634 Date today = Settings::instance().evaluationDate();
635
636 Handle<Quote> spot(ext::shared_ptr<Quote>(new SimpleQuote(100)));
637 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
638 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
639 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
640 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
641
642 Real v0 = 0.09;
643 Real kappa = 1.15;
644 Real theta = 0.0348;
645 Real sigma = 0.39;
646 Real rho = -0.64;
647
648 ext::shared_ptr<HestonProcess> hestonProcess(new
649 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
650 spot, v0, kappa, theta, sigma, rho));
651
652 ext::shared_ptr<PricingEngine> engine =
653 MakeMCDiscreteGeometricAPHestonEngine<LowDiscrepancy>(hestonProcess)
654 .withSamples(samples: 8191)
655 .withSeed(seed: 43);
656
657 testDiscreteGeometricAveragePriceHeston(engine, tol);
658}
659
660
661void AsianOptionTest::testDiscreteGeometricAveragePriceHestonPastFixings() {
662
663 BOOST_TEST_MESSAGE("Testing Analytic vs MC for seasoned discrete geometric Asians under Heston...");
664
665 // 30-day options need wider tolerance due to uncertainty around what "weekly
666 // fixing" dates mean over a 30-day month!
667
668 int days[] = {30, 90, 180, 360, 720};
669 Real strikes[] = {90, 100, 110};
670
671 Real tol[3][5][2] = {{{
672 0.04, // strike=90, days=30, k=0
673 0.04, // strike=90, days=30, k=1
674 },
675 {
676 0.04, // strike=90, days=90, k=0
677 0.04, // strike=90, days=90, k=1
678 },
679 {
680 0.04, // strike=90, days=180, k=0
681 0.04, // strike=90, days=180, k=1
682 },
683 {
684 0.05, // strike=90, days=360, k=0
685 0.04, // strike=90, days=360, k=1
686 },
687 {
688 0.04, // strike=90, days=720, k=0
689 0.04, // strike=90, days=720, k=1
690 }},
691
692 {{
693 0.04, // strike=100, days=30, k=0
694 0.04, // strike=100, days=30, k=1
695 },
696 {
697 0.04, // strike=100, days=90, k=0
698 0.04, // strike=100, days=90, k=1
699 },
700 {
701 0.04, // strike=100, days=180, k=0
702 0.04, // strike=100, days=180, k=1
703 },
704 {
705 0.06, // strike=100, days=360, k=0
706 0.06, // strike=100, days=360, k=1
707 },
708 {
709 0.06, // strike=100, days=720, k=0
710 0.05, // strike=100, days=720, k=1
711 }},
712
713 {{
714 0.04, // strike=110, days=30, k=0
715 0.04, // strike=110, days=30, k=1
716 },
717 {
718 0.04, // strike=110, days=90, k=0
719 0.04, // strike=110, days=90, k=1
720 },
721 {
722 0.04, // strike=110, days=180, k=0
723 0.04, // strike=110, days=180, k=1
724 },
725 {
726 0.05, // strike=110, days=360, k=0
727 0.04, // strike=110, days=360, k=1
728 },
729 {
730 0.06, // strike=110, days=720, k=0
731 0.05, // strike=110, days=720, k=1
732 }}};
733
734 DayCounter dc = Actual365Fixed();
735 Date today = Settings::instance().evaluationDate();
736
737 Handle<Quote> spot(ext::shared_ptr<Quote>(new SimpleQuote(100)));
738 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
739 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
740 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
741 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
742
743 Real v0 = 0.09;
744 Real kappa = 1.15;
745 Real theta = 0.0348;
746 Real sigma = 0.39;
747 Real rho = -0.64;
748
749 ext::shared_ptr<HestonProcess> hestonProcess(new
750 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
751 spot, v0, kappa, theta, sigma, rho));
752
753 ext::shared_ptr<AnalyticDiscreteGeometricAveragePriceAsianHestonEngine> analyticEngine(new
754 AnalyticDiscreteGeometricAveragePriceAsianHestonEngine(hestonProcess));
755
756 ext::shared_ptr<PricingEngine> mcEngine =
757 MakeMCDiscreteGeometricAPHestonEngine<LowDiscrepancy>(hestonProcess)
758 .withSamples(samples: 8191)
759 .withSeed(seed: 43);
760
761 Option::Type type(Option::Call);
762 Average::Type averageType = Average::Geometric;
763
764 for (Size strike_index = 0; strike_index < LENGTH(strikes); strike_index++) {
765
766 for (Size day_index = 0; day_index < LENGTH(days); day_index++) {
767
768 for (Size k=0; k<2; k++) {
769
770 Size futureFixings = int(std::floor(x: days[day_index] / 30.0));
771 std::vector<Date> fixingDates(futureFixings);
772 Date expiryDate = today + days[day_index] * Days;
773
774 for (int i=futureFixings-1; i>=0; i--) {
775 fixingDates[i] = expiryDate - i * 30;
776 }
777
778 ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(expiryDate));
779 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strikes[strike_index]));
780
781 Real runningAccumulator = 1.0;
782 Size pastFixingsCount = 0;
783 if (k == 0) {
784 runningAccumulator = 100.0;
785 pastFixingsCount = 1;
786 } else {
787 runningAccumulator = 95.0 * 100.0 * 105.0;
788 pastFixingsCount = 3;
789 }
790
791 DiscreteAveragingAsianOption option(averageType, runningAccumulator, pastFixingsCount,
792 fixingDates, payoff, europeanExercise);
793
794 option.setPricingEngine(analyticEngine);
795 Real analyticPrice = option.NPV();
796
797 option.setPricingEngine(mcEngine);
798 Real mcPrice = option.NPV();
799
800 auto tolerance = tol[strike_index][day_index][k];
801
802 if (std::fabs(x: analyticPrice-mcPrice) > tolerance) {
803 REPORT_FAILURE("value", averageType, runningAccumulator, pastFixingsCount,
804 std::vector<Date>(), payoff, europeanExercise, spot->value(),
805 qRate->value(), rRate->value(), today,
806 std::sqrt(v0), analyticPrice, mcPrice, tolerance);
807 }
808 }
809 }
810 }
811}
812
813namespace {
814
815 struct DiscreteAverageData {
816 Option::Type type;
817 Real underlying;
818 Real strike;
819 Rate dividendYield;
820 Rate riskFreeRate;
821 Time first;
822 Time length;
823 Size fixings;
824 Volatility volatility;
825 bool controlVariate;
826 Real result;
827 };
828
829}
830
831
832void AsianOptionTest::testMCDiscreteArithmeticAveragePrice() {
833
834 BOOST_TEST_MESSAGE(
835 "Testing Monte Carlo discrete arithmetic average-price Asians...");
836
837 // data from "Asian Option", Levy, 1997
838 // in "Exotic Options: The State of the Art",
839 // edited by Clewlow, Strickland
840 DiscreteAverageData cases4[] = {
841 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 2,
842 .volatility: 0.13, .controlVariate: true, .result: 1.3942835683 },
843 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 4,
844 .volatility: 0.13, .controlVariate: true, .result: 1.5852442983 },
845 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 8,
846 .volatility: 0.13, .controlVariate: true, .result: 1.66970673 },
847 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 12,
848 .volatility: 0.13, .controlVariate: true, .result: 1.6980019214 },
849 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 26,
850 .volatility: 0.13, .controlVariate: true, .result: 1.7255070456 },
851 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 52,
852 .volatility: 0.13, .controlVariate: true, .result: 1.7401553533 },
853 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 100,
854 .volatility: 0.13, .controlVariate: true, .result: 1.7478303712 },
855 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 250,
856 .volatility: 0.13, .controlVariate: true, .result: 1.7490291943 },
857 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 500,
858 .volatility: 0.13, .controlVariate: true, .result: 1.7515113291 },
859 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 1000,
860 .volatility: 0.13, .controlVariate: true, .result: 1.7537344885 },
861 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 2,
862 .volatility: 0.13, .controlVariate: true, .result: 1.8496053697 },
863 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 4,
864 .volatility: 0.13, .controlVariate: true, .result: 2.0111495205 },
865 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 8,
866 .volatility: 0.13, .controlVariate: true, .result: 2.0852138818 },
867 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 12,
868 .volatility: 0.13, .controlVariate: true, .result: 2.1105094397 },
869 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 26,
870 .volatility: 0.13, .controlVariate: true, .result: 2.1346526695 },
871 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 52,
872 .volatility: 0.13, .controlVariate: true, .result: 2.147489651 },
873 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 100,
874 .volatility: 0.13, .controlVariate: true, .result: 2.154728109 },
875 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 250,
876 .volatility: 0.13, .controlVariate: true, .result: 2.1564276565 },
877 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 500,
878 .volatility: 0.13, .controlVariate: true, .result: 2.1594238588 },
879 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 1000,
880 .volatility: 0.13, .controlVariate: true, .result: 2.1595367326 },
881 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 2,
882 .volatility: 0.13, .controlVariate: true, .result: 2.63315092584 },
883 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 4,
884 .volatility: 0.13, .controlVariate: true, .result: 2.76723962361 },
885 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 8,
886 .volatility: 0.13, .controlVariate: true, .result: 2.83124836881 },
887 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 12,
888 .volatility: 0.13, .controlVariate: true, .result: 2.84290301412 },
889 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 26,
890 .volatility: 0.13, .controlVariate: true, .result: 2.88179560417 },
891 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 52,
892 .volatility: 0.13, .controlVariate: true, .result: 2.88447044543 },
893 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 100,
894 .volatility: 0.13, .controlVariate: true, .result: 2.89985329603 },
895 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 250,
896 .volatility: 0.13, .controlVariate: true, .result: 2.90047296063 },
897 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 500,
898 .volatility: 0.13, .controlVariate: true, .result: 2.89813412160 },
899 { .type: Option::Put, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 1000,
900 .volatility: 0.13, .controlVariate: true, .result: 2.89703362437 }
901 };
902
903 DayCounter dc = Actual360();
904 Date today = Settings::instance().evaluationDate();
905
906 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
907 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
908 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
909 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
910 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
911 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
912 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
913
914
915
916 Average::Type averageType = Average::Arithmetic;
917 Real runningSum = 0.0;
918 Size pastFixings = 0;
919 for (auto& l : cases4) {
920
921 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(l.type, l.strike));
922
923 Time dt = l.length / (l.fixings - 1);
924 std::vector<Time> timeIncrements(l.fixings);
925 std::vector<Date> fixingDates(l.fixings);
926 timeIncrements[0] = l.first;
927 fixingDates[0] = today + timeToDays(t: timeIncrements[0]);
928 for (Size i = 1; i < l.fixings; i++) {
929 timeIncrements[i] = i * dt + l.first;
930 fixingDates[i] = today + timeToDays(t: timeIncrements[i]);
931 }
932 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(fixingDates[l.fixings - 1]));
933
934 spot->setValue(l.underlying);
935 qRate->setValue(l.dividendYield);
936 rRate->setValue(l.riskFreeRate);
937 vol->setValue(l.volatility);
938
939 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
940 BlackScholesMertonProcess(Handle<Quote>(spot),
941 Handle<YieldTermStructure>(qTS),
942 Handle<YieldTermStructure>(rTS),
943 Handle<BlackVolTermStructure>(volTS)));
944
945
946 ext::shared_ptr<PricingEngine> engine =
947 MakeMCDiscreteArithmeticAPEngine<LowDiscrepancy>(stochProcess)
948 .withSamples(samples: 2047)
949 .withControlVariate(b: l.controlVariate);
950
951 DiscreteAveragingAsianOption option(averageType, runningSum,
952 pastFixings, fixingDates,
953 payoff, exercise);
954 option.setPricingEngine(engine);
955
956 Real calculated = option.NPV();
957 Real expected = l.result;
958 Real tolerance = 2.0e-2;
959 if (std::fabs(x: calculated-expected) > tolerance) {
960 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
961 fixingDates, payoff, exercise, spot->value(),
962 qRate->value(), rRate->value(), today,
963 vol->value(), expected, calculated, tolerance);
964 }
965
966 if (l.fixings < 100) {
967 engine = ext::shared_ptr<PricingEngine>(
968 new FdBlackScholesAsianEngine(stochProcess, 100, 100, 100));
969 option.setPricingEngine(engine);
970 calculated = option.NPV();
971 if (std::fabs(x: calculated-expected) > tolerance) {
972 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
973 fixingDates, payoff, exercise, spot->value(),
974 qRate->value(), rRate->value(), today,
975 vol->value(), expected, calculated, tolerance);
976 }
977 }
978
979 engine = ext::make_shared<TurnbullWakemanAsianEngine>(args&: stochProcess);
980 option.setPricingEngine(engine);
981 calculated = option.NPV();
982 tolerance = 3.0e-2;
983 if (std::fabs(x: calculated - expected) > tolerance) {
984 BOOST_TEST_MESSAGE(
985 "The consistency check of the analytic approximation engine failed");
986 REPORT_FAILURE("value", averageType, runningSum, pastFixings, fixingDates, payoff,
987 exercise, spot->value(), qRate->value(), rRate->value(), today,
988 vol->value(), expected, calculated, tolerance);
989 }
990 }
991}
992
993
994void AsianOptionTest::testMCDiscreteArithmeticAveragePriceHeston() {
995
996 BOOST_TEST_MESSAGE(
997 "Testing Monte Carlo discrete arithmetic average-price Asians in Heston model...");
998
999 // data from "A numerical method to price exotic path-dependent
1000 // options on an underlying described by the Heston stochastic
1001 // volatility model", Ballestra, Pacelli and Zirilli, Journal
1002 // of Banking & Finance, 2007 (section 4 - Numerical Results)
1003
1004 // nb. for Heston, the volatility param below is ignored
1005 DiscreteAverageData cases[] = {
1006 { .type: Option::Call, .underlying: 120.0, .strike: 100.0, .dividendYield: 0.0, .riskFreeRate: 0.05, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 12,
1007 .volatility: 0.1, .controlVariate: false, .result: 22.50 }
1008 };
1009
1010 Real vol = 0.3;
1011 Real v0 = vol*vol;
1012 Real kappa = 11.35;
1013 Real theta = 0.022;
1014 Real sigma = 0.618;
1015 Real rho = -0.5;
1016
1017 DayCounter dc = Actual360();
1018 Date today = Settings::instance().evaluationDate();
1019
1020 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
1021 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
1022 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
1023 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
1024 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
1025
1026 Average::Type averageType = Average::Arithmetic;
1027 Real runningSum = 0.0;
1028 Size pastFixings = 0;
1029
1030 for (auto& l : cases) {
1031
1032 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(l.type, l.strike));
1033
1034 Time dt = l.length / (l.fixings - 1);
1035 std::vector<Time> timeIncrements(l.fixings);
1036 std::vector<Date> fixingDates(l.fixings);
1037 timeIncrements[0] = l.first;
1038 fixingDates[0] = today + Integer(timeIncrements[0]*365.25);
1039 for (Size i = 1; i < l.fixings; i++) {
1040 timeIncrements[i] = i * dt + l.first;
1041 fixingDates[i] = today + Integer(timeIncrements[i]*365.25);
1042 }
1043 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(fixingDates[l.fixings - 1]));
1044
1045 spot->setValue(l.underlying);
1046 qRate->setValue(l.dividendYield);
1047 rRate->setValue(l.riskFreeRate);
1048
1049 ext::shared_ptr<HestonProcess> hestonProcess(new
1050 HestonProcess(Handle<YieldTermStructure>(rTS),
1051 Handle<YieldTermStructure>(qTS),
1052 Handle<Quote>(spot),
1053 v0, kappa, theta, sigma, rho));
1054
1055 ext::shared_ptr<PricingEngine> engine =
1056 MakeMCDiscreteArithmeticAPHestonEngine<LowDiscrepancy>(hestonProcess)
1057 .withSeed(seed: 42)
1058 .withSamples(samples: 4095);
1059
1060 DiscreteAveragingAsianOption option(averageType, runningSum,
1061 pastFixings, fixingDates,
1062 payoff, exercise);
1063 option.setPricingEngine(engine);
1064
1065 Real calculated = option.NPV();
1066 Real expected = l.result;
1067 // Bounds given in paper, "22.48 to 22.52"
1068 Real tolerance = 5.0e-2;
1069
1070 if (std::fabs(x: calculated-expected) > tolerance) {
1071 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
1072 fixingDates, payoff, exercise, spot->value(),
1073 qRate->value(), rRate->value(), today,
1074 vol, expected, calculated, tolerance);
1075 }
1076
1077 // Also test the control variate version of the pricer
1078 ext::shared_ptr<PricingEngine> engine2 =
1079 MakeMCDiscreteArithmeticAPHestonEngine<LowDiscrepancy>(hestonProcess)
1080 .withSeed(seed: 42)
1081 .withSteps(steps: 48)
1082 .withSamples(samples: 4095)
1083 .withControlVariate(b: true);
1084
1085 option.setPricingEngine(engine2);
1086
1087 Real calculatedCV = option.NPV();
1088 Real expectedCV = l.result;
1089 tolerance = 3.0e-2;
1090
1091 if (std::fabs(x: calculatedCV-expectedCV) > tolerance) {
1092 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
1093 fixingDates, payoff, exercise, spot->value(),
1094 qRate->value(), rRate->value(), today,
1095 vol, expectedCV, calculatedCV, tolerance);
1096 }
1097 }
1098
1099 // An additional dataset using the Heston parameters coming from "General lower
1100 // bounds for arithmetic Asian option prices", Applied Mathematical Finance 15(2)
1101 // 123-149 (2008), by Albrecher, H., Mayer, P., and Schoutens, W. The numerical
1102 // accuracy of prices given in Table 6 is low, but higher accuracy prices for the
1103 // same parameters and options are reported by in "Pricing bounds and approximations
1104 // for discrete arithmetic Asian options under time-changed Levy processes" by Zeng,
1105 // P.P., and Kwok Y.K. (2013) in Table 4.
1106 Real strikes[] = {60.0, 80.0, 100.0, 120.0, 140.0};
1107 Real prices[] = {42.5990, 29.3698, 18.2360, 10.0565, 4.9609};
1108
1109 Real v02 = 0.0175;
1110 Real kappa2 = 1.5768;
1111 Real theta2 = 0.0398;
1112 Real sigma2 = 0.5751;
1113 Real rho2 = -0.5711;
1114
1115 DayCounter dc2 = Actual365Fixed();
1116
1117 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(100.0));
1118 ext::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0));
1119 ext::shared_ptr<YieldTermStructure> qTS2 = flatRate(today, forward: qRate2, dc: dc2);
1120 ext::shared_ptr<SimpleQuote> rRate2(new SimpleQuote(0.03));
1121 ext::shared_ptr<YieldTermStructure> rTS2 = flatRate(today, forward: rRate2, dc: dc2);
1122
1123 ext::shared_ptr<HestonProcess> hestonProcess2(new
1124 HestonProcess(Handle<YieldTermStructure>(rTS2),
1125 Handle<YieldTermStructure>(qTS2),
1126 Handle<Quote>(spot2),
1127 v02, kappa2, theta2, sigma2, rho2));
1128
1129 ext::shared_ptr<PricingEngine> engine3 =
1130 MakeMCDiscreteArithmeticAPHestonEngine<LowDiscrepancy>(hestonProcess2)
1131 .withSeed(seed: 42)
1132 .withSteps(steps: 180)
1133 .withSamples(samples: 8191);
1134
1135 ext::shared_ptr<PricingEngine> engine4 =
1136 MakeMCDiscreteArithmeticAPHestonEngine<LowDiscrepancy>(hestonProcess2)
1137 .withSeed(seed: 42)
1138 .withSteps(steps: 180)
1139 .withSamples(samples: 8191)
1140 .withControlVariate(b: true);
1141
1142 std::vector<Date> fixingDates(120);
1143 for (Size i=1; i<=120; i++) {
1144 fixingDates[i-1] = today + Period(i, Months);
1145 }
1146
1147 ext::shared_ptr<Exercise> exercise(new
1148 EuropeanExercise(fixingDates[119]));
1149
1150 for (Size i=0; i<LENGTH(prices); i++) {
1151 Real strike = strikes[i];
1152 Real expected = prices[i];
1153
1154 ext::shared_ptr<StrikedTypePayoff> payoff(new
1155 PlainVanillaPayoff(Option::Call, strike));
1156
1157 DiscreteAveragingAsianOption option(averageType, runningSum,
1158 pastFixings, fixingDates,
1159 payoff, exercise);
1160
1161 option.setPricingEngine(engine3);
1162 Real calculated = option.NPV();
1163 Real tolerance = 9.0e-2;
1164
1165 if (std::fabs(x: calculated-expected) > tolerance) {
1166 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
1167 fixingDates, payoff, exercise, spot->value(),
1168 qRate2->value(), rRate2->value(), today,
1169 vol, expected, calculated, tolerance);
1170 }
1171
1172 option.setPricingEngine(engine4);
1173 calculated = option.NPV();
1174 tolerance = 3.0e-2;
1175
1176 if (std::fabs(x: calculated-expected) > tolerance) {
1177 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
1178 fixingDates, payoff, exercise, spot->value(),
1179 qRate2->value(), rRate2->value(), today,
1180 vol, expected, calculated, tolerance);
1181 }
1182 }
1183}
1184
1185
1186
1187void AsianOptionTest::testMCDiscreteArithmeticAverageStrike() {
1188
1189 BOOST_TEST_MESSAGE(
1190 "Testing Monte Carlo discrete arithmetic average-strike Asians...");
1191
1192 // data from "Asian Option", Levy, 1997
1193 // in "Exotic Options: The State of the Art",
1194 // edited by Clewlow, Strickland
1195 DiscreteAverageData cases5[] = {
1196 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 2,
1197 .volatility: 0.13, .controlVariate: true, .result: 1.51917595129 },
1198 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 4,
1199 .volatility: 0.13, .controlVariate: true, .result: 1.67940165674 },
1200 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 8,
1201 .volatility: 0.13, .controlVariate: true, .result: 1.75371215251 },
1202 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 12,
1203 .volatility: 0.13, .controlVariate: true, .result: 1.77595318693 },
1204 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 26,
1205 .volatility: 0.13, .controlVariate: true, .result: 1.81430536630 },
1206 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 52,
1207 .volatility: 0.13, .controlVariate: true, .result: 1.82269246898 },
1208 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 100,
1209 .volatility: 0.13, .controlVariate: true, .result: 1.83822402464 },
1210 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 250,
1211 .volatility: 0.13, .controlVariate: true, .result: 1.83875059026 },
1212 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 500,
1213 .volatility: 0.13, .controlVariate: true, .result: 1.83750703638 },
1214 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 0.0, .length: 11.0/12.0, .fixings: 1000,
1215 .volatility: 0.13, .controlVariate: true, .result: 1.83887181884 },
1216 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 2,
1217 .volatility: 0.13, .controlVariate: true, .result: 1.51154400089 },
1218 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 4,
1219 .volatility: 0.13, .controlVariate: true, .result: 1.67103508506 },
1220 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 8,
1221 .volatility: 0.13, .controlVariate: true, .result: 1.74529684070 },
1222 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 12,
1223 .volatility: 0.13, .controlVariate: true, .result: 1.76667074564 },
1224 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 26,
1225 .volatility: 0.13, .controlVariate: true, .result: 1.80528400613 },
1226 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 52,
1227 .volatility: 0.13, .controlVariate: true, .result: 1.81400883891 },
1228 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 100,
1229 .volatility: 0.13, .controlVariate: true, .result: 1.82922901451 },
1230 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 250,
1231 .volatility: 0.13, .controlVariate: true, .result: 1.82937111773 },
1232 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 500,
1233 .volatility: 0.13, .controlVariate: true, .result: 1.82826193186 },
1234 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 1.0/12.0, .length: 11.0/12.0, .fixings: 1000,
1235 .volatility: 0.13, .controlVariate: true, .result: 1.82967846654 },
1236 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 2,
1237 .volatility: 0.13, .controlVariate: true, .result: 1.49648170891 },
1238 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 4,
1239 .volatility: 0.13, .controlVariate: true, .result: 1.65443100462 },
1240 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 8,
1241 .volatility: 0.13, .controlVariate: true, .result: 1.72817806731 },
1242 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 12,
1243 .volatility: 0.13, .controlVariate: true, .result: 1.74877367895 },
1244 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 26,
1245 .volatility: 0.13, .controlVariate: true, .result: 1.78733801988 },
1246 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 52,
1247 .volatility: 0.13, .controlVariate: true, .result: 1.79624826757 },
1248 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 100,
1249 .volatility: 0.13, .controlVariate: true, .result: 1.81114186876 },
1250 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 250,
1251 .volatility: 0.13, .controlVariate: true, .result: 1.81101152587 },
1252 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 500,
1253 .volatility: 0.13, .controlVariate: true, .result: 1.81002311939 },
1254 { .type: Option::Call, .underlying: 90.0, .strike: 87.0, .dividendYield: 0.06, .riskFreeRate: 0.025, .first: 3.0/12.0, .length: 11.0/12.0, .fixings: 1000,
1255 .volatility: 0.13, .controlVariate: true, .result: 1.81145760308 }
1256 };
1257
1258 DayCounter dc = Actual360();
1259 Date today = Settings::instance().evaluationDate();
1260
1261 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
1262 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
1263 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
1264 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
1265 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
1266 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
1267 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
1268
1269 Average::Type averageType = Average::Arithmetic;
1270 Real runningSum = 0.0;
1271 Size pastFixings = 0;
1272 for (auto& l : cases5) {
1273
1274 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(l.type, l.strike));
1275
1276 Time dt = l.length / (l.fixings - 1);
1277 std::vector<Time> timeIncrements(l.fixings);
1278 std::vector<Date> fixingDates(l.fixings);
1279 timeIncrements[0] = l.first;
1280 fixingDates[0] = today + timeToDays(t: timeIncrements[0]);
1281 for (Size i = 1; i < l.fixings; i++) {
1282 timeIncrements[i] = i * dt + l.first;
1283 fixingDates[i] = today + timeToDays(t: timeIncrements[i]);
1284 }
1285 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(fixingDates[l.fixings - 1]));
1286
1287 spot->setValue(l.underlying);
1288 qRate->setValue(l.dividendYield);
1289 rRate->setValue(l.riskFreeRate);
1290 vol->setValue(l.volatility);
1291
1292 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
1293 BlackScholesMertonProcess(Handle<Quote>(spot),
1294 Handle<YieldTermStructure>(qTS),
1295 Handle<YieldTermStructure>(rTS),
1296 Handle<BlackVolTermStructure>(volTS)));
1297
1298 ext::shared_ptr<PricingEngine> engine =
1299 MakeMCDiscreteArithmeticASEngine<LowDiscrepancy>(stochProcess)
1300 .withSeed(seed: 3456789)
1301 .withSamples(samples: 1023);
1302
1303 DiscreteAveragingAsianOption option(averageType, runningSum,
1304 pastFixings, fixingDates,
1305 payoff, exercise);
1306 option.setPricingEngine(engine);
1307
1308 Real calculated = option.NPV();
1309 Real expected = l.result;
1310 Real tolerance = 2.0e-2;
1311 if (std::fabs(x: calculated-expected) > tolerance) {
1312 REPORT_FAILURE("value", averageType, runningSum, pastFixings,
1313 fixingDates, payoff, exercise, spot->value(),
1314 qRate->value(), rRate->value(), today,
1315 vol->value(), expected, calculated, tolerance);
1316 }
1317 }
1318}
1319
1320void AsianOptionTest::testAnalyticDiscreteGeometricAveragePriceGreeks() {
1321
1322 BOOST_TEST_MESSAGE("Testing discrete-averaging geometric Asian greeks...");
1323
1324 std::map<std::string,Real> calculated, expected, tolerance;
1325 tolerance["delta"] = 1.0e-5;
1326 tolerance["gamma"] = 1.0e-5;
1327 tolerance["theta"] = 1.0e-5;
1328 tolerance["rho"] = 1.0e-5;
1329 tolerance["divRho"] = 1.0e-5;
1330 tolerance["vega"] = 1.0e-5;
1331
1332 Option::Type types[] = { Option::Call, Option::Put };
1333 Real underlyings[] = { 100.0 };
1334 Real strikes[] = { 90.0, 100.0, 110.0 };
1335 Rate qRates[] = { 0.04, 0.05, 0.06 };
1336 Rate rRates[] = { 0.01, 0.05, 0.15 };
1337 Integer lengths[] = { 1, 2 };
1338 Volatility vols[] = { 0.11, 0.50, 1.20 };
1339
1340 DayCounter dc = Actual360();
1341 Date today = Settings::instance().evaluationDate();
1342 Settings::instance().evaluationDate() = today;
1343
1344 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
1345 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
1346 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
1347 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
1348 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
1349 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
1350 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
1351
1352 ext::shared_ptr<BlackScholesMertonProcess> process(
1353 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
1354
1355 for (auto& type : types) {
1356 for (Real strike : strikes) {
1357 for (int length : lengths) {
1358
1359 ext::shared_ptr<EuropeanExercise> maturity(
1360 new EuropeanExercise(today + length * Years));
1361
1362 ext::shared_ptr<PlainVanillaPayoff> payoff(new PlainVanillaPayoff(type, strike));
1363
1364 Real runningAverage = 120;
1365 Size pastFixings = 1;
1366
1367 std::vector<Date> fixingDates;
1368 for (Date d = today + 3 * Months; d <= maturity->lastDate(); d += 3 * Months)
1369 fixingDates.push_back(x: d);
1370
1371
1372 ext::shared_ptr<PricingEngine> engine(
1373 new AnalyticDiscreteGeometricAveragePriceAsianEngine(process));
1374
1375 DiscreteAveragingAsianOption option(Average::Geometric, runningAverage, pastFixings,
1376 fixingDates, payoff, maturity);
1377 option.setPricingEngine(engine);
1378
1379 for (Real u : underlyings) {
1380 for (Real m : qRates) {
1381 for (Real n : rRates) {
1382 for (Real v : vols) {
1383
1384 Rate q = m, r = n;
1385 spot->setValue(u);
1386 qRate->setValue(q);
1387 rRate->setValue(r);
1388 vol->setValue(v);
1389
1390 Real value = option.NPV();
1391 calculated["delta"] = option.delta();
1392 calculated["gamma"] = option.gamma();
1393 calculated["theta"] = option.theta();
1394 calculated["rho"] = option.rho();
1395 calculated["divRho"] = option.dividendRho();
1396 calculated["vega"] = option.vega();
1397
1398 if (value > spot->value() * 1.0e-5) {
1399 // perturb spot and get delta and gamma
1400 Real du = u * 1.0e-4;
1401 spot->setValue(u + du);
1402 Real value_p = option.NPV(), delta_p = option.delta();
1403 spot->setValue(u - du);
1404 Real value_m = option.NPV(), delta_m = option.delta();
1405 spot->setValue(u);
1406 expected["delta"] = (value_p - value_m) / (2 * du);
1407 expected["gamma"] = (delta_p - delta_m) / (2 * du);
1408
1409 // perturb rates and get rho and dividend rho
1410 Spread dr = r * 1.0e-4;
1411 rRate->setValue(r + dr);
1412 value_p = option.NPV();
1413 rRate->setValue(r - dr);
1414 value_m = option.NPV();
1415 rRate->setValue(r);
1416 expected["rho"] = (value_p - value_m) / (2 * dr);
1417
1418 Spread dq = q * 1.0e-4;
1419 qRate->setValue(q + dq);
1420 value_p = option.NPV();
1421 qRate->setValue(q - dq);
1422 value_m = option.NPV();
1423 qRate->setValue(q);
1424 expected["divRho"] = (value_p - value_m) / (2 * dq);
1425
1426 // perturb volatility and get vega
1427 Volatility dv = v * 1.0e-4;
1428 vol->setValue(v + dv);
1429 value_p = option.NPV();
1430 vol->setValue(v - dv);
1431 value_m = option.NPV();
1432 vol->setValue(v);
1433 expected["vega"] = (value_p - value_m) / (2 * dv);
1434
1435 // perturb date and get theta
1436 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
1437 Settings::instance().evaluationDate() = today - 1;
1438 value_m = option.NPV();
1439 Settings::instance().evaluationDate() = today + 1;
1440 value_p = option.NPV();
1441 Settings::instance().evaluationDate() = today;
1442 expected["theta"] = (value_p - value_m) / dT;
1443
1444 // compare
1445 std::map<std::string, Real>::iterator it;
1446 for (it = calculated.begin(); it != calculated.end(); ++it) {
1447 std::string greek = it->first;
1448 Real expct = expected[greek], calcl = calculated[greek],
1449 tol = tolerance[greek];
1450 Real error = relativeError(x1: expct, x2: calcl, reference: u);
1451 if (error > tol) {
1452 REPORT_FAILURE(greek, Average::Geometric,
1453 runningAverage, pastFixings,
1454 std::vector<Date>(), payoff, maturity, u,
1455 q, r, today, v, expct, calcl, tol);
1456 }
1457 }
1458 }
1459 }
1460 }
1461 }
1462 }
1463 }
1464 }
1465 }
1466}
1467
1468
1469void AsianOptionTest::testPastFixings() {
1470
1471 BOOST_TEST_MESSAGE("Testing use of past fixings in Asian options...");
1472
1473 DayCounter dc = Actual360();
1474 Date today = Settings::instance().evaluationDate();
1475
1476 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
1477 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
1478 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
1479 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
1480 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
1481 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
1482 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
1483
1484 ext::shared_ptr<StrikedTypePayoff> payoff(
1485 new PlainVanillaPayoff(Option::Put, 100.0));
1486
1487
1488 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(today + 1*Years));
1489
1490 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
1491 new BlackScholesMertonProcess(Handle<Quote>(spot),
1492 Handle<YieldTermStructure>(qTS),
1493 Handle<YieldTermStructure>(rTS),
1494 Handle<BlackVolTermStructure>(volTS)));
1495
1496 // MC arithmetic average-price
1497
1498 Real runningSum = 0.0;
1499 Size pastFixings = 0;
1500 std::vector<Date> fixingDates1;
1501 for (Integer i=0; i<=12; ++i)
1502 fixingDates1.push_back(x: today + i*Months);
1503
1504 DiscreteAveragingAsianOption option1(Average::Arithmetic, runningSum,
1505 pastFixings, fixingDates1,
1506 payoff, exercise);
1507
1508 pastFixings = 2;
1509 runningSum = pastFixings * spot->value() * 0.8;
1510 std::vector<Date> fixingDates2;
1511 for (Integer i=-2; i<=12; ++i)
1512 fixingDates2.push_back(x: today + i*Months);
1513
1514 DiscreteAveragingAsianOption option2(Average::Arithmetic, runningSum,
1515 pastFixings, fixingDates2,
1516 payoff, exercise);
1517
1518 ext::shared_ptr<PricingEngine> engine =
1519 MakeMCDiscreteArithmeticAPEngine<LowDiscrepancy>(stochProcess)
1520 .withSamples(samples: 2047);
1521
1522 option1.setPricingEngine(engine);
1523 option2.setPricingEngine(engine);
1524
1525 Real price1 = option1.NPV();
1526 Real price2 = option2.NPV();
1527
1528 if (close(x: price1, y: price2)) {
1529 BOOST_ERROR(
1530 "past fixings had no effect on arithmetic average-price option"
1531 << "\n without fixings: " << price1
1532 << "\n with fixings: " << price2);
1533 }
1534
1535 // Test past-fixings-as-a-vector interface
1536
1537 std::vector<Real> allPastFixings = {spot->value() * 0.8, spot->value() * 0.8};
1538
1539 DiscreteAveragingAsianOption option1a(Average::Arithmetic, fixingDates1,
1540 payoff, exercise);
1541
1542 DiscreteAveragingAsianOption option2a(Average::Arithmetic, fixingDates2,
1543 payoff, exercise, allPastFixings);
1544
1545 option1a.setPricingEngine(engine);
1546 option2a.setPricingEngine(engine);
1547
1548 Real price1a = option1a.NPV();
1549 Real price2a = option2a.NPV();
1550
1551 if (std::fabs(x: price1 - price1a) > 1e-8) {
1552 BOOST_ERROR(
1553 "Unseasoned option prices do not match in old and new interface"
1554 << "\n Old Interface: " << price1
1555 << "\n New Interface: " << price1a);
1556 }
1557
1558 if (std::fabs(x: price2 - price2a) > 1e-8) {
1559 BOOST_ERROR(
1560 "Seasoned option prices do not match in old and new interface"
1561 << "\n Old Interface: " << price2
1562 << "\n New Interface: " << price2a);
1563 }
1564
1565 // MC arithmetic average-strike
1566
1567 engine =
1568 MakeMCDiscreteArithmeticASEngine<LowDiscrepancy>(stochProcess)
1569 .withSamples(samples: 2047);
1570
1571 option1.setPricingEngine(engine);
1572 option2.setPricingEngine(engine);
1573
1574 price1 = option1.NPV();
1575 price2 = option2.NPV();
1576
1577 if (close(x: price1, y: price2)) {
1578 BOOST_ERROR(
1579 "past fixings had no effect on arithmetic average-strike option"
1580 << "\n without fixings: " << price1
1581 << "\n with fixings: " << price2);
1582 }
1583
1584 // analytic geometric average-price
1585
1586 Real runningProduct = 1.0;
1587 pastFixings = 0;
1588
1589 DiscreteAveragingAsianOption option3(Average::Geometric, runningProduct,
1590 pastFixings, fixingDates1,
1591 payoff, exercise);
1592
1593 pastFixings = 2;
1594 runningProduct = spot->value() * spot->value();
1595
1596 DiscreteAveragingAsianOption option4(Average::Geometric, runningProduct,
1597 pastFixings, fixingDates2,
1598 payoff, exercise);
1599
1600 engine = ext::shared_ptr<PricingEngine>(
1601 new AnalyticDiscreteGeometricAveragePriceAsianEngine(stochProcess));
1602
1603 option3.setPricingEngine(engine);
1604 option4.setPricingEngine(engine);
1605
1606 Real price3 = option3.NPV();
1607 Real price4 = option4.NPV();
1608
1609 if (close(x: price3, y: price4)) {
1610 BOOST_ERROR(
1611 "past fixings had no effect on geometric average-price option"
1612 << "\n without fixings: " << price3
1613 << "\n with fixings: " << price4);
1614 }
1615
1616 // MC geometric average-price
1617
1618 engine =
1619 MakeMCDiscreteGeometricAPEngine<LowDiscrepancy>(stochProcess)
1620 .withSamples(samples: 2047);
1621
1622 option3.setPricingEngine(engine);
1623 option4.setPricingEngine(engine);
1624
1625 price3 = option3.NPV();
1626 price4 = option4.NPV();
1627
1628 if (close(x: price3, y: price4)) {
1629 BOOST_ERROR(
1630 "past fixings had no effect on geometric average-price option"
1631 << "\n without fixings: " << price3
1632 << "\n with fixings: " << price4);
1633 }
1634
1635}
1636
1637void AsianOptionTest::testPastFixingsModelDependency() {
1638
1639 BOOST_TEST_MESSAGE(
1640 "Testing use of past fixings in Asian options where model dependency is flagged...");
1641
1642 DayCounter dc = Actual360();
1643 Date today = Settings::instance().evaluationDate();
1644
1645 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
1646 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.03));
1647 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
1648 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.06));
1649 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
1650 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
1651 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
1652
1653 ext::shared_ptr<StrikedTypePayoff> call_payoff(new PlainVanillaPayoff(Option::Call, 20.0));
1654 ext::shared_ptr<StrikedTypePayoff> put_payoff(new PlainVanillaPayoff(Option::Put, 20.0));
1655
1656 std::vector<Date> fixingDates = {today - 6 * Weeks, today - 2 * Weeks, today + 2 * Weeks,
1657 today + 6 * Weeks};
1658
1659 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(today + 6 * Weeks));
1660
1661 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new BlackScholesMertonProcess(
1662 Handle<Quote>(spot), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
1663 Handle<BlackVolTermStructure>(volTS)));
1664
1665 // Test guaranteed exercise (calls) and permanent OTMness (puts), with the average price TW
1666 // engine
1667
1668 ext::shared_ptr<PricingEngine> engine = ext::shared_ptr<PricingEngine>(
1669 new TurnbullWakemanAsianEngine(stochProcess));
1670
1671 std::vector<Real> allPastFixings = {spot->value(), spot->value()};
1672
1673 DiscreteAveragingAsianOption call_option(Average::Arithmetic, fixingDates, call_payoff,
1674 exercise, allPastFixings);
1675 DiscreteAveragingAsianOption put_option(Average::Arithmetic, fixingDates, put_payoff, exercise,
1676 allPastFixings);
1677
1678 call_option.setPricingEngine(engine);
1679 put_option.setPricingEngine(engine);
1680
1681 // The expected call NPV is equal to that of an averaging forward over the same fixing dates,
1682 // since exercise is guaranteed
1683 Real expected_call_option_npv =
1684 rTS->discount(d: exercise->lastDate()) *
1685 ((100.0 + 100.0 + 100.0 * qTS->discount(d: fixingDates[2]) / rTS->discount(d: fixingDates[2]) +
1686 100.0 * qTS->discount(d: fixingDates[3]) / rTS->discount(d: fixingDates[3])) /
1687 fixingDates.size() -
1688 call_payoff->strike());
1689
1690 BOOST_CHECK_EQUAL(call_option.NPV(), expected_call_option_npv);
1691 BOOST_CHECK_EQUAL(put_option.NPV(), 0.0);
1692
1693 // Compare greeks to numerical greeks
1694 Real dS = 0.001;
1695 Real callPrice = call_option.NPV();
1696 Real putPrice = put_option.NPV();
1697 Real callDelta = call_option.delta();
1698 Real callGamma = call_option.gamma();
1699 Real putDelta = put_option.delta();
1700 Real putGamma = put_option.gamma();
1701
1702 ext::shared_ptr<SimpleQuote> spotUp(new SimpleQuote(100.0+dS));
1703 ext::shared_ptr<SimpleQuote> spotDown(new SimpleQuote(100.0-dS));
1704
1705 ext::shared_ptr<BlackScholesMertonProcess> stochProcessUp(new BlackScholesMertonProcess(
1706 Handle<Quote>(spotUp), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
1707 Handle<BlackVolTermStructure>(volTS)));
1708
1709 ext::shared_ptr<BlackScholesMertonProcess> stochProcessDown(new BlackScholesMertonProcess(
1710 Handle<Quote>(spotDown), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
1711 Handle<BlackVolTermStructure>(volTS)));
1712
1713 ext::shared_ptr<PricingEngine> engineUp(
1714 new TurnbullWakemanAsianEngine(stochProcessUp));
1715
1716 ext::shared_ptr<PricingEngine> engineDown(
1717 new TurnbullWakemanAsianEngine(stochProcessDown));
1718
1719 call_option.setPricingEngine(engineUp);
1720 Real callCalculatedUp = call_option.NPV();
1721 put_option.setPricingEngine(engineUp);
1722 Real putCalculatedUp = put_option.NPV();
1723
1724 call_option.setPricingEngine(engineDown);
1725 Real callCalculatedDown = call_option.NPV();
1726 put_option.setPricingEngine(engineDown);
1727 Real putCalculatedDown = put_option.NPV();
1728
1729 Real callDeltaBump = (callCalculatedUp - callCalculatedDown) / (2 * dS);
1730 Real callGammaBump = (callCalculatedUp + callCalculatedDown - 2*callPrice) / (dS * dS);
1731
1732 Real putDeltaBump = (putCalculatedUp - putCalculatedDown) / (2 * dS);
1733 Real putGammaBump = (putCalculatedUp + putCalculatedDown - 2*putPrice) / (dS * dS);
1734
1735 Real tolerance = 1.0e-8;
1736 if (std::fabs(x: callDeltaBump - callDelta) > tolerance) {
1737 BOOST_ERROR(
1738 "Seasoned analytic call delta did not match numerical delta:"
1739 << "\n analytic delta: " << callDelta << "\n bump delta: " << callDeltaBump
1740 << "\n error: " << std::fabs(callDeltaBump - callDelta));
1741 }
1742 if (std::fabs(x: callGammaBump - callGamma) > tolerance) {
1743 BOOST_ERROR(
1744 "Seasoned analytic call gamma did not match numerical gamma:"
1745 << "\n analytic gamma: " << callGamma << "\n bump gamma: " << callGammaBump
1746 << "\n error: " << std::fabs(callGammaBump - callGamma));
1747 }
1748 if (std::fabs(x: putDeltaBump - putDelta) > tolerance) {
1749 BOOST_ERROR(
1750 "Seasoned analytic put delta did not match numerical delta:"
1751 << "\n analytic delta: " << putDelta << "\n bump delta: " << putDeltaBump
1752 << "\n error: " << std::fabs(putDeltaBump - putDelta));
1753 }
1754 if (std::fabs(x: putGammaBump - putGamma) > tolerance) {
1755 BOOST_ERROR(
1756 "Seasoned analytic put gamma did not match numerical gamma:"
1757 << "\n analytic gamma: " << putGamma << "\n bump gamma: " << putGammaBump
1758 << "\n error: " << std::fabs(putGammaBump - putGamma));
1759 }
1760}
1761
1762
1763void AsianOptionTest::testAllFixingsInThePast() {
1764
1765 BOOST_TEST_MESSAGE(
1766 "Testing Asian options with all fixing dates in the past...");
1767
1768 DayCounter dc = Actual360();
1769 Date today = Settings::instance().evaluationDate();
1770
1771 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
1772 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.005));
1773 ext::shared_ptr<YieldTermStructure> qTS = flatRate(forward: qRate, dc);
1774 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
1775 ext::shared_ptr<YieldTermStructure> rTS = flatRate(forward: rRate, dc);
1776 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.20));
1777 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(volatility: vol, dc);
1778
1779 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
1780 new BlackScholesMertonProcess(Handle<Quote>(spot),
1781 Handle<YieldTermStructure>(qTS),
1782 Handle<YieldTermStructure>(rTS),
1783 Handle<BlackVolTermStructure>(volTS)));
1784
1785 Date exerciseDate = today + 2*Weeks;
1786 Date startDate = exerciseDate - 1*Years;
1787 std::vector<Date> fixingDates;
1788 fixingDates.reserve(n: 12);
1789 for (Integer i = 0; i < 12; ++i)
1790 fixingDates.push_back(x: startDate + i*Months);
1791 Size pastFixings = 12;
1792
1793 ext::shared_ptr<StrikedTypePayoff> payoff(
1794 new PlainVanillaPayoff(Option::Put, 100.0));
1795 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate));
1796
1797 // MC arithmetic average-price
1798
1799 Real runningSum = pastFixings * spot->value();
1800
1801 DiscreteAveragingAsianOption option1(Average::Arithmetic, runningSum,
1802 pastFixings, fixingDates,
1803 payoff, exercise);
1804 option1.setPricingEngine(
1805 MakeMCDiscreteArithmeticAPEngine<LowDiscrepancy>(stochProcess)
1806 .withSamples(samples: 2047));
1807
1808 // MC arithmetic average-strike
1809
1810 DiscreteAveragingAsianOption option2(Average::Arithmetic, runningSum,
1811 pastFixings, fixingDates,
1812 payoff, exercise);
1813 option2.setPricingEngine(
1814 MakeMCDiscreteArithmeticASEngine<LowDiscrepancy>(stochProcess)
1815 .withSamples(samples: 2047));
1816
1817 // MC geometric average-price
1818
1819 Real runningProduct = std::pow(x: spot->value(), y: int(pastFixings));
1820
1821 DiscreteAveragingAsianOption option3(Average::Geometric, runningProduct,
1822 pastFixings, fixingDates,
1823 payoff, exercise);
1824 option3.setPricingEngine(
1825 MakeMCDiscreteGeometricAPEngine<LowDiscrepancy>(stochProcess)
1826 .withSamples(samples: 2047));
1827
1828 // Check that NPV raises a specific exception instead of crashing.
1829 // (It used to do that.)
1830
1831 bool raised = false;
1832 try {
1833 option1.NPV();
1834 } catch (detail::PastFixingsOnly&) {
1835 raised = true;
1836 }
1837 if (!raised) {
1838 BOOST_FAIL("exception expected");
1839 }
1840
1841 raised = false;
1842 try {
1843 option1.NPV();
1844 } catch (detail::PastFixingsOnly&) {
1845 raised = true;
1846 }
1847 if (!raised) {
1848 BOOST_FAIL("exception expected");
1849 }
1850
1851 raised = false;
1852 try {
1853 option2.NPV();
1854 } catch (detail::PastFixingsOnly&) {
1855 raised = true;
1856 }
1857 if (!raised) {
1858 BOOST_FAIL("exception expected");
1859 }
1860
1861 // also check with the evaluation date on last fixing
1862
1863 Settings::instance().evaluationDate() = fixingDates.back();
1864
1865 raised = false;
1866 try {
1867 option1.NPV();
1868 } catch (detail::PastFixingsOnly&) {
1869 raised = true;
1870 }
1871 if (!raised) {
1872 BOOST_FAIL("exception expected");
1873 }
1874
1875 raised = false;
1876 try {
1877 option1.NPV();
1878 } catch (detail::PastFixingsOnly&) {
1879 raised = true;
1880 }
1881 if (!raised) {
1882 BOOST_FAIL("exception expected");
1883 }
1884
1885 raised = false;
1886 try {
1887 option2.NPV();
1888 } catch (detail::PastFixingsOnly&) {
1889 raised = true;
1890 }
1891 if (!raised) {
1892 BOOST_FAIL("exception expected");
1893 }
1894}
1895
1896namespace {
1897
1898 struct ContinuousAverageData {
1899 Option::Type type;
1900 Real spot;
1901 Real currentAverage;
1902 Real strike;
1903 Rate dividendYield;
1904 Rate riskFreeRate;
1905 Volatility volatility;
1906 Natural length;
1907 Natural elapsed;
1908 Real result;
1909 };
1910
1911}
1912
1913void AsianOptionTest::testLevyEngine() {
1914
1915 BOOST_TEST_MESSAGE("Testing Levy engine for Asians options...");
1916
1917 // data from Haug, "Option Pricing Formulas", p.99-100
1918 ContinuousAverageData cases[] = {
1919 { .type: Option::Call, .spot: 6.80, .currentAverage: 6.80, .strike: 6.90, .dividendYield: 0.09, .riskFreeRate: 0.07, .volatility: 0.14, .length: 180, .elapsed: 0, .result: 0.0944 },
1920 { .type: Option::Put, .spot: 6.80, .currentAverage: 6.80, .strike: 6.90, .dividendYield: 0.09, .riskFreeRate: 0.07, .volatility: 0.14, .length: 180, .elapsed: 0, .result: 0.2237 },
1921 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 0, .result: 7.0544 },
1922 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 90, .result: 5.6731 },
1923 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 180, .result: 5.0806 },
1924 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 0, .result: 10.1213 },
1925 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 90, .result: 6.9705 },
1926 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 95.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 180, .result: 5.1411 },
1927 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 0, .result: 3.7845 },
1928 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 90, .result: 1.9964 },
1929 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 180, .result: 0.6722 },
1930 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 0, .result: 7.5038 },
1931 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 90, .result: 4.0687 },
1932 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 100.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 180, .result: 1.4222 },
1933 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 0, .result: 1.6729 },
1934 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 90, .result: 0.3565 },
1935 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.15, .length: 270, .elapsed: 180, .result: 0.0004 },
1936 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 0, .result: 5.4071 },
1937 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 90, .result: 2.1359 },
1938 { .type: Option::Call, .spot: 100.0, .currentAverage: 100.0, .strike: 105.0, .dividendYield: 0.05, .riskFreeRate: 0.1, .volatility: 0.35, .length: 270, .elapsed: 180, .result: 0.1552 }
1939 };
1940
1941 DayCounter dc = Actual360();
1942 Date today = Settings::instance().evaluationDate();
1943
1944 for (auto& l : cases) {
1945
1946 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(l.spot));
1947 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: l.dividendYield, dc);
1948 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: l.riskFreeRate, dc);
1949 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: l.volatility, dc);
1950
1951 Average::Type averageType = Average::Arithmetic;
1952 ext::shared_ptr<Quote> average(new SimpleQuote(l.currentAverage));
1953
1954 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(l.type, l.strike));
1955
1956 Date startDate = today - l.elapsed;
1957 Date maturity = startDate + l.length;
1958
1959 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(maturity));
1960
1961 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
1962 BlackScholesMertonProcess(Handle<Quote>(spot),
1963 Handle<YieldTermStructure>(qTS),
1964 Handle<YieldTermStructure>(rTS),
1965 Handle<BlackVolTermStructure>(volTS)));
1966
1967 ext::shared_ptr<PricingEngine> engine(
1968 new ContinuousArithmeticAsianLevyEngine(
1969 stochProcess, Handle<Quote>(average), startDate));
1970
1971 ContinuousAveragingAsianOption option(averageType,
1972 payoff, exercise);
1973 option.setPricingEngine(engine);
1974
1975 Real calculated = option.NPV();
1976 Real expected = l.result;
1977 Real tolerance = 1.0e-4;
1978 Real error = std::fabs(x: expected-calculated);
1979 if (error > tolerance) {
1980 BOOST_ERROR(
1981 "Asian option with Levy engine:"
1982 << "\n spot: " << l.spot << "\n current average: "
1983 << l.currentAverage << "\n strike: " << l.strike
1984 << "\n dividend yield: " << l.dividendYield << "\n risk-free rate: "
1985 << l.riskFreeRate << "\n volatility: " << l.volatility
1986 << "\n reference date: " << today << "\n length: " << l.length
1987 << "\n elapsed: " << l.elapsed << "\n expected value: " << expected
1988 << "\n calculated: " << calculated << "\n error: " << error);
1989 }
1990 }
1991}
1992
1993namespace {
1994
1995 struct VecerData {
1996 Real spot;
1997 Rate riskFreeRate;
1998 Volatility volatility;
1999 Real strike;
2000 Natural length;
2001 Real result;
2002 Real tolerance;
2003 };
2004
2005}
2006
2007void AsianOptionTest::testVecerEngine() {
2008 BOOST_TEST_MESSAGE("Testing Vecer engine for Asian options...");
2009
2010 VecerData cases[] = {
2011 { .spot: 1.9, .riskFreeRate: 0.05, .volatility: 0.5, .strike: 2.0, .length: 1, .result: 0.193174, .tolerance: 1.0e-5 },
2012 { .spot: 2.0, .riskFreeRate: 0.05, .volatility: 0.5, .strike: 2.0, .length: 1, .result: 0.246416, .tolerance: 1.0e-5 },
2013 { .spot: 2.1, .riskFreeRate: 0.05, .volatility: 0.5, .strike: 2.0, .length: 1, .result: 0.306220, .tolerance: 1.0e-4 },
2014 { .spot: 2.0, .riskFreeRate: 0.02, .volatility: 0.1, .strike: 2.0, .length: 1, .result: 0.055986, .tolerance: 2.0e-4 },
2015 { .spot: 2.0, .riskFreeRate: 0.18, .volatility: 0.3, .strike: 2.0, .length: 1, .result: 0.218388, .tolerance: 1.0e-4 },
2016 { .spot: 2.0, .riskFreeRate: 0.0125, .volatility: 0.25, .strike: 2.0, .length: 2, .result: 0.172269, .tolerance: 1.0e-4 },
2017 { .spot: 2.0, .riskFreeRate: 0.05, .volatility: 0.5, .strike: 2.0, .length: 2, .result: 0.350095, .tolerance: 2.0e-4 }
2018 };
2019
2020 Date today = Settings::instance().evaluationDate();
2021 DayCounter dayCounter = Actual360();
2022
2023 Option::Type type = Option::Call;
2024 Handle<YieldTermStructure> q(flatRate(today, forward: 0.0, dc: dayCounter));
2025
2026 Size timeSteps = 200;
2027 Size assetSteps = 200;
2028
2029 for (auto& i : cases) {
2030 Handle<Quote> u(ext::make_shared<SimpleQuote>(args&: i.spot));
2031 Handle<YieldTermStructure> r(flatRate(today, forward: i.riskFreeRate, dc: dayCounter));
2032 Handle<BlackVolTermStructure> sigma(flatVol(today, volatility: i.volatility, dc: dayCounter));
2033 ext::shared_ptr<BlackScholesMertonProcess> process =
2034 ext::make_shared<BlackScholesMertonProcess>(args&: u, args&: q, args&: r, args&: sigma);
2035
2036 Date maturity = today + i.length * 360;
2037 ext::shared_ptr<Exercise> exercise =
2038 ext::make_shared<EuropeanExercise>(args&: maturity);
2039 ext::shared_ptr<StrikedTypePayoff> payoff =
2040 ext::make_shared<PlainVanillaPayoff>(args&: type, args&: i.strike);
2041 Handle<Quote> average(ext::make_shared<SimpleQuote>(args: 0.0));
2042
2043 ContinuousAveragingAsianOption option(Average::Arithmetic,
2044 payoff, exercise);
2045 option.setPricingEngine(
2046 ext::make_shared<ContinuousArithmeticAsianVecerEngine>(
2047 args&: process,args&: average,args&: today,args&: timeSteps,args&: assetSteps,args: -1.0,args: 1.0));
2048
2049 Real calculated = option.NPV();
2050 Real error = std::fabs(x: calculated - i.result);
2051 if (error > i.tolerance)
2052 BOOST_ERROR("Failed to reproduce expected NPV"
2053 << "\n calculated: " << calculated << "\n expected: " << i.result
2054 << "\n expected: " << i.result << "\n error: " << error
2055 << "\n tolerance: " << i.tolerance);
2056 }
2057}
2058
2059void AsianOptionTest::testAnalyticContinuousGeometricAveragePriceHeston() {
2060
2061 BOOST_TEST_MESSAGE("Testing analytic continuous geometric Asians under Heston...");
2062
2063 // data from "Pricing of Geometric Asian Options under Heston's Stochastic
2064 // Volatility Model", Kim & Wee, Quantitative Finance, 14:10, 1795-1809, 2011
2065
2066 // 73, 348 and 1095 are 0.2, 1.5 and 3.0 years respectively in Actual365Fixed
2067 Time days[] = {73, 73, 73, 73, 73, 548, 548, 548, 548, 548, 1095, 1095, 1095, 1095, 1095};
2068 Real strikes[] = {90.0, 95.0, 100.0, 105.0, 110.0, 90.0, 95.0, 100.0, 105.0, 110.0, 90.0, 95.0,
2069 100.0, 105.0, 110.0};
2070
2071 // Prices from Table 1 (params obey Feller condition)
2072 Real prices[] = {10.6571, 6.5871, 3.4478, 1.4552, 0.4724, 16.5030, 13.7625, 11.3374, 9.2245,
2073 7.4122, 20.5102, 18.3060, 16.2895, 14.4531, 12.7882};
2074
2075 // Prices from Table 4 (params do not obey Feller condition)
2076 Real prices_2[] = {10.6425, 6.4362, 3.1578, 1.1936, 0.3609, 14.9955, 11.6707, 8.7767, 6.3818,
2077 4.5118, 18.1219, 15.2009, 12.5707, 10.2539, 8.2611};
2078
2079 // 0.2 and 3.0 match to 1e-4. Unfortunatly 1.5 corresponds to 547.5 days, 547 and 548
2080 // bound the expected answer but are both out by ~5e-3
2081 Real tolerance = 1.0e-2;
2082
2083 DayCounter dc = Actual365Fixed();
2084 Date today = Settings::instance().evaluationDate();
2085 Option::Type type(Option::Call);
2086 Average::Type averageType = Average::Geometric;
2087
2088 Handle<Quote> spot(ext::shared_ptr<Quote>(new SimpleQuote(100)));
2089 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
2090 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
2091 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
2092 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
2093
2094 Real v0 = 0.09;
2095 Real kappa = 1.15;
2096 Real theta = 0.348;
2097 Real sigma = 0.39;
2098 Real rho = -0.64;
2099
2100 ext::shared_ptr<HestonProcess> hestonProcess(new
2101 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
2102 spot, v0, kappa, theta, sigma, rho));
2103
2104 ext::shared_ptr<AnalyticContinuousGeometricAveragePriceAsianHestonEngine> engine(new
2105 AnalyticContinuousGeometricAveragePriceAsianHestonEngine(hestonProcess));
2106
2107 for (Size i=0; i<LENGTH(strikes); i++) {
2108 Real strike = strikes[i];
2109 Time day = days[i];
2110 Real expected = prices[i];
2111
2112 Date expiryDate = today + day*Days;
2113
2114 ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(expiryDate));
2115 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
2116
2117 ContinuousAveragingAsianOption option(averageType, payoff, europeanExercise);
2118 option.setPricingEngine(engine);
2119
2120 Real calculated = option.NPV();
2121
2122 if (std::fabs(x: calculated-expected) > tolerance) {
2123 REPORT_FAILURE("value", averageType, 1.0, 0.0,
2124 std::vector<Date>(), payoff, europeanExercise, spot->value(),
2125 qRate->value(), rRate->value(), today,
2126 std::sqrt(v0), expected, calculated, tolerance);
2127 }
2128 }
2129
2130 Real v0_2 = 0.09;
2131 Real kappa_2 = 2.0;
2132 Real theta_2 = 0.09;
2133 Real sigma_2 = 1.0;
2134 Real rho_2 = -0.3;
2135
2136 ext::shared_ptr<HestonProcess> hestonProcess_2(new
2137 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
2138 spot, v0_2, kappa_2, theta_2, sigma_2, rho_2));
2139
2140 ext::shared_ptr<AnalyticContinuousGeometricAveragePriceAsianHestonEngine> engine_2(new
2141 AnalyticContinuousGeometricAveragePriceAsianHestonEngine(hestonProcess_2));
2142
2143 for (Size i=0; i<LENGTH(strikes); i++) {
2144 Real strike = strikes[i];
2145 Time day = days[i];
2146 Real expected = prices_2[i];
2147
2148 Date expiryDate = today + day*Days;
2149
2150 ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(expiryDate));
2151 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
2152
2153 ContinuousAveragingAsianOption option(averageType, payoff, europeanExercise);
2154 option.setPricingEngine(engine_2);
2155
2156 Real calculated = option.NPV();
2157
2158 if (std::fabs(x: calculated-expected) > tolerance) {
2159 REPORT_FAILURE("value", averageType, 1.0, 0.0,
2160 std::vector<Date>(), payoff, europeanExercise, spot->value(),
2161 qRate->value(), rRate->value(), today,
2162 std::sqrt(v0), expected, calculated, tolerance);
2163 }
2164 }
2165
2166 // Also test the continuous data from the authors' subsequent paper
2167
2168 // data from "A Recursive Method for Discretely Monitored Geometric Asian Option
2169 // Prices", Kim, Kim, Kim & Wee, Bull. Korean Math. Soc. 53, 733-749, 2016
2170
2171 // 73, 348 and 1095 are 0.2, 1.5 and 3.0 years respectively in Actual365Fixed
2172 Time days_3[] = {30, 91, 182, 365, 730, 1095, 30, 91, 182, 365, 730, 1095, 30,
2173 91, 182, 365, 730, 1095};
2174 Real strikes_3[] = {90, 90, 90, 90, 90, 90, 100, 100, 100, 100, 100, 100, 110,
2175 110, 110, 110, 110, 110};
2176
2177 // 30-day options need wider tolerance due to the day-bracket issue discussed above
2178 Real tol_3[] = {2.0e-2, 1.0e-2, 1.0e-2, 1.0e-2, 1.0e-2, 1.0e-2, 2.0e-2, 1.0e-2,
2179 1.0e-2, 1.0e-2, 1.0e-2, 1.0e-2, 2.0e-2, 1.0e-2, 1.0e-2, 1.0e-2,
2180 1.0e-2, 1.0e-2};
2181
2182 // Prices from Tables 1, 2 and 3
2183 Real prices_3[] = {10.1513, 10.8175, 11.8664, 13.5931, 16.0988, 17.9475, 2.0472,
2184 3.5735, 5.0588, 7.1132, 9.9139, 11.9959, 0.0350, 0.4869,
2185 1.3376, 2.8569, 5.2804, 7.2682};
2186
2187 // Note that although these parameters look similar to the first set above, theta
2188 // is a factor of 10 smaller. I guess there is a mis-transcription somewhere!
2189 Real v0_3 = 0.09;
2190 Real kappa_3 = 1.15;
2191 Real theta_3 = 0.0348;
2192 Real sigma_3 = 0.39;
2193 Real rho_3 = -0.64;
2194
2195 ext::shared_ptr<HestonProcess> hestonProcess_3(new
2196 HestonProcess(Handle<YieldTermStructure>(rTS), Handle<YieldTermStructure>(qTS),
2197 spot, v0_3, kappa_3, theta_3, sigma_3, rho_3));
2198
2199 ext::shared_ptr<AnalyticContinuousGeometricAveragePriceAsianHestonEngine> engine_3(new
2200 AnalyticContinuousGeometricAveragePriceAsianHestonEngine(hestonProcess_3));
2201
2202 for (Size i=0; i<LENGTH(strikes_3); i++) {
2203 Real strike = strikes_3[i];
2204 Time day = days_3[i];
2205 Real expected = prices_3[i];
2206 Real tolerance = tol_3[i];
2207
2208 Date expiryDate = today + day*Days;
2209
2210 ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(expiryDate));
2211 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
2212
2213 ContinuousAveragingAsianOption option(averageType, payoff, europeanExercise);
2214 option.setPricingEngine(engine_3);
2215
2216 Real calculated = option.NPV();
2217
2218 if (std::fabs(x: calculated-expected) > tolerance) {
2219 REPORT_FAILURE("value", averageType, 1.0, 0.0,
2220 std::vector<Date>(), payoff, europeanExercise, spot->value(),
2221 qRate->value(), rRate->value(), today,
2222 std::sqrt(v0), expected, calculated, tolerance);
2223 }
2224 }
2225
2226}
2227
2228namespace {
2229 struct DiscreteAverageDataTermStructure {
2230 Option::Type type;
2231 Real underlying;
2232 Real strike;
2233 Rate b;
2234 Rate riskFreeRate;
2235 Time first; // t1
2236 Time expiry;
2237 Size fixings;
2238 Volatility volatility;
2239 std::string slope;
2240 Real result;
2241 };
2242}
2243
2244void AsianOptionTest::testTurnbullWakemanAsianEngine() {
2245
2246 BOOST_TEST_MESSAGE("Testing Turnbull-Wakeman engine for discrete-time arithmetic average-rate "
2247 "Asians options with term structure support...");
2248
2249 // Data from Haug, "Option Pricing Formulas", Table 4-28, p.201
2250 // Type, underlying, strike, b, rfRate, t1, expiry, fixings, base vol, slope, expected result
2251 DiscreteAverageDataTermStructure cases[] = {
2252 {.type: Option::Call, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 19.5152},
2253 {.type: Option::Call, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 19.5063},
2254 {.type: Option::Call, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 19.5885},
2255 {.type: Option::Put, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 0.0090},
2256 {.type: Option::Put, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 0.0001},
2257 {.type: Option::Put, .underlying: 100, .strike: 80, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 0.0823},
2258
2259 {.type: Option::Call, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 10.1437},
2260 {.type: Option::Call, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 9.8313},
2261 {.type: Option::Call, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 10.7062},
2262 {.type: Option::Put, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 0.3906},
2263 {.type: Option::Put, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 0.0782},
2264 {.type: Option::Put, .underlying: 100, .strike: 90, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 0.9531},
2265
2266 {.type: Option::Call, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 3.2700},
2267 {.type: Option::Call, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 2.2819},
2268 {.type: Option::Call, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 4.3370},
2269 {.type: Option::Put, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 3.2700},
2270 {.type: Option::Put, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 2.2819},
2271 {.type: Option::Put, .underlying: 100, .strike: 100, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 4.3370},
2272
2273 {.type: Option::Call, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 0.5515},
2274 {.type: Option::Call, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 0.1314},
2275 {.type: Option::Call, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 1.2429},
2276 {.type: Option::Put, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 10.3046},
2277 {.type: Option::Put, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 9.8845},
2278 {.type: Option::Put, .underlying: 100, .strike: 110, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 10.9960},
2279
2280 {.type: Option::Call, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 0.0479},
2281 {.type: Option::Call, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 0.0016},
2282 {.type: Option::Call, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 0.2547},
2283 {.type: Option::Put, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "flat", .result: 19.5541},
2284 {.type: Option::Put, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "up", .result: 19.5078},
2285 {.type: Option::Put, .underlying: 100, .strike: 120, .b: 0, .riskFreeRate: 0.05, .first: 1.0 / 52, .expiry: 0.5, .fixings: 26, .volatility: 0.2, .slope: "down", .result: 19.7609}};
2286
2287 DayCounter dc = Actual360();
2288 Date today = Settings::instance().evaluationDate();
2289
2290 for (auto& l : cases) {
2291 Time dt = (l.expiry - l.first) / (l.fixings - 1);
2292 std::vector<Date> fixingDates(l.fixings);
2293 fixingDates[0] = today + timeToDays(t: l.first, daysPerYear: 360);
2294
2295 for (Size i = 1; i < l.fixings; i++) {
2296 fixingDates[i] = today + timeToDays(t: i * dt + l.first, daysPerYear: 360);
2297 }
2298
2299 // Set up market data
2300 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(l.underlying));
2301 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: l.b + l.riskFreeRate, dc);
2302 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: l.riskFreeRate, dc);
2303 ext::shared_ptr<BlackVolTermStructure> volTS;
2304 Volatility volSlope = 0.005;
2305 if (l.slope == "flat") {
2306 volTS = flatVol(today, volatility: l.volatility, dc);
2307 } else if (l.slope == "up") {
2308 std::vector<Volatility> volatilities(l.fixings);
2309 for (Size i = 0; i < l.fixings; ++i) {
2310 // Loop to fill a vector of vols from 7.5 % to 20 %
2311 volatilities[i] = l.volatility - (l.fixings - 1) * volSlope + i * volSlope;
2312 }
2313 volTS =
2314 ext::make_shared<BlackVarianceCurve>(args&: today, args&: fixingDates, args&: volatilities, args&: dc, args: true);
2315 } else if (l.slope == "down") {
2316 std::vector<Volatility> volatilities(l.fixings);
2317 for (Size i = 0; i < l.fixings; ++i) {
2318 // Loop to fill a vector of vols from 32.5 % to 20 %
2319 volatilities[i] = l.volatility + (l.fixings - 1) * volSlope - i * volSlope;
2320 }
2321 volTS =
2322 ext::make_shared<BlackVarianceCurve>(args&: today, args&: fixingDates, args&: volatilities, args&: dc, args: false);
2323 } else {
2324 QL_FAIL("unexpected slope type in engine test case");
2325 }
2326
2327 Average::Type averageType = Average::Arithmetic;
2328
2329 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(l.type, l.strike));
2330
2331 Date maturity = today + timeToDays(t: l.expiry, daysPerYear: 360);
2332
2333 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(maturity));
2334
2335 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new BlackScholesMertonProcess(
2336 Handle<Quote>(spot), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
2337 Handle<BlackVolTermStructure>(volTS)));
2338
2339 // Construct engine
2340 ext::shared_ptr<PricingEngine> engine(
2341 new TurnbullWakemanAsianEngine(stochProcess));
2342
2343 DiscreteAveragingAsianOption option(averageType, 0, 0, fixingDates, payoff, exercise);
2344 option.setPricingEngine(engine);
2345
2346 Real calculated = option.NPV();
2347 Real expected = l.result;
2348 Real tolerance = 2.5e-3;
2349 Real error = std::fabs(x: expected - calculated);
2350 if (error > tolerance) {
2351 BOOST_ERROR(
2352 "Failed to reproduce expected NPV:"
2353 << "\n type: " << l.type << "\n spot: " << l.underlying
2354 << "\n strike: " << l.strike << "\n dividend yield: "
2355 << l.b + l.riskFreeRate << "\n risk-free rate: " << l.riskFreeRate
2356 << "\n volatility: " << l.volatility << "\n slope: " << l.slope
2357 << "\n reference date: " << today << "\n expiry: " << l.expiry
2358 << "\n expected value: " << expected << "\n calculated: " << calculated
2359 << "\n error: " << error);
2360 }
2361
2362 // Compare greeks to numerical greeks
2363 Real dS = 0.001;
2364 Real delta = option.delta();
2365 Real gamma = option.gamma();
2366
2367 ext::shared_ptr<SimpleQuote> spotUp(new SimpleQuote(l.underlying+dS));
2368 ext::shared_ptr<SimpleQuote> spotDown(new SimpleQuote(l.underlying-dS));
2369
2370 ext::shared_ptr<BlackScholesMertonProcess> stochProcessUp(new BlackScholesMertonProcess(
2371 Handle<Quote>(spotUp), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
2372 Handle<BlackVolTermStructure>(volTS)));
2373
2374 ext::shared_ptr<BlackScholesMertonProcess> stochProcessDown(new BlackScholesMertonProcess(
2375 Handle<Quote>(spotDown), Handle<YieldTermStructure>(qTS), Handle<YieldTermStructure>(rTS),
2376 Handle<BlackVolTermStructure>(volTS)));
2377
2378 ext::shared_ptr<PricingEngine> engineUp(
2379 new TurnbullWakemanAsianEngine(stochProcessUp));
2380
2381 ext::shared_ptr<PricingEngine> engineDown(
2382 new TurnbullWakemanAsianEngine(stochProcessDown));
2383
2384 option.setPricingEngine(engineUp);
2385 Real calculatedUp = option.NPV();
2386
2387 option.setPricingEngine(engineDown);
2388 Real calculatedDown = option.NPV();
2389
2390 Real deltaBump = (calculatedUp - calculatedDown) / (2 * dS);
2391 Real gammaBump = (calculatedUp + calculatedDown - 2*calculated) / (dS * dS);
2392
2393 tolerance = 1.0e-6;
2394 Real deltaError = std::fabs(x: deltaBump - delta);
2395 if (deltaError > tolerance) {
2396 BOOST_ERROR(
2397 "Analytical delta failed to match bump delta:"
2398 << "\n type: " << l.type << "\n spot: " << l.underlying
2399 << "\n strike: " << l.strike << "\n dividend yield: "
2400 << l.b + l.riskFreeRate << "\n risk-free rate: " << l.riskFreeRate
2401 << "\n volatility: " << l.volatility << "\n slope: " << l.slope
2402 << "\n reference date: " << today << "\n expiry: " << l.expiry
2403 << "\n analytic delta: " << delta << "\n bump delta: " << deltaBump
2404 << "\n error: " << deltaError);
2405 }
2406
2407 Real gammaError = std::fabs(x: gammaBump - gamma);
2408 if (gammaError > tolerance) {
2409 BOOST_ERROR(
2410 "Analytical gamma failed to match bump gamma:"
2411 << "\n type: " << l.type << "\n spot: " << l.underlying
2412 << "\n strike: " << l.strike << "\n dividend yield: "
2413 << l.b + l.riskFreeRate << "\n risk-free rate: " << l.riskFreeRate
2414 << "\n volatility: " << l.volatility << "\n slope: " << l.slope
2415 << "\n reference date: " << today << "\n expiry: " << l.expiry
2416 << "\n analytic gamma: " << gamma << "\n bump gamma: " << gammaBump
2417 << "\n error: " << gammaError);
2418 }
2419 }
2420}
2421
2422test_suite* AsianOptionTest::suite(SpeedLevel speed) {
2423 auto* suite = BOOST_TEST_SUITE("Asian option tests");
2424
2425 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticContinuousGeometricAveragePrice));
2426 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticContinuousGeometricAveragePriceGreeks));
2427 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticDiscreteGeometricAveragePrice));
2428 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticDiscreteGeometricAverageStrike));
2429 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testMCDiscreteGeometricAveragePrice));
2430 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testMCDiscreteArithmeticAverageStrike));
2431 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticDiscreteGeometricAveragePriceGreeks));
2432 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testPastFixings));
2433 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAllFixingsInThePast));
2434 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testTurnbullWakemanAsianEngine));
2435 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testPastFixingsModelDependency));
2436
2437 if (speed <= Fast) {
2438 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testMCDiscreteArithmeticAveragePrice));
2439 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testMCDiscreteGeometricAveragePriceHeston));
2440 }
2441
2442 if (speed == Slow) {
2443 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testMCDiscreteArithmeticAveragePriceHeston));
2444 }
2445
2446 return suite;
2447}
2448
2449test_suite* AsianOptionTest::experimental(SpeedLevel speed) {
2450 auto* suite = BOOST_TEST_SUITE("Asian option experimental tests");
2451 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testLevyEngine));
2452 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testVecerEngine));
2453 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticContinuousGeometricAveragePriceHeston));
2454 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testAnalyticDiscreteGeometricAveragePriceHeston));
2455 suite->add(QUANTLIB_TEST_CASE(&AsianOptionTest::testDiscreteGeometricAveragePriceHestonPastFixings));
2456
2457 return suite;
2458}
2459

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