[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) 2004 Ferdinando Ametrano
5 Copyright (C) 2004, 2007 StatPro Italia srl
6 Copyright (C) 2008 Paul Farrington
7 Copyright (C) 2014 Thema Consulting SA
8 Copyright (C) 2019 Klaus Spanderen
9
10 This file is part of QuantLib, a free-software/open-source library
11 for financial quantitative analysts and developers - http://quantlib.org/
12
13 QuantLib is free software: you can redistribute it and/or modify it
14 under the terms of the QuantLib license. You should have received a
15 copy of the license along with this program; if not, please email
16 <quantlib-dev@lists.sf.net>. The license is also available online at
17 <http://quantlib.org/license.shtml>.
18
19 This program is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 FOR A PARTICULAR PURPOSE. See the license for more details.
22*/
23
24#include "quantooption.hpp"
25#include "utilities.hpp"
26#include <ql/time/daycounters/actual360.hpp>
27#include <ql/instruments/quantovanillaoption.hpp>
28#include <ql/instruments/quantoforwardvanillaoption.hpp>
29#include <ql/instruments/quantobarrieroption.hpp>
30#include <ql/experimental/barrieroption/quantodoublebarrieroption.hpp>
31#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
32#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
33#include <ql/pricingengines/vanilla/fdhestonvanillaengine.hpp>
34#include <ql/pricingengines/barrier/analyticbarrierengine.hpp>
35#include <ql/pricingengines/barrier/analyticdoublebarrierengine.hpp>
36#include <ql/pricingengines/quanto/quantoengine.hpp>
37#include <ql/pricingengines/forward/forwardperformanceengine.hpp>
38#include <ql/termstructures/yield/flatforward.hpp>
39#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
40#include <ql/termstructures/volatility/equityfx/localconstantvol.hpp>
41#include <ql/methods/finitedifferences/utilities/fdmquantohelper.hpp>
42#include <ql/methods/finitedifferences/meshers/fdmblackscholesmesher.hpp>
43#include <ql/utilities/dataformatters.hpp>
44#include <map>
45
46using namespace QuantLib;
47using namespace boost::unit_test_framework;
48
49#undef QUANTO_REPORT_FAILURE
50#define QUANTO_REPORT_FAILURE(greekName, payoff, exercise, s, q, r, \
51 today, v, fxr, fxv, corr, expected, \
52 calculated, error, tolerance) \
53 BOOST_FAIL("Quanto " << exerciseTypeToString(exercise) << " " \
54 << payoff->optionType() << " option with " \
55 << payoffTypeToString(payoff) << " payoff:\n" \
56 << " spot value: " << s << "\n" \
57 << " strike: " << payoff->strike() << "\n" \
58 << " dividend yield: " << io::rate(q) << "\n" \
59 << " risk-free rate: " << io::rate(r) << "\n" \
60 << " fx risk-free rate: " << io::rate(fxr) << "\n" \
61 << " reference date: " << today << "\n" \
62 << " maturity: " << exercise->lastDate() << "\n" \
63 << " volatility: " << io::volatility(v) << "\n" \
64 << " fx volatility: " << io::volatility(fxv) << "\n" \
65 << " correlation: " << corr << "\n\n" \
66 << " expected " << greekName << ": " << expected << "\n" \
67 << " calculated " << greekName << ": " << calculated << "\n"\
68 << " error: " << error << "\n" \
69 << " tolerance: " << tolerance);
70
71#undef QUANTO_FORWARD_REPORT_FAILURE
72#define QUANTO_FORWARD_REPORT_FAILURE(greekName, payoff, moneyness, \
73 exercise, s, q, r, \
74 today, reset, v, fxr, fxv, corr, expected, \
75 calculated, error, tolerance) \
76 BOOST_FAIL("Quanto " << exerciseTypeToString(exercise) << " " \
77 << payoff->optionType() << " option with " \
78 << payoffTypeToString(payoff) << " payoff:\n" \
79 << " spot value: " << s << "\n" \
80 << " strike: " << payoff->strike() << "\n" \
81 << " moneyness: " << io::percent(moneyness) << "\n" \
82 << " dividend yield: " << io::rate(q) << "\n" \
83 << " risk-free rate: " << io::rate(r) << "\n" \
84 << " fx risk-free rate: " << io::rate(fxr) << "\n" \
85 << " reference date: " << today << "\n" \
86 << " reset date: " << reset << "\n" \
87 << " maturity: " << exercise->lastDate() << "\n" \
88 << " volatility: " << io::volatility(v) << "\n" \
89 << " fx volatility: " << io::volatility(fxv) << "\n" \
90 << " correlation: " << corr << "\n\n" \
91 << " expected " << greekName << ": " << expected << "\n" \
92 << " calculated " << greekName << ": " << calculated << "\n"\
93 << " error: " << error << "\n" \
94 << " tolerance: " << tolerance);
95
96#undef QUANTO_BARRIER_REPORT_FAILURE
97#define QUANTO_BARRIER_REPORT_FAILURE(greekName, payoff, \
98 barrierType, barrier, rebate, \
99 exercise, s, q, r, \
100 today, v, fxr, fxv, corr, expected, \
101 calculated, error, tolerance) \
102 BOOST_FAIL("Quanto Barrier" << exerciseTypeToString(exercise) << " " \
103 << payoff->optionType() << " option with " \
104 << " barrier type: " << barrierType << "\n" \
105 << " barrier: " << barrier << "\n" \
106 << " rebate: " << rebate << "\n" \
107 << " payoff: " << payoffTypeToString(payoff) << "\n" \
108 << " spot value: " << s << "\n" \
109 << " strike: " << payoff->strike() << "\n" \
110 << " dividend yield: " << io::rate(q) << "\n" \
111 << " risk-free rate: " << io::rate(r) << "\n" \
112 << " fx risk-free rate: " << io::rate(fxr) << "\n" \
113 << " reference date: " << today << "\n" \
114 << " maturity: " << exercise->lastDate() << "\n" \
115 << " volatility: " << io::volatility(v) << "\n" \
116 << " fx volatility: " << io::volatility(fxv) << "\n" \
117 << " correlation: " << corr << "\n\n" \
118 << " expected " << greekName << ": " << expected << "\n" \
119 << " calculated " << greekName << ": " << calculated << "\n"\
120 << " error: " << error << "\n" \
121 << " tolerance: " << tolerance);
122
123#undef QUANTO_DOUBLE_BARRIER_REPORT_FAILURE
124#define QUANTO_DOUBLE_BARRIER_REPORT_FAILURE(greekName, payoff, \
125 barrierType, barrier_lo, barrier_hi, rebate, \
126 exercise, s, q, r, \
127 today, v, fxr, fxv, corr, expected, \
128 calculated, error, tolerance) \
129 BOOST_ERROR("Quanto Double Barrier" << exerciseTypeToString(exercise) << " " \
130 << payoff->optionType() << " option with " \
131 << " barrier type: " << barrierType << "\n" \
132 << " barrier_lo: " << barrier_lo << "\n" \
133 << " barrier_hi: " << barrier_hi << "\n" \
134 << " rebate: " << rebate << "\n" \
135 << " payoff: " << payoffTypeToString(payoff) << "\n" \
136 << " spot value: " << s << "\n" \
137 << " strike: " << payoff->strike() << "\n" \
138 << " dividend yield: " << io::rate(q) << "\n" \
139 << " risk-free rate: " << io::rate(r) << "\n" \
140 << " fx risk-free rate: " << io::rate(fxr) << "\n" \
141 << " reference date: " << today << "\n" \
142 << " maturity: " << exercise->lastDate() << "\n" \
143 << " volatility: " << io::volatility(v) << "\n" \
144 << " fx volatility: " << io::volatility(fxv) << "\n" \
145 << " correlation: " << corr << "\n\n" \
146 << " expected " << greekName << ": " << expected << "\n" \
147 << " calculated " << greekName << ": " << calculated << "\n"\
148 << " error: " << error << "\n" \
149 << " tolerance: " << tolerance);
150
151namespace {
152
153 struct QuantoOptionData {
154 Option::Type type;
155 Real strike;
156 Real s; // spot
157 Rate q; // dividend
158 Rate r; // risk-free rate
159 Time t; // time to maturity
160 Volatility v; // volatility
161 Rate fxr; // fx risk-free rate
162 Volatility fxv; // fx volatility
163 Real corr; // correlation
164 Real result; // expected result
165 Real tol; // tolerance
166 };
167
168 struct QuantoForwardOptionData {
169 Option::Type type;
170 Real moneyness;
171 Real s; // spot
172 Rate q; // dividend
173 Rate r; // risk-free rate
174 Time start; // time to reset
175 Time t; // time to maturity
176 Volatility v; // volatility
177 Rate fxr; // fx risk-free rate
178 Volatility fxv; // fx volatility
179 Real corr; // correlation
180 Real result; // expected result
181 Real tol; // tolerance
182 };
183
184 struct QuantoBarrierOptionData {
185 Barrier::Type barrierType;
186 Real barrier;
187 Real rebate;
188 Option::Type type;
189 Real s; // spot
190 Real strike;
191 Rate q; // dividend
192 Rate r; // risk-free rate
193 Time t; // time to maturity
194 Volatility v; // volatility
195 Rate fxr; // fx risk-free rate
196 Volatility fxv; // fx volatility
197 Real corr; // correlation
198 Real result; // expected result
199 Real tol; // tolerance
200 };
201
202 struct QuantoDoubleBarrierOptionData {
203 DoubleBarrier::Type barrierType;
204 Real barrier_lo;
205 Real barrier_hi;
206 Real rebate;
207 Option::Type type;
208 Real s; // spot
209 Real strike;
210 Rate q; // dividend
211 Rate r; // risk-free rate
212 Time t; // time to maturity
213 Volatility v; // volatility
214 Rate fxr; // fx risk-free rate
215 Volatility fxv; // fx volatility
216 Real corr; // correlation
217 Real result; // expected result
218 Real tol; // tolerance
219 };
220}
221
222
223void QuantoOptionTest::testValues() {
224
225 BOOST_TEST_MESSAGE("Testing quanto option values...");
226
227 /* The data below are from
228 from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
229 */
230 QuantoOptionData values[] = {
231 // type, strike, spot, div, rate, t, vol, fx risk-free rate, fx volatility, correlation, result, tol
232 // "Option pricing formulas", pag 105-106
233 { .type: Option::Call, .strike: 105.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.5, .v: 0.2, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 5.3280/1.5, .tol: 1.0e-4 },
234 // "Option pricing formulas", VBA code
235 { .type: Option::Put, .strike: 105.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.5, .v: 0.2, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 8.1636, .tol: 1.0e-4 }
236 };
237
238 DayCounter dc = Actual360();
239 Date today = Date::todaysDate();
240
241 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
242 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
243 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
244 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
245 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
246 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
247 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
248
249 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
250 Handle<YieldTermStructure> fxrTS(flatRate(today, forward: fxRate, dc));
251 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
252 Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: fxVol, dc));
253 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
254
255 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
256 new BlackScholesMertonProcess(Handle<Quote>(spot),
257 Handle<YieldTermStructure>(qTS),
258 Handle<YieldTermStructure>(rTS),
259 Handle<BlackVolTermStructure>(volTS)));
260 ext::shared_ptr<PricingEngine> engine(
261 new QuantoEngine<VanillaOption, AnalyticEuropeanEngine>(
262 stochProcess, fxrTS, fxVolTS,
263 Handle<Quote>(correlation)));
264
265 for (auto& value : values) {
266
267 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike));
268 Date exDate = today + timeToDays(t: value.t);
269 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
270
271 spot->setValue(value.s);
272 qRate->setValue(value.q);
273 rRate->setValue(value.r);
274 vol->setValue(value.v);
275
276 fxRate->setValue(value.fxr);
277 fxVol->setValue(value.fxv);
278 correlation->setValue(value.corr);
279
280 QuantoVanillaOption option(payoff, exercise);
281 option.setPricingEngine(engine);
282
283 Real calculated = option.NPV();
284 Real error = std::fabs(x: calculated - value.result);
285 Real tolerance = 1e-4;
286 if (error>tolerance) {
287 QUANTO_REPORT_FAILURE("value", payoff, exercise, value.s, value.q, value.r, today,
288 value.v, value.fxr, value.fxv, value.corr, value.result,
289 calculated, error, tolerance);
290 }
291 }
292}
293
294
295void QuantoOptionTest::testGreeks() {
296
297 BOOST_TEST_MESSAGE("Testing quanto option greeks...");
298
299 std::map<std::string,Real> calculated, expected, tolerance;
300 tolerance["delta"] = 1.0e-5;
301 tolerance["gamma"] = 1.0e-5;
302 tolerance["theta"] = 1.0e-5;
303 tolerance["rho"] = 1.0e-5;
304 tolerance["divRho"] = 1.0e-5;
305 tolerance["vega"] = 1.0e-5;
306 tolerance["qrho"] = 1.0e-5;
307 tolerance["qvega"] = 1.0e-5;
308 tolerance["qlambda"] = 1.0e-5;
309
310 Option::Type types[] = { Option::Call, Option::Put };
311 Real strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
312 Real underlyings[] = { 100.0 };
313 Rate qRates[] = { 0.04, 0.05 };
314 Rate rRates[] = { 0.01, 0.05, 0.15 };
315 Integer lengths[] = { 2 };
316 Volatility vols[] = { 0.11, 1.20 };
317 Real correlations[] = { 0.10, 0.90 };
318
319 DayCounter dc = Actual360();
320 Date today = Date::todaysDate();
321 Settings::instance().evaluationDate() = today;
322
323 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
324 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
325 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
326 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
327 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
328 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
329 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
330 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
331 Handle<YieldTermStructure> fxrTS(flatRate(forward: fxRate, dc));
332 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
333 Handle<BlackVolTermStructure> fxVolTS(flatVol(volatility: fxVol, dc));
334 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
335
336 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
337 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
338
339 ext::shared_ptr<PricingEngine> engine(
340 new QuantoEngine<VanillaOption,AnalyticEuropeanEngine>(
341 stochProcess,fxrTS, fxVolTS,
342 Handle<Quote>(correlation)));
343
344 for (auto& type : types) {
345 for (Real strike : strikes) {
346 for (int length : lengths) {
347
348 Date exDate = today + length * Years;
349 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
350
351 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
352
353 QuantoVanillaOption option(payoff, exercise);
354 option.setPricingEngine(engine);
355
356 for (Real u : underlyings) {
357 for (Real m : qRates) {
358 for (Real n : rRates) {
359 for (Real v : vols) {
360 for (Real fxr : rRates) {
361 for (Real fxv : vols) {
362 for (Real corr : correlations) {
363
364 Rate q = m, r = n;
365 spot->setValue(u);
366 qRate->setValue(q);
367 rRate->setValue(r);
368 vol->setValue(v);
369 fxRate->setValue(fxr);
370 fxVol->setValue(fxv);
371 correlation->setValue(corr);
372
373 Real value = option.NPV();
374 calculated["delta"] = option.delta();
375 calculated["gamma"] = option.gamma();
376 calculated["theta"] = option.theta();
377 calculated["rho"] = option.rho();
378 calculated["divRho"] = option.dividendRho();
379 calculated["vega"] = option.vega();
380 calculated["qrho"] = option.qrho();
381 calculated["qvega"] = option.qvega();
382 calculated["qlambda"] = option.qlambda();
383
384 if (value > spot->value() * 1.0e-5) {
385 // perturb spot and get delta and gamma
386 Real du = u * 1.0e-4;
387 spot->setValue(u + du);
388 Real value_p = option.NPV(),
389 delta_p = option.delta();
390 spot->setValue(u - du);
391 Real value_m = option.NPV(),
392 delta_m = option.delta();
393 spot->setValue(u);
394 expected["delta"] = (value_p - value_m) / (2 * du);
395 expected["gamma"] = (delta_p - delta_m) / (2 * du);
396
397 // perturb rates and get rho and dividend rho
398 Spread dr = r * 1.0e-4;
399 rRate->setValue(r + dr);
400 value_p = option.NPV();
401 rRate->setValue(r - dr);
402 value_m = option.NPV();
403 rRate->setValue(r);
404 expected["rho"] = (value_p - value_m) / (2 * dr);
405
406 Spread dq = q * 1.0e-4;
407 qRate->setValue(q + dq);
408 value_p = option.NPV();
409 qRate->setValue(q - dq);
410 value_m = option.NPV();
411 qRate->setValue(q);
412 expected["divRho"] = (value_p - value_m) / (2 * dq);
413
414 // perturb volatility and get vega
415 Volatility dv = v * 1.0e-4;
416 vol->setValue(v + dv);
417 value_p = option.NPV();
418 vol->setValue(v - dv);
419 value_m = option.NPV();
420 vol->setValue(v);
421 expected["vega"] = (value_p - value_m) / (2 * dv);
422
423 // perturb fx rate and get qrho
424 Spread dfxr = fxr * 1.0e-4;
425 fxRate->setValue(fxr + dfxr);
426 value_p = option.NPV();
427 fxRate->setValue(fxr - dfxr);
428 value_m = option.NPV();
429 fxRate->setValue(fxr);
430 expected["qrho"] = (value_p - value_m) / (2 * dfxr);
431
432 // perturb fx volatility and get qvega
433 Volatility dfxv = fxv * 1.0e-4;
434 fxVol->setValue(fxv + dfxv);
435 value_p = option.NPV();
436 fxVol->setValue(fxv - dfxv);
437 value_m = option.NPV();
438 fxVol->setValue(fxv);
439 expected["qvega"] =
440 (value_p - value_m) / (2 * dfxv);
441
442 // perturb correlation and get qlambda
443 Real dcorr = corr * 1.0e-4;
444 correlation->setValue(corr + dcorr);
445 value_p = option.NPV();
446 correlation->setValue(corr - dcorr);
447 value_m = option.NPV();
448 correlation->setValue(corr);
449 expected["qlambda"] =
450 (value_p - value_m) / (2 * dcorr);
451
452 // perturb date and get theta
453 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
454 Settings::instance().evaluationDate() = today - 1;
455 value_m = option.NPV();
456 Settings::instance().evaluationDate() = today + 1;
457 value_p = option.NPV();
458 Settings::instance().evaluationDate() = today;
459 expected["theta"] = (value_p - value_m) / dT;
460
461 // compare
462 std::map<std::string, Real>::iterator it;
463 for (it = calculated.begin();
464 it != calculated.end(); ++it) {
465 std::string greek = it->first;
466 Real expct = expected[greek],
467 calcl = calculated[greek],
468 tol = tolerance[greek];
469 Real error = relativeError(x1: expct, x2: calcl, reference: u);
470 if (error > tol) {
471 QUANTO_REPORT_FAILURE(
472 greek, payoff, exercise, u, q, r, today,
473 v, fxr, fxv, corr, expct, calcl, error,
474 tol);
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482 }
483 }
484 }
485 }
486 }
487 }
488}
489
490
491
492void QuantoOptionTest::testForwardValues() {
493
494 BOOST_TEST_MESSAGE("Testing quanto-forward option values...");
495
496 QuantoForwardOptionData values[] = {
497 // type, moneyness, spot, div, risk-free rate, reset, maturity, vol, fx risk-free rate, fx vol, corr, result, tol
498 // reset=0.0, quanto (not-forward) options
499 { .type: Option::Call, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.00, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 5.3280/1.5, .tol: 1.0e-4 },
500 { .type: Option::Put, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.00, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 8.1636, .tol: 1.0e-4 },
501 // reset!=0.0, quanto-forward options (cursory checked against FinCAD 7)
502 { .type: Option::Call, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.25, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 2.0171, .tol: 1.0e-4 },
503 { .type: Option::Put, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.25, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 6.7296, .tol: 1.0e-4 }
504 };
505
506 DayCounter dc = Actual360();
507 Date today = Date::todaysDate();
508
509 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
510 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
511 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
512 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
513 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
514 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
515 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
516
517 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
518 Handle<YieldTermStructure> fxrTS(flatRate(today, forward: fxRate, dc));
519 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
520 Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: fxVol, dc));
521 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
522
523 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
524 new BlackScholesMertonProcess(Handle<Quote>(spot),
525 Handle<YieldTermStructure>(qTS),
526 Handle<YieldTermStructure>(rTS),
527 Handle<BlackVolTermStructure>(volTS)));
528
529 ext::shared_ptr<PricingEngine> engine(
530 new QuantoEngine<ForwardVanillaOption,
531 ForwardVanillaEngine<AnalyticEuropeanEngine> >(
532 stochProcess, fxrTS, fxVolTS,
533 Handle<Quote>(correlation)));
534
535 for (auto& value : values) {
536
537 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, 0.0));
538 Date exDate = today + timeToDays(t: value.t);
539 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
540 Date reset = today + timeToDays(t: value.start);
541
542 spot->setValue(value.s);
543 qRate->setValue(value.q);
544 rRate->setValue(value.r);
545 vol->setValue(value.v);
546
547 fxRate->setValue(value.fxr);
548 fxVol->setValue(value.fxv);
549 correlation->setValue(value.corr);
550
551 QuantoForwardVanillaOption option(value.moneyness, reset, payoff, exercise);
552 option.setPricingEngine(engine);
553
554 Real calculated = option.NPV();
555 Real error = std::fabs(x: calculated - value.result);
556 Real tolerance = 1e-4;
557 if (error>tolerance) {
558 QUANTO_FORWARD_REPORT_FAILURE("value", payoff, value.moneyness, exercise, value.s,
559 value.q, value.r, today, reset, value.v, value.fxr,
560 value.fxv, value.corr, value.result, calculated, error,
561 tolerance);
562 }
563 }
564}
565
566
567void QuantoOptionTest::testForwardGreeks() {
568
569 BOOST_TEST_MESSAGE("Testing quanto-forward option greeks...");
570
571 std::map<std::string,Real> calculated, expected, tolerance;
572 tolerance["delta"] = 1.0e-5;
573 tolerance["gamma"] = 1.0e-5;
574 tolerance["theta"] = 1.0e-5;
575 tolerance["rho"] = 1.0e-5;
576 tolerance["divRho"] = 1.0e-5;
577 tolerance["vega"] = 1.0e-5;
578 tolerance["qrho"] = 1.0e-5;
579 tolerance["qvega"] = 1.0e-5;
580 tolerance["qlambda"] = 1.0e-5;
581
582 Option::Type types[] = { Option::Call, Option::Put };
583 Real moneyness[] = { 0.9, 1.0, 1.1 };
584 Real underlyings[] = { 100.0 };
585 Rate qRates[] = { 0.04, 0.05 };
586 Rate rRates[] = { 0.01, 0.05, 0.15 };
587 Integer lengths[] = { 2 };
588 Integer startMonths[] = { 6, 9 };
589 Volatility vols[] = { 0.11, 1.20 };
590 Real correlations[] = { 0.10, 0.90 };
591
592 DayCounter dc = Actual360();
593 Date today = Date::todaysDate();
594 Settings::instance().evaluationDate() = today;
595
596 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
597 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
598 Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc));
599 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
600 Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc));
601 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
602 Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc));
603 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
604 Handle<YieldTermStructure> fxrTS(flatRate(forward: fxRate, dc));
605 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
606 Handle<BlackVolTermStructure> fxVolTS(flatVol(volatility: fxVol, dc));
607 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
608
609 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
610 new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
611
612 ext::shared_ptr<PricingEngine> engine(
613 new QuantoEngine<ForwardVanillaOption,
614 ForwardVanillaEngine<AnalyticEuropeanEngine> >(
615 stochProcess, fxrTS, fxVolTS,
616 Handle<Quote>(correlation)));
617
618 for (auto& type : types) {
619 for (Real moneynes : moneyness) {
620 for (int length : lengths) {
621 for (int startMonth : startMonths) {
622
623 Date exDate = today + length * Years;
624 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
625
626 Date reset = today + startMonth * Months;
627
628 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, 0.0));
629
630 QuantoForwardVanillaOption option(moneynes, reset, payoff, exercise);
631 option.setPricingEngine(engine);
632
633 for (Real u : underlyings) {
634 for (Real m : qRates) {
635 for (Real n : rRates) {
636 for (Real v : vols) {
637 for (Real fxr : rRates) {
638 for (Real fxv : vols) {
639 for (Real corr : correlations) {
640
641 Rate q = m, r = n;
642 spot->setValue(u);
643 qRate->setValue(q);
644 rRate->setValue(r);
645 vol->setValue(v);
646 fxRate->setValue(fxr);
647 fxVol->setValue(fxv);
648 correlation->setValue(corr);
649
650 Real value = option.NPV();
651 calculated["delta"] = option.delta();
652 calculated["gamma"] = option.gamma();
653 calculated["theta"] = option.theta();
654 calculated["rho"] = option.rho();
655 calculated["divRho"] = option.dividendRho();
656 calculated["vega"] = option.vega();
657 calculated["qrho"] = option.qrho();
658 calculated["qvega"] = option.qvega();
659 calculated["qlambda"] = option.qlambda();
660
661 if (value > spot->value() * 1.0e-5) {
662 // perturb spot and get delta and gamma
663 Real du = u * 1.0e-4;
664 spot->setValue(u + du);
665 Real value_p = option.NPV(),
666 delta_p = option.delta();
667 spot->setValue(u - du);
668 Real value_m = option.NPV(),
669 delta_m = option.delta();
670 spot->setValue(u);
671 expected["delta"] =
672 (value_p - value_m) / (2 * du);
673 expected["gamma"] =
674 (delta_p - delta_m) / (2 * du);
675
676 // perturb rates and get rho and dividend rho
677 Spread dr = r * 1.0e-4;
678 rRate->setValue(r + dr);
679 value_p = option.NPV();
680 rRate->setValue(r - dr);
681 value_m = option.NPV();
682 rRate->setValue(r);
683 expected["rho"] =
684 (value_p - value_m) / (2 * dr);
685
686 Spread dq = q * 1.0e-4;
687 qRate->setValue(q + dq);
688 value_p = option.NPV();
689 qRate->setValue(q - dq);
690 value_m = option.NPV();
691 qRate->setValue(q);
692 expected["divRho"] =
693 (value_p - value_m) / (2 * dq);
694
695 // perturb volatility and get vega
696 Volatility dv = v * 1.0e-4;
697 vol->setValue(v + dv);
698 value_p = option.NPV();
699 vol->setValue(v - dv);
700 value_m = option.NPV();
701 vol->setValue(v);
702 expected["vega"] =
703 (value_p - value_m) / (2 * dv);
704
705 // perturb fx rate and get qrho
706 Spread dfxr = fxr * 1.0e-4;
707 fxRate->setValue(fxr + dfxr);
708 value_p = option.NPV();
709 fxRate->setValue(fxr - dfxr);
710 value_m = option.NPV();
711 fxRate->setValue(fxr);
712 expected["qrho"] =
713 (value_p - value_m) / (2 * dfxr);
714
715 // perturb fx volatility and get qvega
716 Volatility dfxv = fxv * 1.0e-4;
717 fxVol->setValue(fxv + dfxv);
718 value_p = option.NPV();
719 fxVol->setValue(fxv - dfxv);
720 value_m = option.NPV();
721 fxVol->setValue(fxv);
722 expected["qvega"] =
723 (value_p - value_m) / (2 * dfxv);
724
725 // perturb correlation and get qlambda
726 Real dcorr = corr * 1.0e-4;
727 correlation->setValue(corr + dcorr);
728 value_p = option.NPV();
729 correlation->setValue(corr - dcorr);
730 value_m = option.NPV();
731 correlation->setValue(corr);
732 expected["qlambda"] =
733 (value_p - value_m) / (2 * dcorr);
734
735 // perturb date and get theta
736 Time dT = dc.yearFraction(d1: today - 1, d2: today + 1);
737 Settings::instance().evaluationDate() =
738 today - 1;
739 value_m = option.NPV();
740 Settings::instance().evaluationDate() =
741 today + 1;
742 value_p = option.NPV();
743 Settings::instance().evaluationDate() = today;
744 expected["theta"] = (value_p - value_m) / dT;
745
746 // compare
747 std::map<std::string, Real>::iterator it;
748 for (it = calculated.begin();
749 it != calculated.end(); ++it) {
750 std::string greek = it->first;
751 Real expct = expected[greek],
752 calcl = calculated[greek],
753 tol = tolerance[greek];
754 Real error = relativeError(x1: expct, x2: calcl, reference: u);
755 if (error > tol) {
756 QUANTO_FORWARD_REPORT_FAILURE(
757 greek, payoff, moneynes, exercise,
758 u, q, r, today, reset, v, fxr, fxv,
759 corr, expct, calcl, error, tol);
760 }
761 }
762 }
763 }
764 }
765 }
766 }
767 }
768 }
769 }
770 }
771 }
772 }
773 }
774}
775
776
777void QuantoOptionTest::testForwardPerformanceValues() {
778
779 BOOST_TEST_MESSAGE("Testing quanto-forward-performance option values...");
780
781 QuantoForwardOptionData values[] = {
782 // type, moneyness, spot, div, risk-free rate, reset, maturity, vol, fx risk-free rate, fx vol, corr, result, tol
783 // reset=0.0, quanto-(not-forward)-performance options
784 // exactly one hundredth of the non-performance version
785 { .type: Option::Call, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.00, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 5.3280/150, .tol: 1.0e-4 },
786 { .type: Option::Put, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.00, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 0.0816, .tol: 1.0e-4 },
787 // reset!=0.0, quanto-forward-performance options (roughly one hundredth of the non-performance version)
788 { .type: Option::Call, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.25, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 0.0201, .tol: 1.0e-4 },
789 { .type: Option::Put, .moneyness: 1.05, .s: 100.0, .q: 0.04, .r: 0.08, .start: 0.25, .t: 0.5, .v: 0.20, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: 0.0672, .tol: 1.0e-4 }
790 };
791
792 DayCounter dc = Actual360();
793 Date today = Date::todaysDate();
794
795 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
796 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
797 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
798 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
799 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
800 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
801 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
802
803 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
804 Handle<YieldTermStructure> fxrTS(flatRate(today, forward: fxRate, dc));
805 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
806 Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: fxVol, dc));
807 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
808
809 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
810 new BlackScholesMertonProcess(Handle<Quote>(spot),
811 Handle<YieldTermStructure>(qTS),
812 Handle<YieldTermStructure>(rTS),
813 Handle<BlackVolTermStructure>(volTS)));
814
815 ext::shared_ptr<PricingEngine> engine(
816 new QuantoEngine<ForwardVanillaOption,
817 ForwardPerformanceVanillaEngine<AnalyticEuropeanEngine> >(
818 stochProcess, fxrTS, fxVolTS,
819 Handle<Quote>(correlation)));
820
821 for (auto& value : values) {
822
823 ext::shared_ptr<StrikedTypePayoff> payoff(
824 // new PercentageStrikePayoff(values[i].type,
825 // values[i].moneyness));
826 new PlainVanillaPayoff(value.type, 0.0));
827 Date exDate = today + timeToDays(t: value.t);
828 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
829 Date reset = today + timeToDays(t: value.start);
830
831 spot->setValue(value.s);
832 qRate->setValue(value.q);
833 rRate->setValue(value.r);
834 vol->setValue(value.v);
835
836 fxRate->setValue(value.fxr);
837 fxVol->setValue(value.fxv);
838 correlation->setValue(value.corr);
839
840 QuantoForwardVanillaOption option(value.moneyness, reset, payoff, exercise);
841 option.setPricingEngine(engine);
842
843 Real calculated = option.NPV();
844 Real error = std::fabs(x: calculated - value.result);
845 Real tolerance = 1e-4;
846 if (error>tolerance) {
847 QUANTO_FORWARD_REPORT_FAILURE("value", payoff, value.moneyness, exercise, value.s,
848 value.q, value.r, today, reset, value.v, value.fxr,
849 value.fxv, value.corr, value.result, calculated, error,
850 tolerance);
851 }
852 }
853}
854
855void QuantoOptionTest::testBarrierValues() {
856
857 BOOST_TEST_MESSAGE("Testing quanto-barrier option values...");
858
859 QuantoBarrierOptionData values[] = {
860 // TODO: Bench results against an existing prop calculator
861 // barrierType, barrier, rebate, type, spot, strike,
862 // q, r, T, vol, fx risk-free rate, fx vol, corr, result, tol
863 { .barrierType: Barrier::DownOut, .barrier: 95.0, .rebate: 3.0, .type: Option::Call, .s: 100, .strike: 90,
864 .q: 0.04, .r: 0.0212, .t: 0.50, .v: 0.25, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 8.247, .tol: 0.5 },
865 { .barrierType: Barrier::DownOut, .barrier: 95.0, .rebate: 3.0, .type: Option::Put, .s: 100, .strike: 90,
866 .q: 0.04, .r: 0.0212, .t: 0.50, .v: 0.25, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 2.274, .tol: 0.5 },
867 { .barrierType: Barrier::DownIn, .barrier: 95.0, .rebate: 0, .type: Option::Put, .s: 100, .strike: 90,
868 .q: 0.04, .r: 0.0212, .t: 0.50, .v: 0.25, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 2.85, .tol: 0.5 },
869 };
870
871 DayCounter dc = Actual360();
872 Date today = Date::todaysDate();
873
874 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
875 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
876 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
877 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
878 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
879 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
880 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
881
882 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
883 Handle<YieldTermStructure> fxrTS(flatRate(today, forward: fxRate, dc));
884 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
885 Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: fxVol, dc));
886 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
887
888 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
889 new BlackScholesMertonProcess(Handle<Quote>(spot),
890 Handle<YieldTermStructure>(qTS),
891 Handle<YieldTermStructure>(rTS),
892 Handle<BlackVolTermStructure>(volTS)));
893
894 ext::shared_ptr<PricingEngine> engine(
895 new QuantoEngine<BarrierOption, AnalyticBarrierEngine>(
896 stochProcess, fxrTS, fxVolTS,
897 Handle<Quote>(correlation)));
898
899 for (auto& value : values) {
900
901 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike));
902
903 Date exDate = today + timeToDays(t: value.t);
904 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
905
906 spot->setValue(value.s);
907 qRate->setValue(value.q);
908 rRate->setValue(value.r);
909 vol->setValue(value.v);
910
911 fxRate->setValue(value.fxr);
912 fxVol->setValue(value.fxv);
913 correlation->setValue(value.corr);
914
915 QuantoBarrierOption option(value.barrierType, value.barrier, value.rebate, payoff,
916 exercise);
917
918 option.setPricingEngine(engine);
919
920 Real calculated = option.NPV();
921 Real error = std::fabs(x: calculated - value.result);
922 Real tolerance = value.tol;
923
924 if (error>tolerance) {
925 QUANTO_BARRIER_REPORT_FAILURE("value", payoff, value.barrierType, value.barrier,
926 value.rebate, exercise, value.s, value.q, value.r, today,
927 value.v, value.fxr, value.fxv, value.corr, value.result,
928 calculated, error, tolerance);
929 }
930 }
931}
932
933void QuantoOptionTest::testDoubleBarrierValues() {
934
935 BOOST_TEST_MESSAGE("Testing quanto-double-barrier option values...");
936
937 QuantoDoubleBarrierOptionData values[] = {
938 // barrierType, bar.lo, bar.hi, rebate, type, spot, strk, q, r, T, vol, fx rate, fx vol, corr, result, tol
939 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 50.0, .barrier_hi: 150.0, .rebate: 0, .type: Option::Call, .s: 100, .strike: 100.0, .q: 0.00, .r: 0.1, .t: 0.25, .v: 0.15, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 3.4623, .tol: 1.0e-4},
940 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.0, .barrier_hi: 110.0, .rebate: 0, .type: Option::Call, .s: 100, .strike: 100.0, .q: 0.00, .r: 0.1, .t: 0.50, .v: 0.15, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 0.5236, .tol: 1.0e-4},
941 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.0, .barrier_hi: 110.0, .rebate: 0, .type: Option::Put, .s: 100, .strike: 100.0, .q: 0.00, .r: 0.1, .t: 0.25, .v: 0.15, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 1.1320, .tol: 1.0e-4},
942 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.0, .barrier_hi: 120.0, .rebate: 0, .type: Option::Call, .s: 100, .strike: 102.0, .q: 0.00, .r: 0.1, .t: 0.25, .v: 0.25, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 2.6313, .tol: 1.0e-4},
943 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.0, .barrier_hi: 120.0, .rebate: 0, .type: Option::Call, .s: 100, .strike: 102.0, .q: 0.00, .r: 0.1, .t: 0.50, .v: 0.15, .fxr: 0.05, .fxv: 0.2, .corr: 0.3, .result: 1.9305, .tol: 1.0e-4},
944 };
945
946 DayCounter dc = Actual360();
947 Date today = Date::todaysDate();
948
949 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
950 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
951 Handle<YieldTermStructure> qTS(flatRate(today, forward: qRate, dc));
952 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
953 Handle<YieldTermStructure> rTS(flatRate(today, forward: rRate, dc));
954 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
955 Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: vol, dc));
956
957 ext::shared_ptr<SimpleQuote> fxRate(new SimpleQuote(0.0));
958 Handle<YieldTermStructure> fxrTS(flatRate(today, forward: fxRate, dc));
959 ext::shared_ptr<SimpleQuote> fxVol(new SimpleQuote(0.0));
960 Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: fxVol, dc));
961 ext::shared_ptr<SimpleQuote> correlation(new SimpleQuote(0.0));
962
963 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
964 new BlackScholesMertonProcess(Handle<Quote>(spot),
965 Handle<YieldTermStructure>(qTS),
966 Handle<YieldTermStructure>(rTS),
967 Handle<BlackVolTermStructure>(volTS)));
968
969 ext::shared_ptr<PricingEngine> engine(
970 new QuantoEngine<DoubleBarrierOption, AnalyticDoubleBarrierEngine>(
971 stochProcess, fxrTS, fxVolTS,
972 Handle<Quote>(correlation)));
973
974 for (auto& value : values) {
975
976 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike));
977
978 Date exDate = today + timeToDays(t: value.t);
979 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
980
981 spot->setValue(value.s);
982 qRate->setValue(value.q);
983 rRate->setValue(value.r);
984 vol->setValue(value.v);
985
986 fxRate->setValue(value.fxr);
987 fxVol->setValue(value.fxv);
988 correlation->setValue(value.corr);
989
990 QuantoDoubleBarrierOption option(value.barrierType, value.barrier_lo, value.barrier_hi,
991 value.rebate, payoff, exercise);
992
993 option.setPricingEngine(engine);
994
995 Real calculated = option.NPV();
996 Real error = std::fabs(x: calculated - value.result);
997 Real tolerance = value.tol;
998
999 if (error>tolerance) {
1000 QUANTO_DOUBLE_BARRIER_REPORT_FAILURE(
1001 "value", payoff, value.barrierType, value.barrier_lo, value.barrier_hi,
1002 value.rebate, exercise, value.s, value.q, value.r, today, value.v, value.fxr,
1003 value.fxv, value.corr, value.result, calculated, error, tolerance);
1004 }
1005 }
1006}
1007
1008void QuantoOptionTest::testFDMQuantoHelper() {
1009
1010 BOOST_TEST_MESSAGE("Testing FDM quanto helper...");
1011
1012 const DayCounter dc = Actual360();
1013 const Date today = Date(22, April, 2019);
1014
1015 const Real s = 100;
1016 const Rate domesticR = 0.1;
1017 const Rate foreignR = 0.2;
1018 const Rate q = 0.3;
1019 const Volatility vol = 0.3;
1020 const Volatility fxVol = 0.2;
1021
1022 const Real exchRateATMlevel = 1.0;
1023 const Real equityFxCorrelation = -0.75;
1024
1025 const Handle<YieldTermStructure> domesticTS(
1026 flatRate(today, forward: domesticR, dc));
1027
1028 const Handle<YieldTermStructure> divTS(
1029 flatRate(today, forward: q, dc));
1030
1031 const Handle<BlackVolTermStructure> volTS(
1032 flatVol(today, volatility: vol, dc));
1033
1034 const Handle<Quote> spot(
1035 ext::make_shared<SimpleQuote>(args: s));
1036
1037 const ext::shared_ptr<BlackScholesMertonProcess> bsmProcess
1038 = ext::make_shared<BlackScholesMertonProcess>(
1039 args: spot, args: divTS, args: domesticTS, args: volTS);
1040
1041 const ext::shared_ptr<YieldTermStructure> foreignTS
1042 = flatRate(today, forward: foreignR, dc);
1043
1044 const ext::shared_ptr<BlackVolTermStructure> fxVolTS
1045 = flatVol(today, volatility: fxVol, dc);
1046
1047 const ext::shared_ptr<FdmQuantoHelper> fdmQuantoHelper
1048 = ext::make_shared<FdmQuantoHelper>(
1049 args: domesticTS.currentLink(),
1050 args: foreignTS, args: fxVolTS,
1051 args: equityFxCorrelation, args: exchRateATMlevel);
1052
1053 const Real calculatedQuantoAdj
1054 = fdmQuantoHelper->quantoAdjustment(equityVol: vol, t1: 0.0, t2: 1.0);
1055
1056 const Real expectedQuantoAdj
1057 = domesticR - foreignR + equityFxCorrelation*vol*fxVol;
1058
1059 const Real tol = 1e-10;
1060 if (std::fabs(x: calculatedQuantoAdj - expectedQuantoAdj) > tol) {
1061 BOOST_ERROR("failed to reproduce quanto drift rate"
1062 << std::setprecision(10)
1063 << "\n calculated: " << calculatedQuantoAdj
1064 << "\n expected: " << expectedQuantoAdj);
1065 }
1066
1067 const Date maturityDate = today + Period(6, Months);
1068 const Time maturityTime = dc.yearFraction(d1: today, d2: maturityDate);
1069
1070 const Real eps = 0.0002;
1071 const Real scalingFactor = 1.25;
1072
1073 const ext::shared_ptr<FdmBlackScholesMesher> mesher(
1074 new FdmBlackScholesMesher(
1075 3, bsmProcess, maturityTime, s,
1076 Null<Real>(), Null<Real>(), eps, scalingFactor,
1077 std::pair<Real, Real>(Null<Real>(), Null<Real>()),
1078 DividendSchedule(),
1079 fdmQuantoHelper));
1080
1081 const Real normInvEps = InverseCumulativeNormal()(1-eps);
1082 const Real sigmaSqrtT = vol * std::sqrt(x: maturityTime);
1083
1084 const Real qQuanto = q + expectedQuantoAdj;
1085 const Real expectedDriftRate = domesticR - qQuanto;
1086
1087 const Real logFwd = std::log(x: s) + expectedDriftRate*maturityTime;
1088 const Real xMin = logFwd - sigmaSqrtT*normInvEps*scalingFactor;
1089 const Real xMax = std::log(x: s) + sigmaSqrtT*normInvEps*scalingFactor;
1090
1091 const std::vector<Real> loc = mesher->locations();
1092
1093 if (std::fabs(x: loc.front()-xMin) > tol || std::fabs(x: loc.back()-xMax) > tol) {
1094 BOOST_ERROR("failed to reproduce FDM grid boundaries"
1095 << "\n calculated: (" << std::setprecision(10)
1096 << loc.front() << ", " << loc.back() << ")"
1097 << "\n expected: (" << xMin << ", " << xMax << ")");
1098 }
1099}
1100
1101void QuantoOptionTest::testPDEOptionValues() {
1102
1103 BOOST_TEST_MESSAGE("Testing quanto-option values with PDEs...");
1104
1105 const DayCounter dc = Actual360();
1106 const Date today = Date(21, April, 2019);
1107
1108 QuantoOptionData values[] = {
1109 // type, strike, spot, div, domestic rate, t, vol, foreign rate, fx vol, correlation, result, tol
1110 { .type: Option::Call, .strike: 105.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.5, .v: 0.2, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: Null<Real>(), .tol: Null<Real>() },
1111 { .type: Option::Call, .strike: 100.0, .s: 100.0, .q: 0.16, .r: 0.08, .t: 0.25, .v: 0.15, .fxr: 0.05, .fxv: 0.20, .corr: -0.3, .result: Null<Real>(), .tol: Null<Real>() },
1112 { .type: Option::Call, .strike: 105.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.5, .v: 0.2, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: Null<Real>(), .tol: Null<Real>() },
1113 { .type: Option::Put, .strike: 105.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.5, .v: 0.2, .fxr: 0.05, .fxv: 0.10, .corr: 0.3, .result: Null<Real>(), .tol: Null<Real>() },
1114 { .type: Option::Call, .strike: 0.0, .s: 100.0, .q: 0.04, .r: 0.08, .t: 0.3, .v: 0.3, .fxr: 0.05, .fxv: 0.10, .corr: 0.75, .result: Null<Real>(), .tol: Null<Real>() },
1115 };
1116
1117 for (auto& value : values) {
1118
1119 std::map<std::string,Real> calculated, expected, tolerance;
1120 tolerance["npv"] = 2e-4;
1121 tolerance["delta"] = 1e-4;
1122 tolerance["gamma"] = 1e-4;
1123 tolerance["theta"] = 1e-4;
1124
1125 const Handle<Quote> spot(ext::make_shared<SimpleQuote>(args&: value.s));
1126
1127 const Real strike = value.strike;
1128
1129 const Handle<YieldTermStructure> domesticTS(flatRate(today, forward: value.r, dc));
1130
1131 const Handle<YieldTermStructure> divTS(flatRate(today, forward: value.q, dc));
1132
1133 const Handle<BlackVolTermStructure> volTS(flatVol(today, volatility: value.v, dc));
1134
1135 const ext::shared_ptr<BlackScholesMertonProcess> bsmProcess
1136 = ext::make_shared<BlackScholesMertonProcess>(
1137 args: spot, args: divTS, args: domesticTS, args: volTS);
1138
1139 const Handle<YieldTermStructure> foreignTS(flatRate(today, forward: value.fxr, dc));
1140
1141 const Handle<BlackVolTermStructure> fxVolTS(flatVol(today, volatility: value.fxv, dc));
1142
1143 const Real exchRateATMlevel = 1.0;
1144 const Real equityFxCorrelation = value.corr;
1145
1146 const ext::shared_ptr<FdmQuantoHelper> quantoHelper
1147 = ext::make_shared<FdmQuantoHelper>(
1148 args: domesticTS.currentLink(),
1149 args: foreignTS.currentLink(),
1150 args: fxVolTS.currentLink(),
1151 args: equityFxCorrelation, args: exchRateATMlevel);
1152
1153 const ext::shared_ptr<StrikedTypePayoff> payoff =
1154 ext::make_shared<PlainVanillaPayoff>(args&: value.type, args: strike);
1155 const Date exDate = today + timeToDays(t: value.t);
1156 const ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
1157
1158 VanillaOption option(payoff, exercise);
1159
1160 const ext::shared_ptr<PricingEngine> pdeEngine =
1161 ext::make_shared<FdBlackScholesVanillaEngine>(args: bsmProcess, args: quantoHelper,
1162 args: Size(value.t * 200), args: 500, args: 1);
1163
1164 option.setPricingEngine(pdeEngine);
1165
1166 calculated["npv"] = option.NPV();
1167 calculated["delta"] = option.delta();
1168 calculated["gamma"] = option.delta();
1169 calculated["theta"] = option.delta();
1170
1171 const ext::shared_ptr<PricingEngine> analyticEngine
1172 = ext::make_shared<QuantoEngine<
1173 VanillaOption, AnalyticEuropeanEngine> >(
1174 args: bsmProcess, args: foreignTS, args: fxVolTS,
1175 args: Handle<Quote>(
1176 ext::make_shared<SimpleQuote>(args: equityFxCorrelation)));
1177
1178 option.setPricingEngine(analyticEngine);
1179
1180 expected["npv"] = option.NPV();
1181 expected["delta"] = option.delta();
1182 expected["gamma"] = option.delta();
1183 expected["theta"] = option.delta();
1184
1185 for (std::map<std::string,Real>::const_iterator it = calculated.begin();
1186 it != calculated.end(); ++it) {
1187
1188 const std::string greek = it->first;
1189
1190 const Real expct = expected[greek];
1191 const Real calcl = calculated[greek];
1192 const Real error = std::fabs(x: expct - calcl);
1193 const Real tol = tolerance[greek];
1194
1195 if (error > tol) {
1196 QUANTO_REPORT_FAILURE(greek, payoff, exercise, value.s, value.q, value.r, today,
1197 value.v, value.fxr, value.fxv, value.corr, expct, calcl,
1198 error, tol)
1199 }
1200 }
1201 }
1202}
1203
1204void QuantoOptionTest::testAmericanQuantoOption() {
1205
1206 BOOST_TEST_MESSAGE("Testing American quanto-option values with PDEs...");
1207
1208 const DayCounter dc = Actual365Fixed();
1209 const Date today = Date(21, April, 2019);
1210 const Date maturity = today + Period(9, Months);
1211
1212 const Real s = 100;
1213 const Rate domesticR = 0.025;
1214 const Rate foreignR = 0.075;
1215 const Rate q = 0.03;
1216 const Volatility vol = 0.3;
1217 const Volatility fxVol = 0.15;
1218
1219 const Real exchRateATMlevel = 1.0;
1220 const Real equityFxCorrelation = -0.75;
1221
1222 const Handle<YieldTermStructure> domesticTS(
1223 flatRate(today, forward: domesticR, dc));
1224
1225 const Handle<YieldTermStructure> divTS(
1226 flatRate(today, forward: q, dc));
1227
1228 const Handle<BlackVolTermStructure> volTS(
1229 flatVol(today, volatility: vol, dc));
1230
1231 const Handle<Quote> spot(
1232 ext::make_shared<SimpleQuote>(args: s));
1233
1234 const ext::shared_ptr<BlackScholesMertonProcess> bsmProcess
1235 = ext::make_shared<BlackScholesMertonProcess>(
1236 args: spot, args: divTS, args: domesticTS, args: volTS);
1237
1238 const ext::shared_ptr<YieldTermStructure> foreignTS
1239 = flatRate(today, forward: foreignR, dc);
1240
1241 const ext::shared_ptr<BlackVolTermStructure> fxVolTS
1242 = flatVol(today, volatility: fxVol, dc);
1243
1244 const ext::shared_ptr<FdmQuantoHelper> quantoHelper
1245 = ext::make_shared<FdmQuantoHelper>(
1246 args: domesticTS.currentLink(),
1247 args: foreignTS,
1248 args: fxVolTS,
1249 args: equityFxCorrelation, args: exchRateATMlevel);
1250
1251 const Real strike = 105.0;
1252
1253 std::vector<Date> dividendDates = { today + Period(6, Months) };
1254 std::vector<Real> dividendAmounts = { 8.0 };
1255 auto dividends = DividendVector(dividendDates, dividends: dividendAmounts);
1256
1257 VanillaOption option(
1258 ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args: strike),
1259 ext::make_shared<AmericanExercise>(args: maturity));
1260
1261 option.setPricingEngine(
1262 ext::make_shared<FdBlackScholesVanillaEngine>(
1263 args: bsmProcess, args&: dividends, args: quantoHelper, args: 100, args: 400, args: 1));
1264
1265 const Real tol = 1e-4;
1266 const Real expected = 8.90611734;
1267 const Real bsCalculated = option.NPV();
1268
1269 if (std::fabs(x: expected - bsCalculated) > tol) {
1270 BOOST_ERROR("failed to reproduce American quanto option prices "
1271 "with the Black-Scholes-Merton model"
1272 << "\n calculated: " << bsCalculated
1273 << "\n expected: " << expected);
1274 }
1275
1276 option.setPricingEngine(
1277 ext::make_shared<FdBlackScholesVanillaEngine>(
1278 args: bsmProcess, args&: dividends, args: quantoHelper, args: 100, args: 400, args: 1));
1279
1280 const Real localVolCalculated = option.NPV();
1281 if (std::fabs(x: expected - localVolCalculated) > tol) {
1282 BOOST_ERROR("failed to reproduce American quanto option prices "
1283 "with the Local Volatility model"
1284 << "\n calculated: " << localVolCalculated
1285 << "\n expected: " << expected);
1286 }
1287
1288 const Real tolBetweenBSandLocalVol = 1e-6;
1289 if (std::fabs(x: bsCalculated - localVolCalculated) > tolBetweenBSandLocalVol) {
1290 BOOST_ERROR("difference between American quanto option prices "
1291 "for Local Volatility and Black-Scholes model"
1292 << "\n calculated Local Vol : " << localVolCalculated
1293 << "\n calculated Black-Scholes: " << bsCalculated);
1294 }
1295
1296 VanillaOption divOption(
1297 ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args: strike),
1298 ext::make_shared<AmericanExercise>(args: maturity));
1299
1300 const Real v0 = vol*vol;
1301 const Real kappa = 1.0;
1302 const Real theta = v0;
1303 const Real sigma = 1e-4;
1304 const Real rho = 0.0;
1305
1306 const ext::shared_ptr<HestonModel> hestonModel =
1307 ext::make_shared<HestonModel>(
1308 args: ext::make_shared<HestonProcess>(
1309 args: domesticTS, args: divTS, args: spot, args: v0, args: kappa, args: theta, args: sigma, args: rho));
1310
1311 divOption.setPricingEngine(
1312 ext::make_shared<FdHestonVanillaEngine>(
1313 args: hestonModel, args&: dividends, args: quantoHelper, args: 100, args: 400, args: 3, args: 1));
1314
1315 const Real hestonCalculated = divOption.NPV();
1316
1317 if (std::fabs(x: expected - hestonCalculated) > tol) {
1318 BOOST_ERROR("failed to reproduce American quanto option prices "
1319 "with the Heston model"
1320 << "\n calculated: " << hestonCalculated
1321 << "\n expected: " << expected);
1322 }
1323
1324 const ext::shared_ptr<LocalVolTermStructure> localConstVol =
1325 ext::make_shared<LocalConstantVol>(args: today, args: 2.0, args: dc);
1326
1327 const ext::shared_ptr<HestonModel> hestonModel05 =
1328 ext::make_shared<HestonModel>(
1329 args: ext::make_shared<HestonProcess>(
1330 args: domesticTS, args: divTS, args: spot, args: 0.25*v0, args: kappa, args: 0.25*theta, args: sigma, args: rho));
1331
1332 divOption.setPricingEngine(
1333 ext::make_shared<FdHestonVanillaEngine>(
1334 args: hestonModel05, args&: dividends, args: quantoHelper, args: 100, args: 400, args: 3, args: 1,
1335 args: FdmSchemeDesc::Hundsdorfer(), args: localConstVol));
1336
1337 const Real hestoSlvCalculated = divOption.NPV();
1338
1339 if (std::fabs(x: expected - hestoSlvCalculated) > tol) {
1340 BOOST_ERROR("failed to reproduce American quanto option prices "
1341 "with the Heston Local Volatility model"
1342 << "\n calculated: " << hestoSlvCalculated
1343 << "\n expected: " << expected);
1344 }
1345}
1346
1347test_suite* QuantoOptionTest::suite() {
1348 auto* suite = BOOST_TEST_SUITE("Quanto option tests");
1349 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testValues));
1350 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testGreeks));
1351 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testForwardValues));
1352 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testForwardGreeks));
1353 suite->add(QUANTLIB_TEST_CASE(
1354 &QuantoOptionTest::testForwardPerformanceValues));
1355 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testBarrierValues));
1356 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testFDMQuantoHelper));
1357 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testPDEOptionValues));
1358
1359 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testAmericanQuantoOption));
1360 return suite;
1361}
1362
1363test_suite* QuantoOptionTest::experimental() {
1364 auto* suite = BOOST_TEST_SUITE("Experimental quanto option tests");
1365 suite->add(QUANTLIB_TEST_CASE(&QuantoOptionTest::testDoubleBarrierValues));
1366 return suite;
1367}
1368
1369

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