| 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) 2007 StatPro Italia srl |
| 6 | |
| 7 | This file is part of QuantLib, a free-software/open-source library |
| 8 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 9 | |
| 10 | QuantLib is free software: you can redistribute it and/or modify it |
| 11 | under the terms of the QuantLib license. You should have received a |
| 12 | copy of the license along with this program; if not, please email |
| 13 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 14 | <http://quantlib.org/license.shtml>. |
| 15 | |
| 16 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 17 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 18 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 19 | */ |
| 20 | |
| 21 | #include "jumpdiffusion.hpp" |
| 22 | #include "utilities.hpp" |
| 23 | #include <ql/time/daycounters/actual360.hpp> |
| 24 | #include <ql/instruments/europeanoption.hpp> |
| 25 | #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp> |
| 26 | #include <ql/pricingengines/vanilla/jumpdiffusionengine.hpp> |
| 27 | #include <ql/processes/merton76process.hpp> |
| 28 | #include <ql/termstructures/yield/flatforward.hpp> |
| 29 | #include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp> |
| 30 | #include <ql/utilities/dataformatters.hpp> |
| 31 | #include <map> |
| 32 | |
| 33 | using namespace QuantLib; |
| 34 | using namespace boost::unit_test_framework; |
| 35 | |
| 36 | #undef REPORT_FAILURE_1 |
| 37 | #define REPORT_FAILURE_1(greekName, payoff, exercise, s, q, r, today, v, \ |
| 38 | intensity, meanLogJump, jumpVol, expected, \ |
| 39 | calculated, error, tolerance) \ |
| 40 | BOOST_FAIL(exerciseTypeToString(exercise) << " " \ |
| 41 | << payoff->optionType() << " option with " \ |
| 42 | << payoffTypeToString(payoff) << " payoff:\n" \ |
| 43 | << " underlying value: " << s << "\n" \ |
| 44 | << " strike: " << payoff->strike() <<"\n" \ |
| 45 | << " dividend yield: " << io::rate(q) << "\n" \ |
| 46 | << " risk-free rate: " << io::rate(r) << "\n" \ |
| 47 | << " reference date: " << today << "\n" \ |
| 48 | << " maturity: " << exercise->lastDate() << "\n" \ |
| 49 | << " volatility: " << io::volatility(v) << "\n\n" \ |
| 50 | << " intensity: " << intensity << "\n" \ |
| 51 | << " mean log-jump: " << meanLogJump << "\n" \ |
| 52 | << " jump volatility: " << jumpVol << "\n\n" \ |
| 53 | << " expected " << greekName << ": " << expected << "\n" \ |
| 54 | << " calculated " << greekName << ": " << calculated << "\n"\ |
| 55 | << " error: " << error << "\n" \ |
| 56 | << " tolerance: " << tolerance); |
| 57 | |
| 58 | #undef REPORT_FAILURE_2 |
| 59 | #define REPORT_FAILURE_2(greekName, payoff, exercise, s, q, r, today, v, \ |
| 60 | intensity, gamma, expected, calculated, \ |
| 61 | error, tolerance) \ |
| 62 | BOOST_FAIL(exerciseTypeToString(exercise) << " " \ |
| 63 | << payoff->optionType() << " option with " \ |
| 64 | << payoffTypeToString(payoff) << " payoff:\n" \ |
| 65 | << " underlying value: " << s << "\n" \ |
| 66 | << " strike: " << payoff->strike() <<"\n" \ |
| 67 | << " dividend yield: " << io::rate(q) << "\n" \ |
| 68 | << " risk-free rate: " << io::rate(r) << "\n" \ |
| 69 | << " reference date: " << today << "\n" \ |
| 70 | << " maturity: " << exercise->lastDate() << "\n" \ |
| 71 | << " volatility: " << io::volatility(v) << "\n" \ |
| 72 | << " intensity: " << intensity << "\n" \ |
| 73 | << " gamma: " << gamma << "\n\n" \ |
| 74 | << " expected " << greekName << ": " << expected << "\n" \ |
| 75 | << " calculated " << greekName << ": " << calculated << "\n"\ |
| 76 | << " error: " << error << "\n" \ |
| 77 | << " tolerance: " << tolerance); |
| 78 | |
| 79 | namespace { |
| 80 | |
| 81 | struct HaugMertonData { |
| 82 | Option::Type type; |
| 83 | Real strike; |
| 84 | Real s; // spot |
| 85 | Rate q; // dividend |
| 86 | Rate r; // risk-free rate |
| 87 | Time t; // time to maturity |
| 88 | Volatility v; // volatility |
| 89 | Real jumpIntensity; |
| 90 | Real gamma; |
| 91 | Real result; // result |
| 92 | Real tol; // tolerance |
| 93 | }; |
| 94 | |
| 95 | } |
| 96 | |
| 97 | |
| 98 | void JumpDiffusionTest::testMerton76() { |
| 99 | |
| 100 | BOOST_TEST_MESSAGE("Testing Merton 76 jump-diffusion model " |
| 101 | "for European options..." ); |
| 102 | |
| 103 | /* The data below are from |
| 104 | "Option pricing formulas", E.G. Haug, McGraw-Hill 1998, pag 9 |
| 105 | |
| 106 | Haug use the arbitrary truncation criterium of 11 terms in the sum, |
| 107 | which doesn't guarantee convergence up to 1e-2. |
| 108 | Using Haug's criterium Haug's values have been correctly reproduced. |
| 109 | the following values have the right 1e-2 accuracy: any value different |
| 110 | from Haug has been noted. |
| 111 | */ |
| 112 | HaugMertonData values[] = { |
| 113 | // type, strike, spot, q, r, t, vol, int, gamma, value, tol |
| 114 | // gamma = 0.25, strike = 80 |
| 115 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 20.67, .tol: 1e-2 }, |
| 116 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 21.74, .tol: 1e-2 }, |
| 117 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 23.63, .tol: 1e-2 }, |
| 118 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 20.65, .tol: 1e-2 }, |
| 119 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 21.70, .tol: 1e-2 }, |
| 120 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 23.61, .tol: 1e-2 }, |
| 121 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 20.64, .tol: 1e-2 }, |
| 122 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 21.70, .tol: 1e-2 }, |
| 123 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 23.61, .tol: 1e-2 }, // Haug 23.28 |
| 124 | // gamma = 0.25, strike = 90 |
| 125 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 11.00, .tol: 1e-2 }, |
| 126 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 12.74, .tol: 1e-2 }, |
| 127 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 15.40, .tol: 1e-2 }, |
| 128 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 10.98, .tol: 1e-2 }, |
| 129 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 12.75, .tol: 1e-2 }, |
| 130 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 15.42, .tol: 1e-2 }, |
| 131 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 10.98, .tol: 1e-2 }, |
| 132 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 12.75, .tol: 1e-2 }, |
| 133 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 15.42, .tol: 1e-2 }, // Haug 15.20 |
| 134 | // gamma = 0.25, strike = 100 |
| 135 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 3.42, .tol: 1e-2 }, |
| 136 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 5.88, .tol: 1e-2 }, |
| 137 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 8.95, .tol: 1e-2 }, |
| 138 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 3.51, .tol: 1e-2 }, |
| 139 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 5.96, .tol: 1e-2 }, |
| 140 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 9.02, .tol: 1e-2 }, |
| 141 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 3.53, .tol: 1e-2 }, |
| 142 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 5.97, .tol: 1e-2 }, |
| 143 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 9.03, .tol: 1e-2 }, // Haug 8.89 |
| 144 | // gamma = 0.25, strike = 110 |
| 145 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 0.55, .tol: 1e-2 }, |
| 146 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 2.11, .tol: 1e-2 }, |
| 147 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 4.67, .tol: 1e-2 }, |
| 148 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 0.56, .tol: 1e-2 }, |
| 149 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 2.16, .tol: 1e-2 }, |
| 150 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 4.73, .tol: 1e-2 }, |
| 151 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 0.56, .tol: 1e-2 }, |
| 152 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 2.17, .tol: 1e-2 }, |
| 153 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 4.74, .tol: 1e-2 }, // Haug 4.66 |
| 154 | // gamma = 0.25, strike = 120 |
| 155 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 0.10, .tol: 1e-2 }, |
| 156 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 0.64, .tol: 1e-2 }, |
| 157 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.25, .result: 2.23, .tol: 1e-2 }, |
| 158 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 0.06, .tol: 1e-2 }, |
| 159 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 0.63, .tol: 1e-2 }, |
| 160 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.25, .result: 2.25, .tol: 1e-2 }, |
| 161 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 0.05, .tol: 1e-2 }, |
| 162 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 0.62, .tol: 1e-2 }, |
| 163 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.25, .result: 2.25, .tol: 1e-2 }, // Haug 2.21 |
| 164 | |
| 165 | // gamma = 0.50, strike = 80 |
| 166 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 20.72, .tol: 1e-2 }, |
| 167 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 21.83, .tol: 1e-2 }, |
| 168 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 23.71, .tol: 1e-2 }, |
| 169 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 20.66, .tol: 1e-2 }, |
| 170 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 21.73, .tol: 1e-2 }, |
| 171 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 23.63, .tol: 1e-2 }, |
| 172 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 20.65, .tol: 1e-2 }, |
| 173 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 21.71, .tol: 1e-2 }, |
| 174 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 23.61, .tol: 1e-2 }, // Haug 23.28 |
| 175 | // gamma = 0.50, strike = 90 |
| 176 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 11.04, .tol: 1e-2 }, |
| 177 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 12.72, .tol: 1e-2 }, |
| 178 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 15.34, .tol: 1e-2 }, |
| 179 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 11.02, .tol: 1e-2 }, |
| 180 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 12.76, .tol: 1e-2 }, |
| 181 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 15.41, .tol: 1e-2 }, |
| 182 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 11.00, .tol: 1e-2 }, |
| 183 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 12.75, .tol: 1e-2 }, |
| 184 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 15.41, .tol: 1e-2 }, // Haug 15.18 |
| 185 | // gamma = 0.50, strike = 100 |
| 186 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 3.14, .tol: 1e-2 }, |
| 187 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 5.58, .tol: 1e-2 }, |
| 188 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 8.71, .tol: 1e-2 }, |
| 189 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 3.39, .tol: 1e-2 }, |
| 190 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 5.87, .tol: 1e-2 }, |
| 191 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 8.96, .tol: 1e-2 }, |
| 192 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 3.46, .tol: 1e-2 }, |
| 193 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 5.93, .tol: 1e-2 }, |
| 194 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 9.00, .tol: 1e-2 }, // Haug 8.85 |
| 195 | // gamma = 0.50, strike = 110 |
| 196 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 0.53, .tol: 1e-2 }, |
| 197 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 1.93, .tol: 1e-2 }, |
| 198 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 4.42, .tol: 1e-2 }, |
| 199 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 0.58, .tol: 1e-2 }, |
| 200 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 2.11, .tol: 1e-2 }, |
| 201 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 4.67, .tol: 1e-2 }, |
| 202 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 0.57, .tol: 1e-2 }, |
| 203 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 2.14, .tol: 1e-2 }, |
| 204 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 4.71, .tol: 1e-2 }, // Haug 4.62 |
| 205 | // gamma = 0.50, strike = 120 |
| 206 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 0.19, .tol: 1e-2 }, |
| 207 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 0.71, .tol: 1e-2 }, |
| 208 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.50, .result: 2.15, .tol: 1e-2 }, |
| 209 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 0.10, .tol: 1e-2 }, |
| 210 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 0.66, .tol: 1e-2 }, |
| 211 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.50, .result: 2.23, .tol: 1e-2 }, |
| 212 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 0.07, .tol: 1e-2 }, |
| 213 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 0.64, .tol: 1e-2 }, |
| 214 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.50, .result: 2.24, .tol: 1e-2 }, // Haug 2.19 |
| 215 | |
| 216 | // gamma = 0.75, strike = 80 |
| 217 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 20.79, .tol: 1e-2 }, |
| 218 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 21.96, .tol: 1e-2 }, |
| 219 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 23.86, .tol: 1e-2 }, |
| 220 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 20.68, .tol: 1e-2 }, |
| 221 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 21.78, .tol: 1e-2 }, |
| 222 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 23.67, .tol: 1e-2 }, |
| 223 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 20.66, .tol: 1e-2 }, |
| 224 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 21.74, .tol: 1e-2 }, |
| 225 | { .type: Option::Call, .strike: 80.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 23.64, .tol: 1e-2 }, // Haug 23.30 |
| 226 | // gamma = 0.75, strike = 90 |
| 227 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 11.11, .tol: 1e-2 }, |
| 228 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 12.75, .tol: 1e-2 }, |
| 229 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 15.30, .tol: 1e-2 }, |
| 230 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 11.09, .tol: 1e-2 }, |
| 231 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 12.78, .tol: 1e-2 }, |
| 232 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 15.39, .tol: 1e-2 }, |
| 233 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 11.04, .tol: 1e-2 }, |
| 234 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 12.76, .tol: 1e-2 }, |
| 235 | { .type: Option::Call, .strike: 90.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 15.40, .tol: 1e-2 }, // Haug 15.17 |
| 236 | // gamma = 0.75, strike = 100 |
| 237 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 2.70, .tol: 1e-2 }, |
| 238 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 5.08, .tol: 1e-2 }, |
| 239 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 8.24, .tol: 1e-2 }, |
| 240 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 3.16, .tol: 1e-2 }, |
| 241 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 5.71, .tol: 1e-2 }, |
| 242 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 8.85, .tol: 1e-2 }, |
| 243 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 3.33, .tol: 1e-2 }, |
| 244 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 5.85, .tol: 1e-2 }, |
| 245 | { .type: Option::Call, .strike: 100.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 8.95, .tol: 1e-2 }, // Haug 8.79 |
| 246 | // gamma = 0.75, strike = 110 |
| 247 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 0.54, .tol: 1e-2 }, |
| 248 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 1.69, .tol: 1e-2 }, |
| 249 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 3.99, .tol: 1e-2 }, |
| 250 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 0.62, .tol: 1e-2 }, |
| 251 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 2.05, .tol: 1e-2 }, |
| 252 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 4.57, .tol: 1e-2 }, |
| 253 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 0.60, .tol: 1e-2 }, |
| 254 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 2.11, .tol: 1e-2 }, |
| 255 | { .type: Option::Call, .strike: 110.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 4.66, .tol: 1e-2 }, // Haug 4.56 |
| 256 | // gamma = 0.75, strike = 120 |
| 257 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 0.29, .tol: 1e-2 }, |
| 258 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 0.84, .tol: 1e-2 }, |
| 259 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 1.0, .gamma: 0.75, .result: 2.09, .tol: 1e-2 }, |
| 260 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 0.15, .tol: 1e-2 }, |
| 261 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 0.71, .tol: 1e-2 }, |
| 262 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25, .jumpIntensity: 5.0, .gamma: 0.75, .result: 2.21, .tol: 1e-2 }, |
| 263 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.10, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 0.11, .tol: 1e-2 }, |
| 264 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.25, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 0.67, .tol: 1e-2 }, |
| 265 | { .type: Option::Call, .strike: 120.00, .s: 100.00, .q: 0.00, .r: 0.08, .t: 0.50, .v: 0.25,.jumpIntensity: 10.0, .gamma: 0.75, .result: 2.23, .tol: 1e-2 } // Haug 2.17 |
| 266 | }; |
| 267 | |
| 268 | |
| 269 | |
| 270 | DayCounter dc = Actual360(); |
| 271 | Date today = Date::todaysDate(); |
| 272 | |
| 273 | ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0)); |
| 274 | ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0)); |
| 275 | ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc); |
| 276 | ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0)); |
| 277 | ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc); |
| 278 | ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0)); |
| 279 | ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc); |
| 280 | |
| 281 | ext::shared_ptr<SimpleQuote> jumpIntensity(new SimpleQuote(0.0)); |
| 282 | ext::shared_ptr<SimpleQuote> meanLogJump(new SimpleQuote(0.0)); |
| 283 | ext::shared_ptr<SimpleQuote> jumpVol(new SimpleQuote(0.0)); |
| 284 | |
| 285 | ext::shared_ptr<Merton76Process> stochProcess( |
| 286 | new Merton76Process(Handle<Quote>(spot), |
| 287 | Handle<YieldTermStructure>(qTS), |
| 288 | Handle<YieldTermStructure>(rTS), |
| 289 | Handle<BlackVolTermStructure>(volTS), |
| 290 | Handle<Quote>(jumpIntensity), |
| 291 | Handle<Quote>(meanLogJump), |
| 292 | Handle<Quote>(jumpVol))); |
| 293 | ext::shared_ptr<PricingEngine> engine( |
| 294 | new JumpDiffusionEngine(stochProcess)); |
| 295 | |
| 296 | for (auto& value : values) { |
| 297 | |
| 298 | ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike)); |
| 299 | |
| 300 | Date exDate = today + timeToDays(t: value.t); |
| 301 | ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate)); |
| 302 | |
| 303 | spot->setValue(value.s); |
| 304 | qRate->setValue(value.q); |
| 305 | rRate->setValue(value.r); |
| 306 | |
| 307 | |
| 308 | jumpIntensity->setValue(value.jumpIntensity); |
| 309 | |
| 310 | // delta in Haug's notation |
| 311 | Real jVol = value.v * std::sqrt(x: value.gamma / value.jumpIntensity); |
| 312 | jumpVol->setValue(jVol); |
| 313 | |
| 314 | // z in Haug's notation |
| 315 | Real diffusionVol = value.v * std::sqrt(x: 1.0 - value.gamma); |
| 316 | vol ->setValue(diffusionVol); |
| 317 | |
| 318 | // Haug is assuming zero meanJump |
| 319 | Real meanJump = 0.0; |
| 320 | meanLogJump->setValue(std::log(x: 1.0+meanJump)-0.5*jVol*jVol); |
| 321 | |
| 322 | Volatility totalVol = |
| 323 | std::sqrt(x: value.jumpIntensity * jVol * jVol + diffusionVol * diffusionVol); |
| 324 | Volatility volError = std::fabs(x: totalVol - value.v); |
| 325 | QL_REQUIRE(volError<1e-13, |
| 326 | volError << " mismatch" ); |
| 327 | |
| 328 | EuropeanOption option(payoff, exercise); |
| 329 | option.setPricingEngine(engine); |
| 330 | |
| 331 | Real calculated = option.NPV(); |
| 332 | Real error = std::fabs(x: calculated - value.result); |
| 333 | if (error > value.tol) { |
| 334 | REPORT_FAILURE_2("value" , payoff, exercise, value.s, value.q, value.r, today, value.v, |
| 335 | value.jumpIntensity, value.gamma, value.result, calculated, error, |
| 336 | value.tol); |
| 337 | } |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | void JumpDiffusionTest::testGreeks() { |
| 342 | |
| 343 | BOOST_TEST_MESSAGE("Testing jump-diffusion option greeks..." ); |
| 344 | |
| 345 | std::map<std::string,Real> calculated, expected, tolerance; |
| 346 | tolerance["delta" ] = 1.0e-4; |
| 347 | tolerance["gamma" ] = 1.0e-4; |
| 348 | tolerance["theta" ] = 1.1e-4; |
| 349 | tolerance["rho" ] = 1.0e-4; |
| 350 | tolerance["divRho" ] = 1.0e-4; |
| 351 | tolerance["vega" ] = 1.0e-4; |
| 352 | |
| 353 | Option::Type types[] = { Option::Put, Option::Call }; |
| 354 | Real strikes[] = { 50.0, 100.0, 150.0 }; |
| 355 | Real underlyings[] = { 100.0 }; |
| 356 | Rate qRates[] = { -0.05, 0.0, 0.05 }; |
| 357 | Rate rRates[] = { 0.0, 0.01, 0.2 }; |
| 358 | // The testsuite check fails if a too short maturity is chosen(i.e. 1 year). |
| 359 | // The problem is in the theta calculation. With the finite difference(fd) method |
| 360 | // we might get values too close to the jump steps, invalidating the fd methodology |
| 361 | // for calculating greeks. |
| 362 | Time residualTimes[] = { 5.0 }; |
| 363 | Volatility vols[] = { 0.11 }; |
| 364 | Real jInt[] = { 1.0, 5.0 }; |
| 365 | Real mLJ[] = { -0.20, 0.0, 0.20 }; |
| 366 | Volatility jV[] = { 0.01, 0.25 }; |
| 367 | |
| 368 | DayCounter dc = Actual360(); |
| 369 | Date today = Date::todaysDate(); |
| 370 | Settings::instance().evaluationDate() = today; |
| 371 | |
| 372 | ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0)); |
| 373 | ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0)); |
| 374 | Handle<YieldTermStructure> qTS(flatRate(forward: qRate, dc)); |
| 375 | ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0)); |
| 376 | Handle<YieldTermStructure> rTS(flatRate(forward: rRate, dc)); |
| 377 | ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0)); |
| 378 | Handle<BlackVolTermStructure> volTS(flatVol(volatility: vol, dc)); |
| 379 | |
| 380 | ext::shared_ptr<SimpleQuote> jumpIntensity(new SimpleQuote(0.0)); |
| 381 | ext::shared_ptr<SimpleQuote> meanLogJump(new SimpleQuote(0.0)); |
| 382 | ext::shared_ptr<SimpleQuote> jumpVol(new SimpleQuote(0.0)); |
| 383 | |
| 384 | ext::shared_ptr<Merton76Process> stochProcess( |
| 385 | new Merton76Process(Handle<Quote>(spot), qTS, rTS, volTS, |
| 386 | Handle<Quote>(jumpIntensity), |
| 387 | Handle<Quote>(meanLogJump), |
| 388 | Handle<Quote>(jumpVol))); |
| 389 | |
| 390 | ext::shared_ptr<StrikedTypePayoff> payoff; |
| 391 | |
| 392 | // The jumpdiffusionengine greeks are very sensitive to the |
| 393 | // convergence level. A tolerance of 1.0e-08 is usually |
| 394 | // sufficient to get reasonable results |
| 395 | ext::shared_ptr<PricingEngine> engine( |
| 396 | new JumpDiffusionEngine(stochProcess,1e-08)); |
| 397 | |
| 398 | for (auto& type : types) { |
| 399 | for (Real strike : strikes) { |
| 400 | for (Real& jj1 : jInt) { |
| 401 | jumpIntensity->setValue(jj1); |
| 402 | for (Real& jj2 : mLJ) { |
| 403 | meanLogJump->setValue(jj2); |
| 404 | for (Real& jj3 : jV) { |
| 405 | jumpVol->setValue(jj3); |
| 406 | for (Real residualTime : residualTimes) { |
| 407 | Date exDate = today + timeToDays(t: residualTime); |
| 408 | ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate)); |
| 409 | for (Size kk = 0; kk < 1; kk++) { |
| 410 | // option to check |
| 411 | if (kk == 0) { |
| 412 | payoff = ext::shared_ptr<StrikedTypePayoff>( |
| 413 | new PlainVanillaPayoff(type, strike)); |
| 414 | } else if (kk == 1) { |
| 415 | payoff = ext::shared_ptr<StrikedTypePayoff>( |
| 416 | new CashOrNothingPayoff(type, strike, 100.0)); |
| 417 | } |
| 418 | EuropeanOption option(payoff, exercise); |
| 419 | option.setPricingEngine(engine); |
| 420 | |
| 421 | for (Real u : underlyings) { |
| 422 | for (Real q : qRates) { |
| 423 | for (Real r : rRates) { |
| 424 | for (Real v : vols) { |
| 425 | spot->setValue(u); |
| 426 | qRate->setValue(q); |
| 427 | rRate->setValue(r); |
| 428 | vol->setValue(v); |
| 429 | |
| 430 | Real value = option.NPV(); |
| 431 | calculated["delta" ] = option.delta(); |
| 432 | calculated["gamma" ] = option.gamma(); |
| 433 | calculated["theta" ] = option.theta(); |
| 434 | calculated["rho" ] = option.rho(); |
| 435 | calculated["divRho" ] = option.dividendRho(); |
| 436 | calculated["vega" ] = option.vega(); |
| 437 | |
| 438 | if (value > spot->value() * 1.0e-5) { |
| 439 | // perturb spot and get delta and gamma |
| 440 | Real du = u * 1.0e-5; |
| 441 | spot->setValue(u + du); |
| 442 | Real value_p = option.NPV(), |
| 443 | delta_p = option.delta(); |
| 444 | spot->setValue(u - du); |
| 445 | Real value_m = option.NPV(), |
| 446 | delta_m = option.delta(); |
| 447 | spot->setValue(u); |
| 448 | expected["delta" ] = |
| 449 | (value_p - value_m) / (2 * du); |
| 450 | expected["gamma" ] = |
| 451 | (delta_p - delta_m) / (2 * du); |
| 452 | |
| 453 | // perturb rates and get rho and dividend rho |
| 454 | Spread dr = 1.0e-5; |
| 455 | rRate->setValue(r + dr); |
| 456 | value_p = option.NPV(); |
| 457 | rRate->setValue(r - dr); |
| 458 | value_m = option.NPV(); |
| 459 | rRate->setValue(r); |
| 460 | expected["rho" ] = |
| 461 | (value_p - value_m) / (2 * dr); |
| 462 | |
| 463 | Spread dq = 1.0e-5; |
| 464 | qRate->setValue(q + dq); |
| 465 | value_p = option.NPV(); |
| 466 | qRate->setValue(q - dq); |
| 467 | value_m = option.NPV(); |
| 468 | qRate->setValue(q); |
| 469 | expected["divRho" ] = |
| 470 | (value_p - value_m) / (2 * dq); |
| 471 | |
| 472 | // perturb volatility and get vega |
| 473 | Volatility dv = v * 1.0e-4; |
| 474 | vol->setValue(v + dv); |
| 475 | value_p = option.NPV(); |
| 476 | vol->setValue(v - dv); |
| 477 | value_m = option.NPV(); |
| 478 | vol->setValue(v); |
| 479 | expected["vega" ] = |
| 480 | (value_p - value_m) / (2 * dv); |
| 481 | |
| 482 | // get theta from time-shifted options |
| 483 | Time dT = dc.yearFraction(d1: today - 1, d2: today + 1); |
| 484 | Settings::instance().evaluationDate() = |
| 485 | today - 1; |
| 486 | value_m = option.NPV(); |
| 487 | Settings::instance().evaluationDate() = |
| 488 | today + 1; |
| 489 | value_p = option.NPV(); |
| 490 | Settings::instance().evaluationDate() = today; |
| 491 | expected["theta" ] = (value_p - value_m) / dT; |
| 492 | // compare |
| 493 | std::map<std::string, Real>::iterator it; |
| 494 | for (it = expected.begin(); |
| 495 | it != expected.end(); ++it) { |
| 496 | std::string greek = it->first; |
| 497 | Real expct = expected[greek], |
| 498 | calcl = calculated[greek], |
| 499 | tol = tolerance[greek]; |
| 500 | Real error = std::fabs(x: expct - calcl); |
| 501 | if (error > tol) { |
| 502 | REPORT_FAILURE_1( |
| 503 | greek, payoff, exercise, u, q, r, |
| 504 | today, v, jj1, jj2, jj3, expct, |
| 505 | calcl, error, tol); |
| 506 | } |
| 507 | } |
| 508 | } |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | } // strike loop |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | } // type loop |
| 520 | } |
| 521 | |
| 522 | |
| 523 | test_suite* JumpDiffusionTest::suite() { |
| 524 | auto* suite = BOOST_TEST_SUITE("Jump-diffusion tests" ); |
| 525 | suite->add(QUANTLIB_TEST_CASE(&JumpDiffusionTest::testMerton76)); |
| 526 | suite->add(QUANTLIB_TEST_CASE(&JumpDiffusionTest::testGreeks)); |
| 527 | return suite; |
| 528 | } |
| 529 | |