[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) 2010 Master IMAFA - Polytech'Nice Sophia - Université de Nice Sophia Antipolis
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "margrabeoption.hpp"
21#include "utilities.hpp"
22#include <ql/time/daycounters/actual360.hpp>
23#include <ql/instruments/margrabeoption.hpp>
24#include <ql/pricingengines/exotic/analyticamericanmargrabeengine.hpp>
25#include <ql/pricingengines/exotic/analyticeuropeanmargrabeengine.hpp>
26#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
27#include <ql/utilities/dataformatters.hpp>
28
29using namespace QuantLib;
30using namespace boost::unit_test_framework;
31
32#undef REPORT_FAILURE
33#define REPORT_FAILURE(greekName, exercise, \
34 s1, s2, Q1, Q2, q1, q2, r, today, v1, v2, rho, \
35 expected, calculated, error, tolerance) \
36 BOOST_ERROR( \
37 exerciseTypeToString(exercise) << " " \
38 << "Call option on Exchange Asset s2 for Asset s1" \
39 << " with null payoff:\n" \
40 << "1st underlying value: " << s1 << "\n" \
41 << "2nd underlying value: " << s2 << "\n" \
42 << "1st underlying quantity: " << Q1 << "\n" \
43 << "2nd underlying quantity: " << Q2 << "\n" \
44 << " 1st dividend yield: " << io::rate(q1) << "\n" \
45 << " 2nd dividend yield: " << io::rate(q2) << "\n" \
46 << " risk-free rate: " << io::rate(r) << "\n" \
47 << " reference date: " << today << "\n" \
48 << " maturity: " << exercise->lastDate() << "\n" \
49 << "1st asset volatility: " << io::volatility(v1) << "\n" \
50 << "2nd asset volatility: " << io::volatility(v2) << "\n" \
51 << " correlation: " << rho << "\n\n" \
52 << " expected " << greekName << ": " << expected << "\n" \
53 << " calculated " << greekName << ": " << calculated << "\n"\
54 << " error: " << error << "\n" \
55 << " tolerance: " << tolerance);
56
57#undef REPORT_FAILURE2
58#define REPORT_FAILURE2(greekName, exercise, s1, s2, q1, q2, r, today, \
59 v1, v2, expected, calculated, error, tolerance) \
60 BOOST_ERROR(exerciseTypeToString(exercise) << " " \
61 << " European option with " \
62 << " null pay off " << "\n" \
63 << " spot1 value: " << s1 << "\n" \
64 << " spot2 value: " << s2 << "\n" \
65 << " strike: 0 " << "\n" \
66 << " dividend yield 1: " << io::rate(q1) << "\n" \
67 << " dividend yield 2: " << io::rate(q2) << "\n" \
68 << " risk-free rate: " << io::rate(r) << "\n" \
69 << " reference date: " << today << "\n" \
70 << " maturity: " << exercise->lastDate() << "\n" \
71 << " volatility 1: " << io::volatility(v1) << "\n\n" \
72 << " volatility 2: " << io::volatility(v2) << "\n\n" \
73 << " expected " << greekName << ": " << expected << "\n" \
74 << " calculated " << greekName << ": " << calculated << "\n"\
75 << " error: " << error << "\n" \
76 << " tolerance: " << tolerance);
77
78namespace {
79
80 struct MargrabeOptionTwoData {
81 Real s1;
82 Real s2;
83 Integer Q1;
84 Integer Q2;
85 Rate q1;
86 Rate q2;
87 Rate r;
88 Time t; // years
89 Volatility v1;
90 Volatility v2;
91 Real rho;
92 Real result;
93 Real delta1;
94 Real delta2;
95 Real gamma1;
96 Real gamma2;
97 Real theta;
98 Real rho_greek;
99 Real tol;
100 };
101
102 struct MargrabeAmericanOptionTwoData {
103 Real s1;
104 Real s2;
105 Integer Q1;
106 Integer Q2;
107 Rate q1;
108 Rate q2;
109 Rate r;
110 Time t; // years
111 Volatility v1;
112 Volatility v2;
113 Real rho;
114 Real result;
115 Real tol;
116 };
117
118}
119
120void MargrabeOptionTest::testEuroExchangeTwoAssets() {
121
122 BOOST_TEST_MESSAGE("Testing European one-asset-for-another option...");
123
124 /*
125 Exchange-One-Asset-for-Another European Options
126 */
127 MargrabeOptionTwoData values[] = {
128 //Simplification : we assume that the option always exchanges S2 for S1
129 //s1, s2, Q1, Q2, q1, q2, r, t, v1, v2, rho, result,
130 //delta1, delta2, gamma1, gamma2, theta, rho, tol
131 // data from "given article p.52"
132 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: -0.50, .result: 2.125, .delta1: 0.841, .delta2: -0.818, .gamma1: 0.112, .gamma2: 0.135, .theta: -2.043, .rho_greek: 0.0, .tol: 1.0e-3},
133 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: -0.50, .result: 2.199, .delta1: 0.813, .delta2: -0.784, .gamma1: 0.109, .gamma2: 0.132, .theta: -2.723, .rho_greek: 0.0, .tol: 1.0e-3},
134 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: -0.50, .result: 2.283, .delta1: 0.788, .delta2: -0.753, .gamma1: 0.105, .gamma2: 0.126, .theta: -3.419, .rho_greek: 0.0, .tol: 1.0e-3},
135
136 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: 0.00, .result: 2.045, .delta1: 0.883, .delta2: -0.870, .gamma1: 0.108, .gamma2: 0.131, .theta: -1.168, .rho_greek: 0.0, .tol: 1.0e-3},
137 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: 0.00, .result: 2.091, .delta1: 0.857, .delta2: -0.838, .gamma1: 0.112, .gamma2: 0.135, .theta: -1.698, .rho_greek: 0.0, .tol: 1.0e-3},
138 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: 0.00, .result: 2.152, .delta1: 0.830, .delta2: -0.805, .gamma1: 0.111, .gamma2: 0.134, .theta: -2.302, .rho_greek: 0.0, .tol: 1.0e-3},
139
140 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: 0.50, .result: 1.974, .delta1: 0.946, .delta2: -0.942, .gamma1: 0.079, .gamma2: 0.096, .theta: -0.126, .rho_greek: 0.0, .tol: 1.0e-3},
141 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: 0.50, .result: 1.989, .delta1: 0.929, .delta2: -0.922, .gamma1: 0.092, .gamma2: 0.111, .theta: -0.398, .rho_greek: 0.0, .tol: 1.0e-3},
142 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: 0.50, .result: 2.019, .delta1: 0.902, .delta2: -0.891, .gamma1: 0.104, .gamma2: 0.125, .theta: -0.838, .rho_greek: 0.0, .tol: 1.0e-3},
143
144 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: -0.50, .result: 2.762, .delta1: 0.672, .delta2: -0.602, .gamma1: 0.072, .gamma2: 0.087, .theta: -1.207, .rho_greek: 0.0, .tol: 1.0e-3},
145 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: -0.50, .result: 2.989, .delta1: 0.661, .delta2: -0.578, .gamma1: 0.064, .gamma2: 0.078, .theta: -1.457, .rho_greek: 0.0, .tol: 1.0e-3},
146 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: -0.50, .result: 3.228, .delta1: 0.653, .delta2: -0.557, .gamma1: 0.058, .gamma2: 0.070, .theta: -1.712, .rho_greek: 0.0, .tol: 1.0e-3},
147
148 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: 0.00, .result: 2.479, .delta1: 0.695, .delta2: -0.640, .gamma1: 0.085, .gamma2: 0.102, .theta: -0.874, .rho_greek: 0.0, .tol: 1.0e-3},
149 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: 0.00, .result: 2.650, .delta1: 0.680, .delta2: -0.616, .gamma1: 0.077, .gamma2: 0.093, .theta: -1.078, .rho_greek: 0.0, .tol: 1.0e-3},
150 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: 0.00, .result: 2.847, .delta1: 0.668, .delta2: -0.592, .gamma1: 0.069, .gamma2: 0.083, .theta: -1.302, .rho_greek: 0.0, .tol: 1.0e-3},
151
152 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: 0.50, .result: 2.138, .delta1: 0.746, .delta2: -0.713, .gamma1: 0.106, .gamma2: 0.128, .theta: -0.416, .rho_greek: 0.0, .tol: 1.0e-3},
153 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: 0.50, .result: 2.231, .delta1: 0.728, .delta2: -0.689, .gamma1: 0.099, .gamma2: 0.120, .theta: -0.550, .rho_greek: 0.0, .tol: 1.0e-3},
154 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: 0.50, .result: 2.374, .delta1: 0.707, .delta2: -0.659, .gamma1: 0.090, .gamma2: 0.109, .theta: -0.741, .rho_greek: 0.0, .tol: 1.0e-3},
155
156 //Quantity tests from Excel calcuations
157 {.s1: 22.0, .s2: 10.0, .Q1: 1, .Q2: 2, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: 0.50, .result: 2.138, .delta1: 0.746, .delta2: -1.426, .gamma1: 0.106, .gamma2: 0.255, .theta: -0.987, .rho_greek: 0.0, .tol: 1.0e-3},
158 {.s1: 11.0, .s2: 20.0, .Q1: 2, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: 0.50, .result: 2.231, .delta1: 1.455, .delta2: -0.689, .gamma1: 0.198, .gamma2: 0.120, .theta: 0.410, .rho_greek: 0.0, .tol: 1.0e-3},
159 {.s1: 11.0, .s2: 10.0, .Q1: 2, .Q2: 2, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: 0.50, .result: 2.374, .delta1: 1.413, .delta2: -1.317, .gamma1: 0.181, .gamma2: 0.219, .theta: -0.336, .rho_greek: 0.0, .tol: 1.0e-3}
160 };
161
162 DayCounter dc = Actual360();
163 Date today = Settings::instance().evaluationDate();
164
165 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
166 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
167
168 ext::shared_ptr<SimpleQuote> qRate1(new SimpleQuote(0.0));
169 ext::shared_ptr<YieldTermStructure> qTS1 = flatRate(today, forward: qRate1, dc);
170 ext::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0));
171 ext::shared_ptr<YieldTermStructure> qTS2 = flatRate(today, forward: qRate2, dc);
172
173 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
174 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
175
176 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
177 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
178 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
179 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(today, volatility: vol2, dc);
180
181 for (auto& value : values) {
182
183 Date exDate = today + timeToDays(t: value.t);
184 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
185
186 spot1->setValue(value.s1);
187 spot2->setValue(value.s2);
188 qRate1->setValue(value.q1);
189 qRate2->setValue(value.q2);
190 rRate->setValue(value.r);
191 vol1->setValue(value.v1);
192 vol2->setValue(value.v2);
193
194 ext::shared_ptr<BlackScholesMertonProcess> stochProcess1(new
195 BlackScholesMertonProcess(Handle<Quote>(spot1),
196 Handle<YieldTermStructure>(qTS1),
197 Handle<YieldTermStructure>(rTS),
198 Handle<BlackVolTermStructure>(volTS1)));
199
200 ext::shared_ptr<BlackScholesMertonProcess> stochProcess2(new
201 BlackScholesMertonProcess(Handle<Quote>(spot2),
202 Handle<YieldTermStructure>(qTS2),
203 Handle<YieldTermStructure>(rTS),
204 Handle<BlackVolTermStructure>(volTS2)));
205
206 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1, stochProcess2};
207
208 Matrix correlationMatrix(2, 2, value.rho);
209 for (Integer j=0; j < 2; j++) {
210 correlationMatrix[j][j] = 1.0;
211 }
212
213 ext::shared_ptr<PricingEngine> engine(
214 new AnalyticEuropeanMargrabeEngine(stochProcess1, stochProcess2, value.rho));
215
216 MargrabeOption margrabeOption(value.Q1, value.Q2, exercise);
217
218 // analytic engine
219 margrabeOption.setPricingEngine(engine);
220
221 Real calculated = margrabeOption.NPV();
222 Real expected = value.result;
223 Real error = std::fabs(x: calculated-expected);
224 Real tolerance = value.tol;
225 if (error > tolerance) {
226 REPORT_FAILURE("value", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
227 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
228 calculated, error, tolerance);
229 }
230
231 calculated = margrabeOption.delta1();
232 expected = value.delta1;
233 error= std::fabs(x: calculated-expected);
234 if (error>tolerance) {
235 REPORT_FAILURE("delta1", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
236 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
237 calculated, error, tolerance);
238 }
239
240 calculated = margrabeOption.delta2();
241 expected = value.delta2;
242 error= std::fabs(x: calculated-expected);
243 if (error>tolerance) {
244 REPORT_FAILURE("delta2", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
245 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
246 calculated, error, tolerance);
247 }
248
249 calculated = margrabeOption.gamma1();
250 expected = value.gamma1;
251 error= std::fabs(x: calculated-expected);
252 if (error>tolerance) {
253 REPORT_FAILURE("gamma1", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
254 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
255 calculated, error, tolerance);
256 }
257
258 calculated = margrabeOption.gamma2();
259 expected = value.gamma2;
260 error= std::fabs(x: calculated-expected);
261 if (error>tolerance) {
262 REPORT_FAILURE("gamma2", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
263 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
264 calculated, error, tolerance);
265 }
266
267 calculated = margrabeOption.theta();
268 expected = value.theta;
269 error= std::fabs(x: calculated-expected);
270 if (error>tolerance) {
271 REPORT_FAILURE("theta", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
272 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
273 calculated, error, tolerance);
274 }
275
276 calculated = margrabeOption.rho();
277 expected = value.rho_greek;
278 error= std::fabs(x: calculated-expected);
279 if (error>tolerance) {
280 REPORT_FAILURE("rho_greek", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
281 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
282 calculated, error, tolerance);
283 }
284 }
285}
286
287void MargrabeOptionTest::testGreeks() {
288
289 BOOST_TEST_MESSAGE("Testing analytic European exchange option greeks...");
290
291 std::map<std::string,Real> calculated, expected, tolerance;
292 tolerance["delta1"] = 1.0e-5;
293 tolerance["delta2"] = 1.0e-5;
294 tolerance["gamma1"] = 1.0e-5;
295 tolerance["gamma2"] = 1.0e-5;
296 tolerance["theta"] = 1.0e-5;
297 tolerance["rho"] = 1.0e-5;
298
299 Real underlyings1[] = { 22.0 };
300 Real underlyings2[] = { 20.0 };
301 Rate qRates1[] = { 0.06, 0.16, 0.04 };
302 Rate qRates2[] = { 0.04, 0.14, 0.02 };
303 Rate rRates[] = { 0.1, 0.2, 0.08 };
304 Time residualTimes[] = { 0.1, 0.5 };
305 Volatility vols1[] = { 0.20 };
306 Volatility vols2[] = { 0.15, 0.20, 0.25};
307
308 DayCounter dc = Actual360();
309 Date today = Date::todaysDate();
310 Settings::instance().evaluationDate() = today;
311
312 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
313 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
314
315 ext::shared_ptr<SimpleQuote> qRate1(new SimpleQuote(0.0));
316 ext::shared_ptr<YieldTermStructure> qTS1 = flatRate(forward: qRate1, dc);
317 ext::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0));
318 ext::shared_ptr<YieldTermStructure> qTS2 = flatRate(forward: qRate2, dc);
319
320 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
321 ext::shared_ptr<YieldTermStructure> rTS = flatRate(forward: rRate, dc);
322
323 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
324 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(volatility: vol1, dc);
325 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
326 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(volatility: vol2, dc);
327
328 for (Real residualTime : residualTimes) {
329 Date exDate = today + timeToDays(t: residualTime);
330 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
331
332 // option to check
333 ext::shared_ptr<BlackScholesMertonProcess> stochProcess1(new BlackScholesMertonProcess(
334 Handle<Quote>(spot1), Handle<YieldTermStructure>(qTS1), Handle<YieldTermStructure>(rTS),
335 Handle<BlackVolTermStructure>(volTS1)));
336
337 ext::shared_ptr<BlackScholesMertonProcess> stochProcess2(new BlackScholesMertonProcess(
338 Handle<Quote>(spot2), Handle<YieldTermStructure>(qTS2), Handle<YieldTermStructure>(rTS),
339 Handle<BlackVolTermStructure>(volTS2)));
340
341 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1, stochProcess2};
342
343 // The correlation -0.5 can be different real between -1 and 1 for more tests
344 Real correlation = -0.5;
345 Matrix correlationMatrix(2, 2, correlation);
346 for (Integer j = 0; j < 2; j++) {
347 correlationMatrix[j][j] = 1.0;
348
349 ext::shared_ptr<PricingEngine> engine(
350 new AnalyticEuropeanMargrabeEngine(stochProcess1, stochProcess2, correlation));
351
352 // The quantities of S1 and S2 can be different from 1 & 1 for more tests
353 MargrabeOption margrabeOption(1, 1, exercise);
354
355 // analytic engine
356 margrabeOption.setPricingEngine(engine);
357
358 for (Size l = 0; l < LENGTH(underlyings1); l++) {
359 for (Size m=0; m<LENGTH(qRates1); m++) {
360 for (Real n : rRates) {
361 for (Size p = 0; p < LENGTH(vols1); p++) {
362 Real u1 = underlyings1[l], u2 = underlyings2[l], u;
363 Rate q1 = qRates1[m], q2 = qRates2[m], r = n;
364 Volatility v1 = vols1[p], v2 = vols2[p];
365
366 spot1->setValue(u1);
367 spot2->setValue(u2);
368 qRate1->setValue(q1);
369 qRate2->setValue(q2);
370 rRate->setValue(r);
371 vol1->setValue(v1);
372 vol2->setValue(v2);
373
374 Real value = margrabeOption.NPV();
375
376 calculated["delta1"] = margrabeOption.delta1();
377 calculated["delta2"] = margrabeOption.delta2();
378 calculated["gamma1"] = margrabeOption.gamma1();
379 calculated["gamma2"] = margrabeOption.gamma2();
380 calculated["theta"] = margrabeOption.theta();
381 calculated["rho"] = margrabeOption.rho();
382
383 if (value > spot1->value() * 1.0e-5) {
384 // perturb spot and get delta1 and gamma
385 u = u1;
386 Real du = u * 1.0e-4;
387 spot1->setValue(u + du);
388 Real value_p = margrabeOption.NPV(),
389 delta_p = margrabeOption.delta1();
390 spot1->setValue(u - du);
391 Real value_m = margrabeOption.NPV(),
392 delta_m = margrabeOption.delta1();
393 spot1->setValue(u);
394 expected["delta1"] = (value_p - value_m) / (2 * du);
395 expected["gamma1"] = (delta_p - delta_m) / (2 * du);
396
397 u = u2;
398 spot2->setValue(u + du);
399 value_p = margrabeOption.NPV();
400 delta_p = margrabeOption.delta2();
401 spot2->setValue(u - du);
402 value_m = margrabeOption.NPV();
403 delta_m = margrabeOption.delta2();
404 spot2->setValue(u);
405 expected["delta2"] = (value_p - value_m) / (2 * du);
406 expected["gamma2"] = (delta_p - delta_m) / (2 * du);
407
408 // perturb rates and get rho
409 Spread dr = r * 1.0e-4;
410 rRate->setValue(r + dr);
411 value_p = margrabeOption.NPV();
412 rRate->setValue(r - dr);
413 value_m = margrabeOption.NPV();
414 rRate->setValue(r);
415 expected["rho"] = (value_p - value_m) / (2 * dr);
416
417 // perturb date and get theta
418 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
419 Settings::instance().evaluationDate() = today - 1;
420 value_m = margrabeOption.NPV();
421 Settings::instance().evaluationDate() = today + 1;
422 value_p = margrabeOption.NPV();
423 Settings::instance().evaluationDate() = today;
424 expected["theta"] = (value_p - value_m) / dT;
425
426 // compare
427 std::map<std::string, Real>::iterator it;
428 for (it = calculated.begin(); it != calculated.end(); ++it) {
429 std::string greek = it->first;
430 Real expct = expected[greek], calcl = calculated[greek],
431 tol = tolerance[greek];
432 Real error = relativeError(x1: expct, x2: calcl, reference: u1);
433 if (error > tol) {
434 REPORT_FAILURE2(greek, exercise, u1, u2, q1, q2, r, today,
435 v1, v2, expct, calcl, error, tol);
436 }
437 }
438 }
439 }
440 }
441 }
442 }
443 }
444 }
445}
446
447void MargrabeOptionTest::testAmericanExchangeTwoAssets() {
448
449 BOOST_TEST_MESSAGE("Testing American one-asset-for-another option...");
450
451 MargrabeAmericanOptionTwoData values[] = {
452 //Simplification : we assume that the option always exchanges S2 for S1
453 //s1, s2, Q1, Q2, q1, q2, r, t, v1, v2, rho, result, tol
454 // data from Haug
455
456 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: -0.50, .result: 2.1357, .tol: 1.0e-3},
457 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: -0.50, .result: 2.2074, .tol: 1.0e-3},
458 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: -0.50, .result: 2.2902, .tol: 1.0e-3},
459
460 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: 0.00, .result: 2.0592, .tol: 1.0e-3},
461 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: 0.00, .result: 2.1032, .tol: 1.0e-3},
462 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: 0.00, .result: 2.1618, .tol: 1.0e-3},
463
464 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.15, .rho: 0.50, .result: 2.0001, .tol: 1.0e-3},
465 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.20, .rho: 0.50, .result: 2.0110, .tol: 1.0e-3},
466 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.10, .v1: 0.20, .v2: 0.25, .rho: 0.50, .result: 2.0359, .tol: 1.0e-3},
467
468 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: -0.50, .result: 2.8051, .tol: 1.0e-3},
469 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: -0.50, .result: 3.0288, .tol: 1.0e-3},
470 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: -0.50, .result: 3.2664, .tol: 1.0e-3},
471
472 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: 0.00, .result: 2.5282, .tol: 1.0e-3},
473 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: 0.00, .result: 2.6945, .tol: 1.0e-3},
474 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: 0.00, .result: 2.8893, .tol: 1.0e-3},
475
476 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.15, .rho: 0.50, .result: 2.2053, .tol: 1.0e-3},
477 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.20, .rho: 0.50, .result: 2.2906, .tol: 1.0e-3},
478 {.s1: 22.0, .s2: 20.0, .Q1: 1, .Q2: 1, .q1: 0.06, .q2: 0.04, .r: 0.10, .t: 0.50, .v1: 0.20, .v2: 0.25, .rho: 0.50, .result: 2.4261, .tol: 1.0e-3}
479 };
480
481 Date today = Settings::instance().evaluationDate();
482 DayCounter dc = Actual360();
483 ext::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0));
484 ext::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0));
485
486 ext::shared_ptr<SimpleQuote> qRate1(new SimpleQuote(0.0));
487 ext::shared_ptr<YieldTermStructure> qTS1 = flatRate(today, forward: qRate1, dc);
488 ext::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0));
489 ext::shared_ptr<YieldTermStructure> qTS2 = flatRate(today, forward: qRate2, dc);
490
491 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
492 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
493
494 ext::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0));
495 ext::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(today, volatility: vol1, dc);
496 ext::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0));
497 ext::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(today, volatility: vol2, dc);
498
499 for (auto& value : values) {
500
501 Date exDate = today + timeToDays(t: value.t);
502 ext::shared_ptr<Exercise> exercise(new AmericanExercise(today, exDate));
503
504 spot1->setValue(value.s1);
505 spot2->setValue(value.s2);
506 qRate1->setValue(value.q1);
507 qRate2->setValue(value.q2);
508 rRate->setValue(value.r);
509 vol1->setValue(value.v1);
510 vol2->setValue(value.v2);
511
512 ext::shared_ptr<BlackScholesMertonProcess> stochProcess1(new
513 BlackScholesMertonProcess(Handle<Quote>(spot1),
514 Handle<YieldTermStructure>(qTS1),
515 Handle<YieldTermStructure>(rTS),
516 Handle<BlackVolTermStructure>(volTS1)));
517
518 ext::shared_ptr<BlackScholesMertonProcess> stochProcess2(new
519 BlackScholesMertonProcess(Handle<Quote>(spot2),
520 Handle<YieldTermStructure>(qTS2),
521 Handle<YieldTermStructure>(rTS),
522 Handle<BlackVolTermStructure>(volTS2)));
523
524 std::vector<ext::shared_ptr<StochasticProcess1D> > procs = {stochProcess1,stochProcess2};
525
526 Matrix correlationMatrix(2, 2, value.rho);
527 for (Integer j=0; j < 2; j++) {
528 correlationMatrix[j][j] = 1.0;
529 }
530
531 ext::shared_ptr<PricingEngine> engine(
532 new AnalyticAmericanMargrabeEngine(stochProcess1, stochProcess2, value.rho));
533
534 MargrabeOption margrabeOption(value.Q1, value.Q2, exercise);
535
536 // analytic engine
537 margrabeOption.setPricingEngine(engine);
538
539 Real calculated = margrabeOption.NPV();
540 Real expected = value.result;
541 Real error = std::fabs(x: calculated-expected);
542 Real tolerance = value.tol;
543 if (error > tolerance) {
544 REPORT_FAILURE("value", exercise, value.s1, value.s2, value.Q1, value.Q2, value.q1,
545 value.q2, value.r, today, value.v1, value.v2, value.rho, expected,
546 calculated, error, tolerance);
547 }
548 }
549}
550
551test_suite* MargrabeOptionTest::suite() {
552 auto* suite = BOOST_TEST_SUITE("Exchange option tests");
553 suite->add(
554 QUANTLIB_TEST_CASE(&MargrabeOptionTest::testEuroExchangeTwoAssets));
555 suite->add(
556 QUANTLIB_TEST_CASE(&MargrabeOptionTest::testAmericanExchangeTwoAssets));
557 suite->add(QUANTLIB_TEST_CASE(&MargrabeOptionTest::testGreeks));
558 return suite;
559}
560

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