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