| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2008 Yee Man Chan |
| 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 "gjrgarchmodel.hpp" |
| 21 | #include "utilities.hpp" |
| 22 | #include <ql/processes/gjrgarchprocess.hpp> |
| 23 | #include <ql/math/distributions/normaldistribution.hpp> |
| 24 | #include <ql/models/equity/gjrgarchmodel.hpp> |
| 25 | #include <ql/models/equity/hestonmodelhelper.hpp> |
| 26 | #include <ql/pricingengines/vanilla/analyticgjrgarchengine.hpp> |
| 27 | #include <ql/pricingengines/vanilla/mceuropeangjrgarchengine.hpp> |
| 28 | #include <ql/pricingengines/blackformula.hpp> |
| 29 | #include <ql/time/calendars/target.hpp> |
| 30 | #include <ql/time/calendars/nullcalendar.hpp> |
| 31 | #include <ql/time/daycounters/actual365fixed.hpp> |
| 32 | #include <ql/time/daycounters/actual360.hpp> |
| 33 | #include <ql/time/daycounters/actualactual.hpp> |
| 34 | #include <ql/termstructures/yield/zerocurve.hpp> |
| 35 | #include <ql/termstructures/yield/flatforward.hpp> |
| 36 | #include <ql/math/optimization/simplex.hpp> |
| 37 | #include <ql/time/period.hpp> |
| 38 | #include <ql/quotes/simplequote.hpp> |
| 39 | |
| 40 | using namespace QuantLib; |
| 41 | using namespace boost::unit_test_framework; |
| 42 | |
| 43 | void GJRGARCHModelTest::testEngines() { |
| 44 | BOOST_TEST_MESSAGE( |
| 45 | "Testing Monte Carlo GJR-GARCH engine against " |
| 46 | "analytic GJR-GARCH engine..." ); |
| 47 | |
| 48 | DayCounter dayCounter = ActualActual(ActualActual::ISDA); |
| 49 | |
| 50 | const Date today = Date::todaysDate(); |
| 51 | Handle<YieldTermStructure> riskFreeTS(flatRate(today, forward: 0.05, dc: dayCounter)); |
| 52 | Handle<YieldTermStructure> dividendTS(flatRate(today, forward: 0.0, dc: dayCounter)); |
| 53 | |
| 54 | const Real s0 = 50.0; |
| 55 | const Real omega = 2.0e-6; |
| 56 | const Real alpha = 0.024; |
| 57 | const Real beta = 0.93; |
| 58 | const Real gamma = 0.059; |
| 59 | const Real daysPerYear = 365.0; // number of trading days per year |
| 60 | const Size maturity[] = {90, 180}; |
| 61 | const Real strike[] = {35,40,45,50,55,60}; |
| 62 | const Real Lambda[] = {0.0,0.1,0.2}; |
| 63 | Real analytic[3][2][6]; // correct values of analytic approximation |
| 64 | analytic[0][0][0] = 15.4315; |
| 65 | analytic[0][0][1] = 10.5552; |
| 66 | analytic[0][0][2] = 5.9625; |
| 67 | analytic[0][0][3] = 2.3282; |
| 68 | analytic[0][0][4] = 0.5408; |
| 69 | analytic[0][0][5] = 0.0835; |
| 70 | analytic[0][1][0] = 15.8969; |
| 71 | analytic[0][1][1] = 11.2173; |
| 72 | analytic[0][1][2] = 6.9112; |
| 73 | analytic[0][1][3] = 3.4788; |
| 74 | analytic[0][1][4] = 1.3769; |
| 75 | analytic[0][1][5] = 0.4357; |
| 76 | analytic[1][0][0] = 15.4556; |
| 77 | analytic[1][0][1] = 10.6929; |
| 78 | analytic[1][0][2] = 6.2381; |
| 79 | analytic[1][0][3] = 2.6831; |
| 80 | analytic[1][0][4] = 0.7822; |
| 81 | analytic[1][0][5] = 0.1738; |
| 82 | analytic[1][1][0] = 16.0587; |
| 83 | analytic[1][1][1] = 11.5338; |
| 84 | analytic[1][1][2] = 7.3170; |
| 85 | analytic[1][1][3] = 3.9074; |
| 86 | analytic[1][1][4] = 1.7279; |
| 87 | analytic[1][1][5] = 0.6568; |
| 88 | analytic[2][0][0] = 15.8000; |
| 89 | analytic[2][0][1] = 11.2734; |
| 90 | analytic[2][0][2] = 7.0376; |
| 91 | analytic[2][0][3] = 3.6767; |
| 92 | analytic[2][0][4] = 1.5871; |
| 93 | analytic[2][0][5] = 0.5934; |
| 94 | analytic[2][1][0] = 16.9286; |
| 95 | analytic[2][1][1] = 12.3170; |
| 96 | analytic[2][1][2] = 8.0405; |
| 97 | analytic[2][1][3] = 4.6348; |
| 98 | analytic[2][1][4] = 2.3429; |
| 99 | analytic[2][1][5] = 1.0590; |
| 100 | Real mcValues[3][2][6]; // correct values of Monte Carlo |
| 101 | mcValues[0][0][0] = 15.4332; |
| 102 | mcValues[0][0][1] = 10.5453; |
| 103 | mcValues[0][0][2] = 5.9351; |
| 104 | mcValues[0][0][3] = 2.3521; |
| 105 | mcValues[0][0][4] = 0.5597; |
| 106 | mcValues[0][0][5] = 0.0776; |
| 107 | mcValues[0][1][0] = 15.8910; |
| 108 | mcValues[0][1][1] = 11.1772; |
| 109 | mcValues[0][1][2] = 6.8827; |
| 110 | mcValues[0][1][3] = 3.5096; |
| 111 | mcValues[0][1][4] = 1.4196; |
| 112 | mcValues[0][1][5] = 0.4502; |
| 113 | mcValues[1][0][0] = 15.4580; |
| 114 | mcValues[1][0][1] = 10.6433; |
| 115 | mcValues[1][0][2] = 6.2019; |
| 116 | mcValues[1][0][3] = 2.7513; |
| 117 | mcValues[1][0][4] = 0.8374; |
| 118 | mcValues[1][0][5] = 0.1706; |
| 119 | mcValues[1][1][0] = 15.9884; |
| 120 | mcValues[1][1][1] = 11.4139; |
| 121 | mcValues[1][1][2] = 7.3103; |
| 122 | mcValues[1][1][3] = 4.0497; |
| 123 | mcValues[1][1][4] = 1.8862; |
| 124 | mcValues[1][1][5] = 0.7322; |
| 125 | mcValues[2][0][0] = 15.6619; |
| 126 | mcValues[2][0][1] = 11.1263; |
| 127 | mcValues[2][0][2] = 7.0968; |
| 128 | mcValues[2][0][3] = 3.9152; |
| 129 | mcValues[2][0][4] = 1.8133; |
| 130 | mcValues[2][0][5] = 0.7010; |
| 131 | mcValues[2][1][0] = 16.5195; |
| 132 | mcValues[2][1][1] = 12.3181; |
| 133 | mcValues[2][1][2] = 8.6085; |
| 134 | mcValues[2][1][3] = 5.5700; |
| 135 | mcValues[2][1][4] = 3.3103; |
| 136 | mcValues[2][1][5] = 1.8053; |
| 137 | |
| 138 | for (Size k = 0; k < 3; ++k) { |
| 139 | Real lambda = Lambda[k]; |
| 140 | Real m1 = beta+(alpha+gamma*CumulativeNormalDistribution()(lambda)) |
| 141 | *(1.0+lambda*lambda)+gamma*lambda*std::exp(x: -lambda*lambda/2.0) |
| 142 | /std::sqrt(x: 2.0*M_PI); |
| 143 | Real v0 = omega/(1.0-m1); |
| 144 | Handle<Quote> q(ext::shared_ptr<Quote>(new SimpleQuote(s0))); |
| 145 | ext::shared_ptr<GJRGARCHProcess> process(new GJRGARCHProcess( |
| 146 | riskFreeTS, dividendTS, q, v0, omega, alpha, beta, gamma, lambda, daysPerYear)); |
| 147 | ext::shared_ptr<PricingEngine> engine1 = |
| 148 | MakeMCEuropeanGJRGARCHEngine<PseudoRandom>(process) |
| 149 | .withStepsPerYear(steps: 20) |
| 150 | .withAbsoluteTolerance(tolerance: 0.02) |
| 151 | .withSeed(seed: 1234); |
| 152 | |
| 153 | ext::shared_ptr<PricingEngine> engine2( |
| 154 | new AnalyticGJRGARCHEngine(ext::make_shared<GJRGARCHModel>( |
| 155 | args&: process))); |
| 156 | for (Size i = 0; i < 2; ++i) { |
| 157 | for (Size j = 0; j < 6; ++j) { |
| 158 | Real x = strike[j]; |
| 159 | |
| 160 | ext::shared_ptr<StrikedTypePayoff> payoff( |
| 161 | new PlainVanillaPayoff(Option::Call, x)); |
| 162 | Date exDate = today + maturity[i]; |
| 163 | ext::shared_ptr<Exercise> exercise( |
| 164 | new EuropeanExercise(exDate)); |
| 165 | |
| 166 | VanillaOption option(payoff, exercise); |
| 167 | |
| 168 | option.setPricingEngine(engine1); |
| 169 | Real calculated = option.NPV(); |
| 170 | |
| 171 | option.setPricingEngine(engine2); |
| 172 | Real expected = option.NPV(); |
| 173 | Real tolerance = 7.5e-2; |
| 174 | |
| 175 | if (std::fabs(x: expected - analytic[k][i][j]) > 2.0*tolerance) { |
| 176 | BOOST_ERROR("failed to match results from engines" |
| 177 | << "\n correct value: " |
| 178 | << analytic[k][i][j] |
| 179 | << "\n Analytic Approx.: " |
| 180 | << expected |
| 181 | << " +/- " << tolerance); |
| 182 | } |
| 183 | if (std::fabs(x: calculated-mcValues[k][i][j]) > 2.0*tolerance) { |
| 184 | BOOST_ERROR("failed to match results from engines" |
| 185 | << "\n correct value: " |
| 186 | << mcValues[k][i][j] |
| 187 | << "\n Monte Carlo: " << calculated |
| 188 | << " +/- " << tolerance); |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | |
| 196 | void GJRGARCHModelTest::testDAXCalibration() { |
| 197 | /* this example is taken from A. Sepp |
| 198 | Pricing European-Style Options under Jump Diffusion Processes |
| 199 | with Stochstic Volatility: Applications of Fourier Transform |
| 200 | http://math.ut.ee/~spartak/papers/stochjumpvols.pdf |
| 201 | */ |
| 202 | |
| 203 | BOOST_TEST_MESSAGE( |
| 204 | "Testing GJR-GARCH model calibration using DAX volatility data..." ); |
| 205 | |
| 206 | Date settlementDate(5, July, 2002); |
| 207 | Settings::instance().evaluationDate() = settlementDate; |
| 208 | |
| 209 | DayCounter dayCounter = Actual365Fixed(); |
| 210 | Calendar calendar = TARGET(); |
| 211 | |
| 212 | Integer t[] = { 13, 41, 75, 165, 256, 345, 524, 703 }; |
| 213 | Rate r[] = { 0.0357,0.0349,0.0341,0.0355,0.0359,0.0368,0.0386,0.0401 }; |
| 214 | |
| 215 | std::vector<Date> dates; |
| 216 | std::vector<Rate> rates; |
| 217 | dates.push_back(x: settlementDate); |
| 218 | rates.push_back(x: 0.0357); |
| 219 | Size i; |
| 220 | for (i = 0; i < 8; ++i) { |
| 221 | dates.push_back(x: settlementDate + t[i]); |
| 222 | rates.push_back(x: r[i]); |
| 223 | } |
| 224 | Handle<YieldTermStructure> riskFreeTS( |
| 225 | ext::shared_ptr<YieldTermStructure>( |
| 226 | new ZeroCurve(dates, rates, dayCounter))); |
| 227 | |
| 228 | Handle<YieldTermStructure> dividendTS( |
| 229 | flatRate(today: settlementDate, forward: 0.0, dc: dayCounter)); |
| 230 | |
| 231 | Volatility v[] = |
| 232 | { 0.6625,0.4875,0.4204,0.3667,0.3431,0.3267,0.3121,0.3121, |
| 233 | 0.6007,0.4543,0.3967,0.3511,0.3279,0.3154,0.2984,0.2921, |
| 234 | 0.5084,0.4221,0.3718,0.3327,0.3155,0.3027,0.2919,0.2889, |
| 235 | 0.4541,0.3869,0.3492,0.3149,0.2963,0.2926,0.2819,0.2800, |
| 236 | 0.4060,0.3607,0.3330,0.2999,0.2887,0.2811,0.2751,0.2775, |
| 237 | 0.3726,0.3396,0.3108,0.2781,0.2788,0.2722,0.2661,0.2686, |
| 238 | 0.3550,0.3277,0.3012,0.2781,0.2781,0.2661,0.2661,0.2681, |
| 239 | 0.3428,0.3209,0.2958,0.2740,0.2688,0.2627,0.2580,0.2620, |
| 240 | 0.3302,0.3062,0.2799,0.2631,0.2573,0.2533,0.2504,0.2544, |
| 241 | 0.3343,0.2959,0.2705,0.2540,0.2504,0.2464,0.2448,0.2462, |
| 242 | 0.3460,0.2845,0.2624,0.2463,0.2425,0.2385,0.2373,0.2422, |
| 243 | 0.3857,0.2860,0.2578,0.2399,0.2357,0.2327,0.2312,0.2351, |
| 244 | 0.3976,0.2860,0.2607,0.2356,0.2297,0.2268,0.2241,0.2320 }; |
| 245 | |
| 246 | Handle<Quote> s0(ext::shared_ptr<Quote>(new SimpleQuote(4468.17))); |
| 247 | Real strike[] = { 3400,3600,3800,4000,4200,4400, |
| 248 | 4500,4600,4800,5000,5200,5400,5600 }; |
| 249 | |
| 250 | std::vector<ext::shared_ptr<CalibrationHelper> > options; |
| 251 | |
| 252 | const Real omega = 2.0e-6; |
| 253 | const Real alpha = 0.024; |
| 254 | const Real beta = 0.93; |
| 255 | const Real gamma = 0.059; |
| 256 | const Real lambda = 0.1; |
| 257 | const Real daysPerYear = 365.0; // number of trading days per year |
| 258 | const Real m1 = beta+(alpha+gamma*CumulativeNormalDistribution()(lambda)) |
| 259 | *(1.0+lambda*lambda)+gamma*lambda*std::exp(x: -lambda*lambda/2.0) |
| 260 | /std::sqrt(x: 2.0*M_PI); |
| 261 | const Real v0 = omega/(1.0-m1); |
| 262 | |
| 263 | ext::shared_ptr<GJRGARCHProcess> process(new GJRGARCHProcess( |
| 264 | riskFreeTS, dividendTS, s0, v0, |
| 265 | omega, alpha, beta, gamma, lambda, daysPerYear)); |
| 266 | ext::shared_ptr<GJRGARCHModel> model(new GJRGARCHModel(process)); |
| 267 | |
| 268 | ext::shared_ptr<PricingEngine> engine( |
| 269 | new AnalyticGJRGARCHEngine(ext::shared_ptr<GJRGARCHModel>(model))); |
| 270 | |
| 271 | for (Size s = 3; s < 10; ++s) { |
| 272 | for (Size m = 0; m < 3; ++m) { |
| 273 | Handle<Quote> vol(ext::shared_ptr<Quote>( |
| 274 | new SimpleQuote(v[s*8+m]))); |
| 275 | |
| 276 | Period maturity((int)((t[m]+3)/7.), Weeks); // round to weeks |
| 277 | ext::shared_ptr<BlackCalibrationHelper> option( |
| 278 | new HestonModelHelper(maturity, calendar, |
| 279 | s0->value(), strike[s], vol, |
| 280 | riskFreeTS, dividendTS, |
| 281 | BlackCalibrationHelper::ImpliedVolError)); |
| 282 | option->setPricingEngine(engine); |
| 283 | options.push_back(x: option); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | Simplex om(0.05); |
| 288 | model->calibrate(options, method&: om, |
| 289 | endCriteria: EndCriteria(400, 40, 1.0e-8, 1.0e-8, 1.0e-8)); |
| 290 | |
| 291 | Real sse = 0; |
| 292 | for (i = 0; i < options.size(); ++i) { |
| 293 | const Real diff = options[i]->calibrationError()*100.0; |
| 294 | sse += diff*diff; |
| 295 | } |
| 296 | Real maxExpected = 15; |
| 297 | if (sse > maxExpected) { |
| 298 | BOOST_FAIL("Failed to reproduce calibration error" |
| 299 | << "\n calculated: " << sse |
| 300 | << "\n expected: < " << maxExpected); |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | test_suite* GJRGARCHModelTest::suite(SpeedLevel speed) { |
| 305 | auto* suite = BOOST_TEST_SUITE("GJR-GARCH model tests" ); |
| 306 | |
| 307 | if (speed <= Fast) { |
| 308 | suite->add(QUANTLIB_TEST_CASE(&GJRGARCHModelTest::testDAXCalibration)); |
| 309 | } |
| 310 | |
| 311 | if (speed == Slow) { |
| 312 | suite->add(QUANTLIB_TEST_CASE(&GJRGARCHModelTest::testEngines)); |
| 313 | } |
| 314 | |
| 315 | return suite; |
| 316 | } |
| 317 | |