[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, 2005, 2008 StatPro Italia srl
5 Copyright (C) 2004 Ferdinando Ametrano
6 Copyright (C) 2004 Neil Firth
7
8 This file is part of QuantLib, a free-software/open-source library
9 for financial quantitative analysts and developers - http://quantlib.org/
10
11 QuantLib is free software: you can redistribute it and/or modify it
12 under the terms of the QuantLib license. You should have received a
13 copy of the license along with this program; if not, please email
14 <quantlib-dev@lists.sf.net>. The license is also available online at
15 <http://quantlib.org/license.shtml>.
16
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the license for more details.
20*/
21
22#include "basketoption.hpp"
23#include "utilities.hpp"
24#include <ql/quotes/simplequote.hpp>
25#include <ql/time/daycounters/actual360.hpp>
26#include <ql/instruments/basketoption.hpp>
27#include <ql/pricingengines/basket/stulzengine.hpp>
28#include <ql/pricingengines/basket/kirkengine.hpp>
29#include <ql/pricingengines/basket/mceuropeanbasketengine.hpp>
30#include <ql/pricingengines/basket/mcamericanbasketengine.hpp>
31#include <ql/processes/blackscholesprocess.hpp>
32#include <ql/processes/stochasticprocessarray.hpp>
33#include <ql/models/equity/hestonmodel.hpp>
34#include <ql/termstructures/yield/flatforward.hpp>
35#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
36#include <ql/termstructures/volatility/equityfx/hestonblackvolsurface.hpp>
37#include <ql/pricingengines/basket/fd2dblackscholesvanillaengine.hpp>
38#include <ql/utilities/dataformatters.hpp>
39
40using namespace QuantLib;
41using namespace boost::unit_test_framework;
42
43#undef REPORT_FAILURE_2
44#define REPORT_FAILURE_2(greekName, basketType, payoff, exercise, \
45 s1, s2, q1, q2, r, today, v1, v2, rho, \
46 expected, calculated, error, tolerance) \
47 BOOST_ERROR( \
48 exerciseTypeToString(exercise) << " " \
49 << payoff->optionType() << " option on " \
50 << basketTypeToString(basketType) \
51 << " with " << payoffTypeToString(payoff) << " payoff:\n" \
52 << "1st underlying value: " << s1 << "\n" \
53 << "2nd underlying value: " << s2 << "\n" \
54 << " strike: " << payoff->strike() << "\n" \
55 << " 1st dividend yield: " << io::rate(q1) << "\n" \
56 << " 2nd dividend yield: " << io::rate(q2) << "\n" \
57 << " risk-free rate: " << io::rate(r) << "\n" \
58 << " reference date: " << today << "\n" \
59 << " maturity: " << exercise->lastDate() << "\n" \
60 << "1st asset volatility: " << io::volatility(v1) << "\n" \
61 << "2nd asset volatility: " << io::volatility(v2) << "\n" \
62 << " correlation: " << rho << "\n\n" \
63 << " expected " << greekName << ": " << expected << "\n" \
64 << " calculated " << greekName << ": " << calculated << "\n"\
65 << " error: " << error << "\n" \
66 << " tolerance: " << tolerance);
67
68#undef REPORT_FAILURE_3
69#define REPORT_FAILURE_3(greekName, basketType, payoff, exercise, \
70 s1, s2, s3, r, today, v1, v2, v3, rho, \
71 expected, calculated, error, tolerance) \
72 BOOST_ERROR( \
73 exerciseTypeToString(exercise) << " " \
74 << payoff->optionType() << " option on " \
75 << basketTypeToString(basketType) \
76 << " with " << payoffTypeToString(payoff) << " payoff:\n" \
77 << "1st underlying value: " << s1 << "\n" \
78 << "2nd underlying value: " << s2 << "\n" \
79 << "3rd underlying value: " << s3 << "\n" \
80 << " strike: " << payoff->strike() <<"\n" \
81 << " risk-free rate: " << io::rate(r) << "\n" \
82 << " reference date: " << today << "\n" \
83 << " maturity: " << exercise->lastDate() << "\n" \
84 << "1st asset volatility: " << io::volatility(v1) << "\n" \
85 << "2nd asset volatility: " << io::volatility(v2) << "\n" \
86 << "3rd asset volatility: " << io::volatility(v3) << "\n" \
87 << " correlation: " << rho << "\n\n" \
88 << " expected " << greekName << ": " << expected << "\n" \
89 << " calculated " << greekName << ": " << calculated << "\n"\
90 << " error: " << error << "\n" \
91 << " tolerance: " << tolerance);
92
93
94namespace {
95
96 enum BasketType { MinBasket, MaxBasket, SpreadBasket };
97
98 std::string basketTypeToString(BasketType basketType) {
99 switch (basketType) {
100 case MinBasket:
101 return "MinBasket";
102 case MaxBasket:
103 return "MaxBasket";
104 case SpreadBasket:
105 return "Spread";
106 }
107 QL_FAIL("unknown basket option type");
108 }
109
110 ext::shared_ptr<BasketPayoff> basketTypeToPayoff(
111 BasketType basketType,
112 const ext::shared_ptr<Payoff> &p) {
113 switch (basketType) {
114 case MinBasket:
115 return ext::shared_ptr<BasketPayoff>(new MinBasketPayoff(p));
116 case MaxBasket:
117 return ext::shared_ptr<BasketPayoff>(new MaxBasketPayoff(p));
118 case SpreadBasket:
119 return ext::shared_ptr<BasketPayoff>(new SpreadBasketPayoff(p));
120 }
121 QL_FAIL("unknown basket option type");
122 }
123
124 struct BasketOptionOneData {
125 Option::Type type;
126 Real strike;
127 Real s; // spot
128 Rate q; // dividend
129 Rate r; // risk-free rate
130 Time t; // time to maturity
131 Volatility v; // volatility
132 Real result; // expected result
133 Real tol; // tolerance
134 };
135
136 struct BasketOptionTwoData {
137 BasketType basketType;
138 Option::Type type;
139 Real strike;
140 Real s1;
141 Real s2;
142 Rate q1;
143 Rate q2;
144 Rate r;
145 Time t; // years
146 Volatility v1;
147 Volatility v2;
148 Real rho;
149 Real result;
150 Real tol;
151 };
152
153 struct BasketOptionThreeData {
154 BasketType basketType;
155 Option::Type type;
156 Real strike;
157 Real s1;
158 Real s2;
159 Real s3;
160 Rate r;
161 Time t; // months
162 Volatility v1;
163 Volatility v2;
164 Volatility v3;
165 Real rho;
166 Real euroValue;
167 Real amValue;
168 };
169
170}
171
172
173void BasketOptionTest::testEuroTwoValues() {
174
175 BOOST_TEST_MESSAGE("Testing two-asset European basket options...");
176
177 /*
178 Data from:
179 Excel spreadsheet www.maths.ox.ac.uk/~firth/computing/excel.shtml
180 and
181 "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag 56-58
182 European two asset max basket options
183 */
184 BasketOptionTwoData values[] = {
185 // basketType, optionType, strike, s1, s2, q1, q2, r, t, v1, v2, rho, result, tol
186 // data from http://www.maths.ox.ac.uk/~firth/computing/excel.shtml
187 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.90, .result: 10.898, .tol: 1.0e-3},
188 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.70, .result: 8.483, .tol: 1.0e-3},
189 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.50, .result: 6.844, .tol: 1.0e-3},
190 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 5.531, .tol: 1.0e-3},
191 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 4.413, .tol: 1.0e-3},
192 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.50, .v2: 0.70, .rho: 0.00, .result: 4.981, .tol: 1.0e-3},
193 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.50, .v2: 0.30, .rho: 0.00, .result: 4.159, .tol: 1.0e-3},
194 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.50, .v2: 0.10, .rho: 0.00, .result: 2.597, .tol: 1.0e-3},
195 {.basketType: MinBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.50, .v2: 0.10, .rho: 0.50, .result: 4.030, .tol: 1.0e-3},
196
197 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.90, .result: 17.565, .tol: 1.0e-3},
198 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.70, .result: 19.980, .tol: 1.0e-3},
199 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.50, .result: 21.619, .tol: 1.0e-3},
200 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 22.932, .tol: 1.0e-3},
201 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 24.049, .tol: 1.1e-3},
202 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 80.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 16.508, .tol: 1.0e-3},
203 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 80.0, .s2: 80.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 8.049, .tol: 1.0e-3},
204 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 80.0, .s2: 120.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 30.141, .tol: 1.0e-3},
205 {.basketType: MaxBasket, .type: Option::Call, .strike: 100.0, .s1: 120.0, .s2: 120.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 42.889, .tol: 1.0e-3},
206
207 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.90, .result: 11.369, .tol: 1.0e-3},
208 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.70, .result: 12.856, .tol: 1.0e-3},
209 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.50, .result: 13.890, .tol: 1.0e-3},
210 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 14.741, .tol: 1.0e-3},
211 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 15.485, .tol: 1.0e-3},
212
213 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.50, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 11.893, .tol: 1.0e-3},
214 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.25, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 8.881, .tol: 1.0e-3},
215 {.basketType: MinBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 2.00, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 19.268, .tol: 1.0e-3},
216
217 {.basketType: MaxBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.90, .result: 7.339, .tol: 1.0e-3},
218 {.basketType: MaxBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.70, .result: 5.853, .tol: 1.0e-3},
219 {.basketType: MaxBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.50, .result: 4.818, .tol: 1.0e-3},
220 {.basketType: MaxBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.30, .result: 3.967, .tol: 1.1e-3},
221 {.basketType: MaxBasket, .type: Option::Put, .strike: 100.0, .s1: 100.0, .s2: 100.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 1.00, .v1: 0.30, .v2: 0.30, .rho: 0.10, .result: 3.223, .tol: 1.0e-3},
222
223 // basketType, optionType, strike, s1, s2, q1, q2, r, t, v1, v2, rho, result, tol
224 // data from "Option pricing formulas" VB code + spreadsheet
225 {.basketType: MinBasket, .type: Option::Call, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 4.8177, .tol: 1.0e-4},
226 {.basketType: MaxBasket, .type: Option::Call, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 11.6323, .tol: 1.0e-4},
227 {.basketType: MinBasket, .type: Option::Put, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 2.0376, .tol: 1.0e-4},
228 {.basketType: MaxBasket, .type: Option::Put, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.00, .q2: 0.00, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 0.5731, .tol: 1.0e-4},
229 {.basketType: MinBasket, .type: Option::Call, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.06, .q2: 0.09, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 2.9340, .tol: 1.0e-4},
230 {.basketType: MinBasket, .type: Option::Put, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.06, .q2: 0.09, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 3.5224, .tol: 1.0e-4},
231 // data from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag 58
232 {.basketType: MaxBasket, .type: Option::Call, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.06, .q2: 0.09, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 8.0701, .tol: 1.0e-4},
233 {.basketType: MaxBasket, .type: Option::Put, .strike: 98.0, .s1: 100.0, .s2: 105.0, .q1: 0.06, .q2: 0.09, .r: 0.05, .t: 0.50, .v1: 0.11, .v2: 0.16, .rho: 0.63, .result: 1.2181, .tol: 1.0e-4},
234
235 /* "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag 59-60
236 Kirk approx. for a european spread option on two futures*/
237
238 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.20, .rho: -0.5, .result: 4.7530, .tol: 1.0e-3},
239 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.20, .rho: 0.0, .result: 3.7970, .tol: 1.0e-3},
240 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.20, .rho: 0.5, .result: 2.5537, .tol: 1.0e-3},
241 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.25, .v2: 0.20, .rho: -0.5, .result: 5.4275, .tol: 1.0e-3},
242 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.25, .v2: 0.20, .rho: 0.0, .result: 4.3712, .tol: 1.0e-3},
243 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.25, .v2: 0.20, .rho: 0.5, .result: 3.0086, .tol: 1.0e-3},
244 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.25, .rho: -0.5, .result: 5.4061, .tol: 1.0e-3},
245 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.25, .rho: 0.0, .result: 4.3451, .tol: 1.0e-3},
246 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.1, .v1: 0.20, .v2: 0.25, .rho: 0.5, .result: 2.9723, .tol: 1.0e-3},
247 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.20, .rho: -0.5,.result: 10.7517, .tol: 1.0e-3},
248 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.20, .rho: 0.0, .result: 8.7020, .tol: 1.0e-3},
249 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.20, .rho: 0.5, .result: 6.0257, .tol: 1.0e-3},
250 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.25, .v2: 0.20, .rho: -0.5,.result: 12.1941, .tol: 1.0e-3},
251 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.25, .v2: 0.20, .rho: 0.0, .result: 9.9340, .tol: 1.0e-3},
252 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.25, .v2: 0.20, .rho: 0.5, .result: 7.0067, .tol: 1.0e-3},
253 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.25, .rho: -0.5,.result: 12.1483, .tol: 1.0e-3},
254 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.25, .rho: 0.0, .result: 9.8780, .tol: 1.0e-3},
255 {.basketType: SpreadBasket, .type: Option::Call, .strike: 3.0, .s1: 122.0, .s2: 120.0, .q1: 0.0, .q2: 0.0, .r: 0.10, .t: 0.5, .v1: 0.20, .v2: 0.25, .rho: 0.5, .result: 6.9284, .tol: 1.0e-3}
256 };
257
258 DayCounter dc = Actual360();
259 Date today = Date::todaysDate();
260
261 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
262 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
263
264 ext::shared_ptr<SimpleQuote> qRate1(new SimpleQuote(0.0));
265 ext::shared_ptr<YieldTermStructure> qTS1 = flatRate(today, forward: qRate1, dc);
266 ext::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0));
267 ext::shared_ptr<YieldTermStructure> qTS2 = flatRate(today, forward: qRate2, dc);
268
269 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
270 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
271
272 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
273 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
274 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
275 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(today, volatility: vol2, dc);
276
277 const Real mcRelativeErrorTolerance = 0.01;
278 const Real fdRelativeErrorTolerance = 0.01;
279
280 for (auto& value : values) {
281
282 ext::shared_ptr<PlainVanillaPayoff> payoff(
283 new PlainVanillaPayoff(value.type, value.strike));
284
285 Date exDate = today + timeToDays(t: value.t);
286 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
287
288 spot1->setValue(value.s1);
289 spot2->setValue(value.s2);
290 qRate1->setValue(value.q1);
291 qRate2->setValue(value.q2);
292 rRate->setValue(value.r);
293 vol1->setValue(value.v1);
294 vol2->setValue(value.v2);
295
296
297 ext::shared_ptr<PricingEngine> analyticEngine;
298 ext::shared_ptr<GeneralizedBlackScholesProcess> p1, p2;
299 switch (value.basketType) {
300 case MaxBasket:
301 case MinBasket:
302 p1 = ext::shared_ptr<GeneralizedBlackScholesProcess>(new BlackScholesMertonProcess(
303 Handle<Quote>(spot1), Handle<YieldTermStructure>(qTS1),
304 Handle<YieldTermStructure>(rTS), Handle<BlackVolTermStructure>(volTS1)));
305 p2 = ext::shared_ptr<GeneralizedBlackScholesProcess>(new BlackScholesMertonProcess(
306 Handle<Quote>(spot2), Handle<YieldTermStructure>(qTS2),
307 Handle<YieldTermStructure>(rTS), Handle<BlackVolTermStructure>(volTS2)));
308 analyticEngine = ext::shared_ptr<PricingEngine>(new StulzEngine(p1, p2, value.rho));
309 break;
310 case SpreadBasket:
311 p1 = ext::shared_ptr<GeneralizedBlackScholesProcess>(
312 new BlackProcess(Handle<Quote>(spot1), Handle<YieldTermStructure>(rTS),
313 Handle<BlackVolTermStructure>(volTS1)));
314 p2 = ext::shared_ptr<GeneralizedBlackScholesProcess>(
315 new BlackProcess(Handle<Quote>(spot2), Handle<YieldTermStructure>(rTS),
316 Handle<BlackVolTermStructure>(volTS2)));
317
318 analyticEngine = ext::shared_ptr<PricingEngine>(
319 new KirkEngine(ext::dynamic_pointer_cast<BlackProcess>(r: p1),
320 ext::dynamic_pointer_cast<BlackProcess>(r: p2), value.rho));
321 break;
322 default:
323 QL_FAIL("unknown basket type");
324 }
325
326 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = { p1, p2 };
327
328 Matrix correlationMatrix(2, 2, value.rho);
329 for (Integer j=0; j < 2; j++) {
330 correlationMatrix[j][j] = 1.0;
331 }
332
333 ext::shared_ptr<StochasticProcessArray> process(
334 new StochasticProcessArray(procs,correlationMatrix));
335
336 ext::shared_ptr<PricingEngine> mcEngine =
337 MakeMCEuropeanBasketEngine<PseudoRandom, Statistics>(process)
338 .withStepsPerYear(steps: 1)
339 .withSamples(samples: 10000)
340 .withSeed(seed: 42);
341
342 ext::shared_ptr<PricingEngine> fdEngine(
343 new Fd2dBlackScholesVanillaEngine(p1, p2, value.rho, 50, 50, 15));
344
345 BasketOption basketOption(basketTypeToPayoff(basketType: value.basketType, p: payoff), exercise);
346
347 // analytic engine
348 basketOption.setPricingEngine(analyticEngine);
349 Real calculated = basketOption.NPV();
350 Real expected = value.result;
351 Real error = std::fabs(x: calculated-expected);
352 if (error > value.tol) {
353 REPORT_FAILURE_2("value", value.basketType, payoff, exercise, value.s1, value.s2,
354 value.q1, value.q2, value.r, today, value.v1, value.v2, value.rho,
355 value.result, calculated, error, value.tol);
356 }
357
358 // fd engine
359 basketOption.setPricingEngine(fdEngine);
360 calculated = basketOption.NPV();
361 Real relError = relativeError(x1: calculated, x2: expected, reference: expected);
362 if (relError > mcRelativeErrorTolerance ) {
363 REPORT_FAILURE_2("FD value", value.basketType, payoff, exercise, value.s1, value.s2,
364 value.q1, value.q2, value.r, today, value.v1, value.v2, value.rho,
365 value.result, calculated, relError, fdRelativeErrorTolerance);
366 }
367
368 // mc engine
369 basketOption.setPricingEngine(mcEngine);
370 calculated = basketOption.NPV();
371 relError = relativeError(x1: calculated, x2: expected, reference: value.s1);
372 if (relError > mcRelativeErrorTolerance ) {
373 REPORT_FAILURE_2("MC value", value.basketType, payoff, exercise, value.s1, value.s2,
374 value.q1, value.q2, value.r, today, value.v1, value.v2, value.rho,
375 value.result, calculated, relError, mcRelativeErrorTolerance);
376 }
377 }
378}
379
380void BasketOptionTest::testBarraquandThreeValues() {
381
382 BOOST_TEST_MESSAGE("Testing three-asset basket options "
383 "against Barraquand's values...");
384
385 /*
386 Data from:
387 "Numerical Valuation of High Dimensional American Securities"
388 Barraquand, J. and Martineau, D.
389 Journal of Financial and Quantitative Analysis 1995 3(30) 383-405
390 */
391 BasketOptionThreeData values[] = {
392 // time in months is with 30 days to the month..
393 // basketType, optionType, strike, s1, s2, s3, r, t, v1, v2, v3, rho, euro, american,
394 // Table 2
395 // not using 4 month case to speed up test
396/*
397 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.0, 8.59, 8.59},
398 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.0, 3.84, 3.84},
399 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.0, 0.89, 0.89},
400 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.0, 12.55, 12.55},
401 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.0, 7.87, 7.87},
402 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.0, 4.26, 4.26},
403 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.0, 15.29, 15.29},
404 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.0, 10.72, 10.72},
405 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.0, 6.96, 6.96},
406*/
407/*
408 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 7.78, 7.78},
409 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 3.18, 3.18},
410 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 0.82, 0.82},
411 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 10.97, 10.97},
412 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 6.69, 6.69},
413 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 3.70, 3.70},
414 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.5, 13.23, 13.23},
415 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.5, 9.11, 9.11},
416 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.5, 5.98, 5.98},
417*/
418/*
419 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 6.53, 6.53},
420 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 2.38, 2.38},
421 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 0.74, 0.74},
422 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 8.51, 8.51},
423 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 4.92, 4.92},
424 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 2.97, 2.97},
425 {MaxBasket, Option::Call, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 10.04, 10.04},
426 {MaxBasket, Option::Call, 40.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 6.64, 6.64},
427 {MaxBasket, Option::Call, 45.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 4.61, 4.61},
428*/
429 // Table 3
430
431 {.basketType: MaxBasket, .type: Option::Put, .strike: 35.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 1.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 0.00, .amValue: 0.00},
432 {.basketType: MaxBasket, .type: Option::Put, .strike: 40.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 1.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 0.13, .amValue: 0.23},
433 {.basketType: MaxBasket, .type: Option::Put, .strike: 45.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 1.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 2.26, .amValue: 5.00},
434 //{MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.0, 0.01, 0.01},
435 {.basketType: MaxBasket, .type: Option::Put, .strike: 40.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 4.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 0.25, .amValue: 0.44},
436 {.basketType: MaxBasket, .type: Option::Put, .strike: 45.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 4.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 1.55, .amValue: 5.00},
437 //{MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.0, 0.03, 0.04},
438 //{MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.0, 0.31, 0.57},
439 {.basketType: MaxBasket, .type: Option::Put, .strike: 45.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 7.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.0, .euroValue: 1.41, .amValue: 5.00},
440
441/*
442 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 0.00, 0.00},
443 {MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 0.38, 0.48},
444 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 0.5, 3.00, 5.00},
445 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 0.07, 0.09},
446 {MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 0.72, 0.93},
447 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 0.5, 2.65, 5.00},
448 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.5, 0.17, 0.20},
449*/
450 {.basketType: MaxBasket, .type: Option::Put, .strike: 40.0, .s1: 40.0, .s2: 40.0, .s3: 40.0, .r: 0.05, .t: 7.00, .v1: 0.20, .v2: 0.30, .v3: 0.50, .rho: 0.5, .euroValue: 0.91, .amValue: 1.19},
451/*
452 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 0.5, 2.63, 5.00},
453
454 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 0.01, 0.01},
455 {MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 0.84, 0.08},
456 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 1.00, 0.20, 0.30, 0.50, 1.0, 4.18, 5.00},
457 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 0.19, 0.19},
458 {MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 1.51, 1.56},
459 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 4.00, 0.20, 0.30, 0.50, 1.0, 4.49, 5.00},
460 {MaxBasket, Option::Put, 35.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 0.41, 0.42},
461 {MaxBasket, Option::Put, 40.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 1.87, 1.96},
462 {MaxBasket, Option::Put, 45.0, 40.0, 40.0, 40.0, 0.05, 7.00, 0.20, 0.30, 0.50, 1.0, 4.70, 5.20}
463*/
464 };
465
466 DayCounter dc = Actual360();
467 Date today = Date::todaysDate();
468
469 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
470 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
471 ext::shared_ptr<SimpleQuote> spot3(new SimpleQuote(0.0));
472
473 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
474 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
475
476 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
477 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
478
479 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
480 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
481 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
482 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(today, volatility: vol2, dc);
483 ext::shared_ptr<SimpleQuote> vol3(new SimpleQuote(0.0));
484 ext::shared_ptr<BlackVolTermStructure> volTS3 = flatVol(today, volatility: vol3, dc);
485
486 for (auto& value : values) {
487
488 ext::shared_ptr<PlainVanillaPayoff> payoff(
489 new PlainVanillaPayoff(value.type, value.strike));
490
491 Date exDate = today + Integer(value.t) * 30;
492 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
493 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
494 exDate));
495
496 spot1->setValue(value.s1);
497 spot2->setValue(value.s2);
498 spot3->setValue(value.s3);
499 rRate->setValue(value.r);
500 vol1->setValue(value.v1);
501 vol2->setValue(value.v2);
502 vol3->setValue(value.v3);
503
504 ext::shared_ptr<StochasticProcess1D> stochProcess1(new
505 BlackScholesMertonProcess(Handle<Quote>(spot1),
506 Handle<YieldTermStructure>(qTS),
507 Handle<YieldTermStructure>(rTS),
508 Handle<BlackVolTermStructure>(volTS1)));
509
510 ext::shared_ptr<StochasticProcess1D> stochProcess2(new
511 BlackScholesMertonProcess(Handle<Quote>(spot2),
512 Handle<YieldTermStructure>(qTS),
513 Handle<YieldTermStructure>(rTS),
514 Handle<BlackVolTermStructure>(volTS2)));
515
516 ext::shared_ptr<StochasticProcess1D> stochProcess3(new
517 BlackScholesMertonProcess(Handle<Quote>(spot3),
518 Handle<YieldTermStructure>(qTS),
519 Handle<YieldTermStructure>(rTS),
520 Handle<BlackVolTermStructure>(volTS3)));
521
522 std::vector<ext::shared_ptr<StochasticProcess1D> > procs
523 = {stochProcess1, stochProcess2, stochProcess3 };
524
525 Matrix correlation(3, 3, value.rho);
526 for (Integer j=0; j < 3; j++) {
527 correlation[j][j] = 1.0;
528 }
529
530 ext::shared_ptr<StochasticProcessArray> process(
531 new StochasticProcessArray(procs,correlation));
532
533 // use a 3D sobol sequence...
534 // Think long and hard before moving to more than 1 timestep....
535 ext::shared_ptr<PricingEngine> mcQuasiEngine =
536 MakeMCEuropeanBasketEngine<LowDiscrepancy>(process)
537 .withStepsPerYear(steps: 1)
538 .withSamples(samples: 8091)
539 .withSeed(seed: 42);
540
541 BasketOption euroBasketOption(basketTypeToPayoff(basketType: value.basketType, p: payoff), exercise);
542 euroBasketOption.setPricingEngine(mcQuasiEngine);
543
544 Real expected = value.euroValue;
545 Real calculated = euroBasketOption.NPV();
546 Real relError = relativeError(x1: calculated, x2: expected, reference: value.s1);
547 Real mcRelativeErrorTolerance = 0.01;
548 if (relError > mcRelativeErrorTolerance ) {
549 REPORT_FAILURE_3("MC Quasi value", value.basketType, payoff, exercise, value.s1,
550 value.s2, value.s3, value.r, today, value.v1, value.v2, value.v3,
551 value.rho, value.euroValue, calculated, relError,
552 mcRelativeErrorTolerance);
553 }
554
555
556 Size requiredSamples = 1000;
557 Size timeSteps = 500;
558 BigNatural seed = 1;
559 ext::shared_ptr<PricingEngine> mcLSMCEngine =
560 MakeMCAmericanBasketEngine<>(process)
561 .withSteps(steps: timeSteps)
562 .withAntitheticVariate()
563 .withSamples(samples: requiredSamples)
564 .withCalibrationSamples(samples: requiredSamples/4)
565 .withSeed(seed);
566
567 BasketOption amBasketOption(basketTypeToPayoff(basketType: value.basketType, p: payoff), amExercise);
568 amBasketOption.setPricingEngine(mcLSMCEngine);
569
570 expected = value.amValue;
571 calculated = amBasketOption.NPV();
572 relError = relativeError(x1: calculated, x2: expected, reference: value.s1);
573 Real mcAmericanRelativeErrorTolerance = 0.01;
574 if (relError > mcAmericanRelativeErrorTolerance) {
575 REPORT_FAILURE_3("MC LSMC Value", value.basketType, payoff, exercise, value.s1,
576 value.s2, value.s3, value.r, today, value.v1, value.v2, value.v3,
577 value.rho, value.amValue, calculated, relError,
578 mcRelativeErrorTolerance);
579 }
580 }
581}
582
583void BasketOptionTest::testTavellaValues() {
584
585 BOOST_TEST_MESSAGE("Testing three-asset American basket options "
586 "against Tavella's values...");
587
588 /*
589 Data from:
590 "Quantitative Methods in Derivatives Pricing"
591 Tavella, D. A. - Wiley (2002)
592 */
593 BasketOptionThreeData values[] = {
594 // time in months is with 30 days to the month..
595 // basketType, optionType, strike, s1, s2, s3, r, t, v1, v2, v3, rho, euroValue, american Value,
596 {.basketType: MaxBasket, .type: Option::Call, .strike: 100, .s1: 100, .s2: 100, .s3: 100, .r: 0.05, .t: 3.00, .v1: 0.20, .v2: 0.20, .v3: 0.20, .rho: 0.0, .euroValue: -999, .amValue: 18.082}
597 };
598
599 DayCounter dc = Actual360();
600 Date today = Date::todaysDate();
601
602 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
603 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
604 ext::shared_ptr<SimpleQuote> spot3(new SimpleQuote(0.0));
605
606 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.1));
607 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
608
609 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
610 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
611
612 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
613 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
614 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
615 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(today, volatility: vol2, dc);
616 ext::shared_ptr<SimpleQuote> vol3(new SimpleQuote(0.0));
617 ext::shared_ptr<BlackVolTermStructure> volTS3 = flatVol(today, volatility: vol3, dc);
618
619 Real mcRelativeErrorTolerance = 0.01;
620 Size requiredSamples = 10000;
621 Size timeSteps = 20;
622 BigNatural seed = 0;
623
624
625 ext::shared_ptr<PlainVanillaPayoff> payoff(new
626 PlainVanillaPayoff(values[0].type, values[0].strike));
627
628 Date exDate = today + timeToDays(t: values[0].t);
629 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today, exDate));
630
631 spot1 ->setValue(values[0].s1);
632 spot2 ->setValue(values[0].s2);
633 spot3 ->setValue(values[0].s3);
634 vol1 ->setValue(values[0].v1);
635 vol2 ->setValue(values[0].v2);
636 vol3 ->setValue(values[0].v3);
637
638 ext::shared_ptr<StochasticProcess1D> stochProcess1(new
639 BlackScholesMertonProcess(Handle<Quote>(spot1),
640 Handle<YieldTermStructure>(qTS),
641 Handle<YieldTermStructure>(rTS),
642 Handle<BlackVolTermStructure>(volTS1)));
643
644 ext::shared_ptr<StochasticProcess1D> stochProcess2(new
645 BlackScholesMertonProcess(Handle<Quote>(spot2),
646 Handle<YieldTermStructure>(qTS),
647 Handle<YieldTermStructure>(rTS),
648 Handle<BlackVolTermStructure>(volTS2)));
649
650 ext::shared_ptr<StochasticProcess1D> stochProcess3(new
651 BlackScholesMertonProcess(Handle<Quote>(spot3),
652 Handle<YieldTermStructure>(qTS),
653 Handle<YieldTermStructure>(rTS),
654 Handle<BlackVolTermStructure>(volTS3)));
655
656 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1,
657 stochProcess2,
658 stochProcess3};
659
660 Matrix correlation(3,3, 0.0);
661 for (Integer j=0; j < 3; j++) {
662 correlation[j][j] = 1.0;
663 }
664 correlation[1][0] = -0.25;
665 correlation[0][1] = -0.25;
666 correlation[2][0] = 0.25;
667 correlation[0][2] = 0.25;
668 correlation[2][1] = 0.3;
669 correlation[1][2] = 0.3;
670
671 ext::shared_ptr<StochasticProcessArray> process(
672 new StochasticProcessArray(procs,correlation));
673 ext::shared_ptr<PricingEngine> mcLSMCEngine =
674 MakeMCAmericanBasketEngine<>(process)
675 .withSteps(steps: timeSteps)
676 .withAntitheticVariate()
677 .withSamples(samples: requiredSamples)
678 .withCalibrationSamples(samples: requiredSamples/4)
679 .withSeed(seed);
680
681 BasketOption basketOption(basketTypeToPayoff(basketType: values[0].basketType,
682 p: payoff),
683 exercise);
684 basketOption.setPricingEngine(mcLSMCEngine);
685
686 Real calculated = basketOption.NPV();
687 Real expected = values[0].amValue;
688 Real errorEstimate = basketOption.errorEstimate();
689 Real relError = relativeError(x1: calculated, x2: expected, reference: values[0].s1);
690 if (relError > mcRelativeErrorTolerance ) {
691 REPORT_FAILURE_3("MC LSMC Tavella value", values[0].basketType,
692 payoff, exercise, values[0].s1, values[0].s2,
693 values[0].s3, values[0].r, today, values[0].v1,
694 values[0].v2, values[0].v3, values[0].rho,
695 values[0].amValue, calculated, errorEstimate,
696 mcRelativeErrorTolerance);
697 }
698}
699
700namespace {
701 BasketOptionOneData oneDataValues[] = {
702 // type, strike, spot, q, r, t, vol, value, tol
703 { .type: Option::Put, .strike: 100.00, .s: 80.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 21.6059, .tol: 1e-2 },
704 { .type: Option::Put, .strike: 100.00, .s: 85.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 18.0374, .tol: 1e-2 },
705 { .type: Option::Put, .strike: 100.00, .s: 90.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 14.9187, .tol: 1e-2 },
706 { .type: Option::Put, .strike: 100.00, .s: 95.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 12.2314, .tol: 1e-2 },
707 { .type: Option::Put, .strike: 100.00, .s: 100.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 9.9458, .tol: 1e-2 },
708 { .type: Option::Put, .strike: 100.00, .s: 105.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 8.0281, .tol: 1e-2 },
709 { .type: Option::Put, .strike: 100.00, .s: 110.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 6.4352, .tol: 1e-2 },
710 { .type: Option::Put, .strike: 100.00, .s: 115.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 5.1265, .tol: 1e-2 },
711 { .type: Option::Put, .strike: 100.00, .s: 120.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 4.0611, .tol: 1e-2 },
712
713 // Longstaff Schwartz 1D example
714 // use constant and three Laguerre polynomials
715 // 100,000 paths and 50 timesteps per year
716 { .type: Option::Put, .strike: 40.00, .s: 36.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.2, .result: 4.478, .tol: 1e-2 },
717 { .type: Option::Put, .strike: 40.00, .s: 36.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.2, .result: 4.840, .tol: 1e-2 },
718 { .type: Option::Put, .strike: 40.00, .s: 36.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.4, .result: 7.101, .tol: 1e-2 },
719 { .type: Option::Put, .strike: 40.00, .s: 36.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.4, .result: 8.508, .tol: 1e-2 },
720
721 { .type: Option::Put, .strike: 40.00, .s: 38.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.2, .result: 3.250, .tol: 1e-2 },
722 { .type: Option::Put, .strike: 40.00, .s: 38.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.2, .result: 3.745, .tol: 1e-2 },
723 { .type: Option::Put, .strike: 40.00, .s: 38.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.4, .result: 6.148, .tol: 1e-2 },
724 { .type: Option::Put, .strike: 40.00, .s: 38.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.4, .result: 7.670, .tol: 1e-2 },
725
726 { .type: Option::Put, .strike: 40.00, .s: 40.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.2, .result: 2.314, .tol: 1e-2 },
727 { .type: Option::Put, .strike: 40.00, .s: 40.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.2, .result: 2.885, .tol: 1e-2 },
728 { .type: Option::Put, .strike: 40.00, .s: 40.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.4, .result: 5.312, .tol: 1e-2 },
729 { .type: Option::Put, .strike: 40.00, .s: 40.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.4, .result: 6.920, .tol: 1e-2 },
730
731 { .type: Option::Put, .strike: 40.00, .s: 42.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.2, .result: 1.617, .tol: 1e-2 },
732 { .type: Option::Put, .strike: 40.00, .s: 42.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.2, .result: 2.212, .tol: 1e-2 },
733 { .type: Option::Put, .strike: 40.00, .s: 42.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.4, .result: 4.582, .tol: 1e-2 },
734 { .type: Option::Put, .strike: 40.00, .s: 42.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.4, .result: 6.248, .tol: 1e-2 },
735
736 { .type: Option::Put, .strike: 40.00, .s: 44.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.2, .result: 1.110, .tol: 1e-2 },
737 { .type: Option::Put, .strike: 40.00, .s: 44.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.2, .result: 1.690, .tol: 1e-2 },
738 { .type: Option::Put, .strike: 40.00, .s: 44.00, .q: 0.0, .r: 0.06, .t: 1.0, .v: 0.4, .result: 3.948, .tol: 1e-2 },
739 { .type: Option::Put, .strike: 40.00, .s: 44.00, .q: 0.0, .r: 0.06, .t: 2.0, .v: 0.4, .result: 5.647, .tol: 1e-2 }
740 };
741}
742
743void BasketOptionTest::testOneDAmericanValues(std::size_t from, std::size_t to) {
744
745 BOOST_TEST_MESSAGE("Testing basket American options against 1-D case "
746 "from " << from << " to " << to-1 << "...");
747
748 DayCounter dc = Actual360();
749 Date today = Date::todaysDate();
750
751 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
752
753 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
754 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
755
756 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
757 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
758
759 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
760 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
761
762 Size requiredSamples = 10000;
763 Size timeSteps = 52;
764 BigNatural seed = 0;
765
766 ext::shared_ptr<StochasticProcess1D> stochProcess1(new
767 BlackScholesMertonProcess(Handle<Quote>(spot1),
768 Handle<YieldTermStructure>(qTS),
769 Handle<YieldTermStructure>(rTS),
770 Handle<BlackVolTermStructure>(volTS1)));
771
772 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1};
773
774 Matrix correlation(1, 1, 1.0);
775
776 ext::shared_ptr<StochasticProcessArray> process(
777 new StochasticProcessArray(procs,correlation));
778
779 ext::shared_ptr<PricingEngine> mcLSMCEngine =
780 MakeMCAmericanBasketEngine<>(process)
781 .withSteps(steps: timeSteps)
782 .withAntitheticVariate()
783 .withSamples(samples: requiredSamples)
784 .withCalibrationSamples(samples: requiredSamples/4)
785 .withSeed(seed);
786
787 for (Size i=from; i<to; i++) {
788 ext::shared_ptr<PlainVanillaPayoff> payoff(new
789 PlainVanillaPayoff(oneDataValues[i].type, oneDataValues[i].strike));
790
791 Date exDate = today + timeToDays(t: oneDataValues[i].t);
792 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today,
793 exDate));
794
795 spot1 ->setValue(oneDataValues[i].s);
796 vol1 ->setValue(oneDataValues[i].v);
797 rRate ->setValue(oneDataValues[i].r);
798 qRate ->setValue(oneDataValues[i].q);
799
800 BasketOption basketOption(// process,
801 basketTypeToPayoff(basketType: MaxBasket, p: payoff),
802 exercise);
803 basketOption.setPricingEngine(mcLSMCEngine);
804
805 Real calculated = basketOption.NPV();
806 Real expected = oneDataValues[i].result;
807 // Real errorEstimate = basketOption.errorEstimate();
808 Real relError = relativeError(x1: calculated, x2: expected, reference: oneDataValues[i].s);
809 // Real error = std::fabs(calculated-expected);
810
811 if (relError > oneDataValues[i].tol) {
812 BOOST_FAIL("expected value: " << oneDataValues[i].result << "\n"
813 << "calculated: " << calculated);
814 }
815
816 }
817}
818
819/* This unit test is a a regression test to check for a crash in
820 monte carlo if the required sample is odd. The crash occurred
821 because the samples array size was off by one when antithetic
822 paths were added.
823*/
824void BasketOptionTest::testOddSamples() {
825
826 BOOST_TEST_MESSAGE("Testing antithetic engine using odd sample number...");
827
828 Size requiredSamples = 10001; // The important line
829 Size timeSteps = 53;
830 BasketOptionOneData values[] = {
831 // type, strike, spot, q, r, t, vol, value, tol
832 { .type: Option::Put, .strike: 100.00, .s: 80.00, .q: 0.0, .r: 0.06, .t: 0.5, .v: 0.4, .result: 21.6059, .tol: 1e-2 }
833 };
834
835 DayCounter dc = Actual360();
836 Date today = Date::todaysDate();
837
838 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
839
840 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
841 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
842
843 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.05));
844 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
845
846 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
847 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
848
849
850
851 BigNatural seed = 0;
852
853 ext::shared_ptr<StochasticProcess1D> stochProcess1(new
854 BlackScholesMertonProcess(Handle<Quote>(spot1),
855 Handle<YieldTermStructure>(qTS),
856 Handle<YieldTermStructure>(rTS),
857 Handle<BlackVolTermStructure>(volTS1)));
858
859 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1};
860
861 Matrix correlation(1, 1, 1.0);
862
863 ext::shared_ptr<StochasticProcessArray> process(
864 new StochasticProcessArray(procs,correlation));
865
866 ext::shared_ptr<PricingEngine> mcLSMCEngine =
867 MakeMCAmericanBasketEngine<>(process)
868 .withSteps(steps: timeSteps)
869 .withAntitheticVariate()
870 .withSamples(samples: requiredSamples)
871 .withCalibrationSamples(samples: requiredSamples/4)
872 .withSeed(seed);
873
874 for (auto& value : values) {
875 ext::shared_ptr<PlainVanillaPayoff> payoff(
876 new PlainVanillaPayoff(value.type, value.strike));
877
878 Date exDate = today + timeToDays(t: value.t);
879 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today,
880 exDate));
881
882 spot1->setValue(value.s);
883 vol1->setValue(value.v);
884 rRate->setValue(value.r);
885 qRate->setValue(value.q);
886
887 BasketOption basketOption(// process,
888 basketTypeToPayoff(basketType: MaxBasket, p: payoff),
889 exercise);
890 basketOption.setPricingEngine(mcLSMCEngine);
891
892 Real calculated = basketOption.NPV();
893 Real expected = value.result;
894 // Real errorEstimate = basketOption.errorEstimate();
895 Real relError = relativeError(x1: calculated, x2: expected, reference: value.s);
896 // Real error = std::fabs(calculated-expected);
897
898 if (relError > value.tol) {
899 BOOST_FAIL("expected value: " << value.result << "\n"
900 << "calculated: " << calculated);
901 }
902 }
903}
904
905void BasketOptionTest::testLocalVolatilitySpreadOption() {
906
907 BOOST_TEST_MESSAGE("Testing 2D local-volatility spread-option pricing...");
908
909 const DayCounter dc = Actual360();
910 const Date today = Date(21, September, 2017);
911 const Date maturity = today + Period(3, Months);
912
913 const Handle<YieldTermStructure> riskFreeRate(flatRate(today, forward: 0.07, dc));
914 const Handle<YieldTermStructure> dividendYield(flatRate(today, forward: 0.03, dc));
915
916 const Handle<Quote> s1(ext::make_shared<SimpleQuote>(args: 100));
917 const Handle<Quote> s2(ext::make_shared<SimpleQuote>(args: 110));
918
919 const ext::shared_ptr<HestonModel> hm1(
920 ext::make_shared<HestonModel>(
921 args: ext::make_shared<HestonProcess>(
922 args: riskFreeRate, args: dividendYield,
923 args: s1, args: 0.09, args: 1.0, args: 0.06, args: 0.6, args: -0.75)));
924
925 const ext::shared_ptr<HestonModel> hm2(
926 ext::make_shared<HestonModel>(
927 args: ext::make_shared<HestonProcess>(
928 args: riskFreeRate, args: dividendYield,
929 args: s2, args: 0.1, args: 2.0, args: 0.07, args: 0.8, args: 0.85)));
930
931 const Handle<BlackVolTermStructure> vol1(
932 ext::make_shared<HestonBlackVolSurface>(args: Handle<HestonModel>(hm1)));
933
934 const Handle<BlackVolTermStructure> vol2(
935 ext::make_shared<HestonBlackVolSurface>(args: Handle<HestonModel>(hm2)));
936
937 BasketOption basketOption(
938 basketTypeToPayoff(
939 basketType: SpreadBasket,
940 p: ext::make_shared<PlainVanillaPayoff>(
941 args: Option::Call, args: s2->value() - s1->value())),
942 ext::make_shared<EuropeanExercise>(args: maturity));
943
944 const Real rho = -0.6;
945
946 const ext::shared_ptr<GeneralizedBlackScholesProcess> bs2(
947 ext::make_shared<GeneralizedBlackScholesProcess>(
948 args: s2, args: dividendYield, args: riskFreeRate, args: vol2));
949
950 const ext::shared_ptr<GeneralizedBlackScholesProcess> bs1(
951 ext::make_shared<GeneralizedBlackScholesProcess>(
952 args: s1, args: dividendYield, args: riskFreeRate, args: vol1));
953
954 basketOption.setPricingEngine(
955 ext::make_shared<Fd2dBlackScholesVanillaEngine>(
956 args: bs1, args: bs2, args: rho, args: 11, args: 11, args: 6, args: 0,
957 args: FdmSchemeDesc::Hundsdorfer(), args: true, args: 0.25));
958
959 const Real tolerance = 0.01;
960 const Real expected = 2.561;
961 const Real calculated = basketOption.NPV();
962
963 if (std::fabs(x: expected - calculated) > tolerance) {
964 BOOST_ERROR("Failed to reproduce expected local volatility price"
965 << "\n calculated: " << calculated
966 << "\n expected: " << expected
967 << "\n tolerance: " << tolerance);
968 }
969}
970
971void BasketOptionTest::test2DPDEGreeks() {
972
973 BOOST_TEST_MESSAGE("Testing Greeks of two-dimensional PDE engine...");
974
975 const Real s1 = 100;
976 const Real s2 = 100;
977 const Real r = 0.013;
978 const Volatility v = 0.2;
979 const Real rho = 0.5;
980 const Real strike = s1-s2;
981 const Size maturityInDays = 1095;
982
983 const DayCounter dc = Actual365Fixed();
984 const Date today = Date::todaysDate();
985 const Date maturity = today + maturityInDays;
986
987 const ext::shared_ptr<SimpleQuote> spot1(
988 ext::make_shared<SimpleQuote>(args: s1));
989 const ext::shared_ptr<SimpleQuote> spot2(
990 ext::make_shared<SimpleQuote>(args: s2));
991
992 const Handle<YieldTermStructure> rTS(flatRate(today, forward: r, dc));
993 const Handle<BlackVolTermStructure> vTS(flatVol(today, volatility: v, dc));
994
995 const ext::shared_ptr<BlackProcess> p1(
996 ext::make_shared<BlackProcess>(args: Handle<Quote>(spot1), args: rTS, args: vTS));
997
998 const ext::shared_ptr<BlackProcess> p2(
999 ext::make_shared<BlackProcess>(args: Handle<Quote>(spot2), args: rTS, args: vTS));
1000
1001 BasketOption option(
1002 ext::make_shared<SpreadBasketPayoff>(
1003 args: ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args: strike)),
1004 ext::make_shared<EuropeanExercise>(args: maturity));
1005
1006 option.setPricingEngine(
1007 ext::make_shared<Fd2dBlackScholesVanillaEngine>(args: p1, args: p2, args: rho));
1008
1009 const Real calculatedDelta = option.delta();
1010 const Real calculatedGamma = option.gamma();
1011
1012 option.setPricingEngine(ext::make_shared<KirkEngine>(args: p1, args: p2, args: rho));
1013
1014 const Real eps = 1.0;
1015 const Real npv = option.NPV();
1016
1017 spot1->setValue(s1 + eps);
1018 spot2->setValue(s2 + eps);
1019 const Real npvUp = option.NPV();
1020
1021 spot1->setValue(s1 - eps);
1022 spot2->setValue(s2 - eps);
1023 const Real npvDown = option.NPV();
1024
1025 const Real expectedDelta = (npvUp - npvDown)/(2*eps);
1026 const Real expectedGamma = (npvUp + npvDown - 2*npv)/(eps*eps);
1027
1028 const Real tol = 0.0005;
1029 if (std::fabs(x: expectedDelta - calculatedDelta) > tol) {
1030 BOOST_FAIL("failed to reproduce delta with 2dim PDE"
1031 << std::fixed << std::setprecision(8)
1032 << "\n calculated: " << calculatedDelta
1033 << "\n expected: " << expectedDelta
1034 << "\n tolerance: " << tol);
1035 }
1036
1037 if (std::fabs(x: expectedGamma - calculatedGamma) > tol) {
1038 BOOST_FAIL("failed to reproduce delta with 2dim PDE"
1039 << std::fixed << std::setprecision(8)
1040 << "\n calculated: " << calculatedGamma
1041 << "\n expected: " << expectedGamma
1042 << "\n tolerance: " << tol);
1043 }
1044}
1045
1046test_suite* BasketOptionTest::suite(SpeedLevel speed) {
1047 auto* suite = BOOST_TEST_SUITE("Basket option tests");
1048
1049 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::testEuroTwoValues));
1050 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::testTavellaValues));
1051
1052 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::testOddSamples));
1053 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::test2DPDEGreeks));
1054
1055 if (speed <= Fast) {
1056 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::testLocalVolatilitySpreadOption));
1057 // unrolled to get different test names
1058 suite->add(QUANTLIB_TEST_CASE([=](){ BasketOptionTest::testOneDAmericanValues( 0, 5); }));
1059 suite->add(QUANTLIB_TEST_CASE([=](){ BasketOptionTest::testOneDAmericanValues( 5, 11); }));
1060 suite->add(QUANTLIB_TEST_CASE([=](){ BasketOptionTest::testOneDAmericanValues(11, 17); }));
1061 suite->add(QUANTLIB_TEST_CASE([=](){ BasketOptionTest::testOneDAmericanValues(17, 23); }));
1062 suite->add(QUANTLIB_TEST_CASE([=](){ BasketOptionTest::testOneDAmericanValues(23, 29); }));
1063 }
1064
1065 if (speed == Slow) {
1066 suite->add(QUANTLIB_TEST_CASE(&BasketOptionTest::testBarraquandThreeValues));
1067 }
1068
1069 return suite;
1070}
1071

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