| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2005, 2008 Klaus Spanderen |
| 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 "batesmodel.hpp" |
| 22 | #include "utilities.hpp" |
| 23 | #include <ql/time/calendars/target.hpp> |
| 24 | #include <ql/processes/batesprocess.hpp> |
| 25 | #include <ql/processes/merton76process.hpp> |
| 26 | #include <ql/instruments/europeanoption.hpp> |
| 27 | #include <ql/time/daycounters/actualactual.hpp> |
| 28 | #include <ql/termstructures/yield/flatforward.hpp> |
| 29 | #include <ql/termstructures/yield/zerocurve.hpp> |
| 30 | #include <ql/pricingengines/blackformula.hpp> |
| 31 | #include <ql/math/optimization/levenbergmarquardt.hpp> |
| 32 | #include <ql/pricingengines/vanilla/batesengine.hpp> |
| 33 | #include <ql/pricingengines/vanilla/jumpdiffusionengine.hpp> |
| 34 | #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp> |
| 35 | #include <ql/pricingengines/vanilla/mceuropeanhestonengine.hpp> |
| 36 | #include <ql/pricingengines/vanilla/fdbatesvanillaengine.hpp> |
| 37 | #include <ql/models/equity/batesmodel.hpp> |
| 38 | #include <ql/models/equity/hestonmodelhelper.hpp> |
| 39 | #include <ql/time/period.hpp> |
| 40 | #include <ql/quotes/simplequote.hpp> |
| 41 | |
| 42 | using namespace QuantLib; |
| 43 | using namespace boost::unit_test_framework; |
| 44 | |
| 45 | namespace bates_model_test { |
| 46 | |
| 47 | Real getCalibrationError( |
| 48 | std::vector<ext::shared_ptr<BlackCalibrationHelper> > & options) { |
| 49 | Real sse = 0; |
| 50 | for (auto& option : options) { |
| 51 | const Real diff = option->calibrationError() * 100.0; |
| 52 | sse += diff*diff; |
| 53 | } |
| 54 | return sse; |
| 55 | } |
| 56 | |
| 57 | } |
| 58 | |
| 59 | |
| 60 | void BatesModelTest::testAnalyticVsBlack() { |
| 61 | |
| 62 | BOOST_TEST_MESSAGE("Testing analytic Bates engine against Black formula..." ); |
| 63 | |
| 64 | Date settlementDate = Date::todaysDate(); |
| 65 | Settings::instance().evaluationDate() = settlementDate; |
| 66 | |
| 67 | DayCounter dayCounter = ActualActual(ActualActual::ISDA); |
| 68 | Date exerciseDate = settlementDate + 6*Months; |
| 69 | |
| 70 | ext::shared_ptr<StrikedTypePayoff> payoff( |
| 71 | new PlainVanillaPayoff(Option::Put, 30)); |
| 72 | ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate)); |
| 73 | |
| 74 | Handle<YieldTermStructure> riskFreeTS(flatRate(forward: 0.1, dc: dayCounter)); |
| 75 | Handle<YieldTermStructure> dividendTS(flatRate(forward: 0.04, dc: dayCounter)); |
| 76 | Handle<Quote> s0(ext::shared_ptr<Quote>(new SimpleQuote(32.0))); |
| 77 | |
| 78 | Real yearFraction = dayCounter.yearFraction(d1: settlementDate, d2: exerciseDate); |
| 79 | Real forwardPrice = s0->value()*std::exp(x: (0.1-0.04)*yearFraction); |
| 80 | Real expected = blackFormula(optionType: payoff->optionType(), strike: payoff->strike(), |
| 81 | forward: forwardPrice, stdDev: std::sqrt(x: 0.05*yearFraction)) * |
| 82 | std::exp(x: -0.1*yearFraction); |
| 83 | const Real v0 = 0.05; |
| 84 | const Real kappa = 5.0; |
| 85 | const Real theta = 0.05; |
| 86 | const Real sigma = 1.0e-4; |
| 87 | const Real rho = 0.0; |
| 88 | const Real lambda = 0.0001; |
| 89 | const Real nu = 0.0; |
| 90 | const Real delta = 0.0001; |
| 91 | |
| 92 | VanillaOption option(payoff, exercise); |
| 93 | |
| 94 | ext::shared_ptr<BatesProcess> process( |
| 95 | new BatesProcess(riskFreeTS, dividendTS, s0, v0, |
| 96 | kappa, theta, sigma, rho, lambda, nu, delta)); |
| 97 | |
| 98 | ext::shared_ptr<PricingEngine> engine(new BatesEngine( |
| 99 | ext::make_shared<BatesModel>(args&: process), 64)); |
| 100 | |
| 101 | option.setPricingEngine(engine); |
| 102 | Real calculated = option.NPV(); |
| 103 | |
| 104 | Real tolerance = 2.0e-7; |
| 105 | Real error = std::fabs(x: calculated - expected); |
| 106 | if (error > tolerance) { |
| 107 | BOOST_ERROR("failed to reproduce Black price with BatesEngine" |
| 108 | << std::fixed |
| 109 | << "\n calculated: " << calculated |
| 110 | << "\n expected: " << expected |
| 111 | << std::scientific |
| 112 | << "\n error: " << error); |
| 113 | } |
| 114 | |
| 115 | engine = ext::shared_ptr<PricingEngine>(new BatesDetJumpEngine( |
| 116 | ext::make_shared<BatesDetJumpModel>( |
| 117 | args&: process, args: 1.0, args: 0.0001), 64)); |
| 118 | |
| 119 | option.setPricingEngine(engine); |
| 120 | calculated = option.NPV(); |
| 121 | |
| 122 | error = std::fabs(x: calculated - expected); |
| 123 | if (error > tolerance) { |
| 124 | BOOST_ERROR("failed to reproduce Black price with " \ |
| 125 | "BatesDetJumpEngine" |
| 126 | << std::fixed |
| 127 | << "\n calculated: " << calculated |
| 128 | << "\n expected: " << expected |
| 129 | << std::scientific |
| 130 | << "\n error: " << error); |
| 131 | } |
| 132 | |
| 133 | engine = ext::shared_ptr<PricingEngine>(new BatesDoubleExpEngine( |
| 134 | ext::make_shared<BatesDoubleExpModel>( |
| 135 | args&: process, args: 0.0001, args: 0.0001, args: 0.0001), 64)); |
| 136 | |
| 137 | option.setPricingEngine(engine); |
| 138 | calculated = option.NPV(); |
| 139 | |
| 140 | error = std::fabs(x: calculated - expected); |
| 141 | if (error > tolerance) { |
| 142 | BOOST_ERROR("failed to reproduce Black price with BatesDoubleExpEngine" |
| 143 | << std::fixed |
| 144 | << "\n calculated: " << calculated |
| 145 | << "\n expected: " << expected |
| 146 | << std::scientific |
| 147 | << "\n error: " << error); |
| 148 | } |
| 149 | |
| 150 | engine = ext::shared_ptr<PricingEngine>(new BatesDoubleExpDetJumpEngine( |
| 151 | ext::make_shared<BatesDoubleExpDetJumpModel>( |
| 152 | |
| 153 | args&: process, args: 0.0001, args: 0.0001, args: 0.0001, args: 0.5, args: 1.0, args: 0.0001), 64)); |
| 154 | |
| 155 | option.setPricingEngine(engine); |
| 156 | calculated = option.NPV(); |
| 157 | |
| 158 | error = std::fabs(x: calculated - expected); |
| 159 | if (error > tolerance) { |
| 160 | BOOST_ERROR("failed to reproduce Black price with " \ |
| 161 | "BatesDoubleExpDetJumpEngine" |
| 162 | << std::fixed |
| 163 | << "\n calculated: " << calculated |
| 164 | << "\n expected: " << expected |
| 165 | << std::scientific |
| 166 | << "\n error: " << error); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | |
| 171 | void BatesModelTest::testAnalyticAndMcVsJumpDiffusion() { |
| 172 | |
| 173 | BOOST_TEST_MESSAGE("Testing analytic Bates engine against Merton-76 engine..." ); |
| 174 | |
| 175 | Date settlementDate = Date::todaysDate(); |
| 176 | Settings::instance().evaluationDate() = settlementDate; |
| 177 | |
| 178 | DayCounter dayCounter = ActualActual(ActualActual::ISDA); |
| 179 | |
| 180 | ext::shared_ptr<StrikedTypePayoff> payoff( |
| 181 | new PlainVanillaPayoff(Option::Put, 95)); |
| 182 | |
| 183 | Handle<YieldTermStructure> riskFreeTS(flatRate(forward: 0.1, dc: dayCounter)); |
| 184 | Handle<YieldTermStructure> dividendTS(flatRate(forward: 0.04, dc: dayCounter)); |
| 185 | Handle<Quote> s0(ext::shared_ptr<Quote>(new SimpleQuote(100))); |
| 186 | |
| 187 | Real v0 = 0.0433; |
| 188 | ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(std::sqrt(x: v0))); |
| 189 | ext::shared_ptr<BlackVolTermStructure> volTS = |
| 190 | flatVol(today: settlementDate, volatility: vol, dc: dayCounter); |
| 191 | |
| 192 | const Real kappa = 0.5; |
| 193 | const Real theta = v0; |
| 194 | const Real sigma = 1.0e-4; |
| 195 | const Real rho = 0.0; |
| 196 | |
| 197 | ext::shared_ptr<SimpleQuote> jumpIntensity(new SimpleQuote(2)); |
| 198 | ext::shared_ptr<SimpleQuote> meanLogJump(new SimpleQuote(-0.2)); |
| 199 | ext::shared_ptr<SimpleQuote> jumpVol(new SimpleQuote(0.2)); |
| 200 | |
| 201 | ext::shared_ptr<BatesProcess> batesProcess(new BatesProcess( |
| 202 | riskFreeTS, dividendTS, s0, v0, kappa, theta, sigma, rho, |
| 203 | jumpIntensity->value(), meanLogJump->value(), jumpVol->value())); |
| 204 | |
| 205 | ext::shared_ptr<Merton76Process> mertonProcess( |
| 206 | new Merton76Process(s0, dividendTS, riskFreeTS, |
| 207 | Handle<BlackVolTermStructure>(volTS), |
| 208 | Handle<Quote>(jumpIntensity), |
| 209 | Handle<Quote>(meanLogJump), |
| 210 | Handle<Quote>(jumpVol))); |
| 211 | |
| 212 | ext::shared_ptr<PricingEngine> batesEngine(new BatesEngine( |
| 213 | ext::make_shared<BatesModel>(args&: batesProcess), 160)); |
| 214 | |
| 215 | const Real mcTol = 0.1; |
| 216 | ext::shared_ptr<PricingEngine> mcBatesEngine = |
| 217 | MakeMCEuropeanHestonEngine<PseudoRandom>(batesProcess) |
| 218 | .withStepsPerYear(steps: 2) |
| 219 | .withAntitheticVariate() |
| 220 | .withAbsoluteTolerance(tolerance: mcTol) |
| 221 | .withSeed(seed: 1234); |
| 222 | |
| 223 | ext::shared_ptr<PricingEngine> mertonEngine( |
| 224 | new JumpDiffusionEngine(mertonProcess, 1e-10, 1000)); |
| 225 | |
| 226 | for (Integer i=1; i<=5; i+=2) { |
| 227 | Date exerciseDate = settlementDate + i*Years; |
| 228 | ext::shared_ptr<Exercise> exercise( |
| 229 | new EuropeanExercise(exerciseDate)); |
| 230 | |
| 231 | VanillaOption batesOption(payoff, exercise); |
| 232 | |
| 233 | batesOption.setPricingEngine(batesEngine); |
| 234 | Real calculated = batesOption.NPV(); |
| 235 | |
| 236 | batesOption.setPricingEngine(mcBatesEngine); |
| 237 | Real mcCalculated = batesOption.NPV(); |
| 238 | |
| 239 | EuropeanOption mertonOption(payoff, exercise); |
| 240 | mertonOption.setPricingEngine(mertonEngine); |
| 241 | Real expected = mertonOption.NPV(); |
| 242 | |
| 243 | Real tolerance = 2e-8; |
| 244 | Real relError = std::fabs(x: calculated - expected)/expected; |
| 245 | if (relError > tolerance) { |
| 246 | BOOST_FAIL("failed to reproduce Merton76 price with semi " |
| 247 | "analytic BatesEngine" |
| 248 | << std::fixed << std::setprecision(8) |
| 249 | << "\n calculated: " << calculated |
| 250 | << "\n expected: " << expected |
| 251 | << "\n rel. error: " << relError |
| 252 | << "\n tolerance: " << tolerance); |
| 253 | } |
| 254 | |
| 255 | Real mcError = std::fabs(x: expected - mcCalculated); |
| 256 | if (mcError > 3*mcTol) { |
| 257 | BOOST_FAIL("failed to reproduce Merton76 price with Monte-Carlo " |
| 258 | "BatesEngine" |
| 259 | << std::fixed << std::setprecision(8) |
| 260 | << "\n calculated: " << mcCalculated |
| 261 | << "\n expected: " << expected |
| 262 | << "\n error: " << mcError |
| 263 | << "\n tolerance: " << mcTol); |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | namespace bates_model_test { |
| 269 | struct HestonModelData { |
| 270 | const char* const name; |
| 271 | Real v0; |
| 272 | Real kappa; |
| 273 | Real theta; |
| 274 | Real sigma; |
| 275 | Real rho; |
| 276 | Real r; |
| 277 | Real q; |
| 278 | }; |
| 279 | |
| 280 | HestonModelData hestonModels[] = { |
| 281 | // ADI finite difference schemes for option pricing in the |
| 282 | // Heston model with correlation, K.J. in t'Hout and S. Foulon, |
| 283 | {.name: "'t Hout case 1" , .v0: 0.04, .kappa: 1.5, .theta: 0.04, .sigma: 0.3, .rho: -0.9, .r: 0.025, .q: 0.0}, |
| 284 | // Efficient numerical methods for pricing American options under |
| 285 | // stochastic volatility, Samuli Ikonen and Jari Toivanen, |
| 286 | {.name: "Ikonen-Toivanen" , .v0: 0.0625, .kappa: 5, .theta: 0.16, .sigma: 0.9, .rho: 0.1, .r: 0.1, .q: 0.0}, |
| 287 | // Not-so-complex logarithms in the Heston model, |
| 288 | // Christian Kahl and Peter Jäckel |
| 289 | {.name: "Kahl-Jaeckel" , .v0: 0.16, .kappa: 1.0, .theta: 0.16, .sigma: 2.0, .rho: -0.8, .r: 0.0, .q: 0.0}, |
| 290 | // self defined test cases |
| 291 | {.name: "Equity case" , .v0: 0.07, .kappa: 2.0, .theta: 0.04, .sigma: 0.55, .rho: -0.8, .r: 0.03, .q: 0.035 }, |
| 292 | }; |
| 293 | } |
| 294 | |
| 295 | void BatesModelTest::testAnalyticVsMCPricing() { |
| 296 | BOOST_TEST_MESSAGE("Testing analytic Bates engine against Monte-Carlo " |
| 297 | "engine..." ); |
| 298 | |
| 299 | using namespace bates_model_test; |
| 300 | |
| 301 | Date settlementDate(30, March, 2007); |
| 302 | Settings::instance().evaluationDate() = settlementDate; |
| 303 | |
| 304 | DayCounter dayCounter = ActualActual(ActualActual::ISDA); |
| 305 | Date exerciseDate(30, March, 2012); |
| 306 | |
| 307 | ext::shared_ptr<StrikedTypePayoff> payoff( |
| 308 | new PlainVanillaPayoff(Option::Put, 100)); |
| 309 | ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate)); |
| 310 | |
| 311 | |
| 312 | for (auto& hestonModel : hestonModels) { |
| 313 | Handle<YieldTermStructure> riskFreeTS(flatRate(forward: hestonModel.r, dc: dayCounter)); |
| 314 | Handle<YieldTermStructure> dividendTS(flatRate(forward: hestonModel.q, dc: dayCounter)); |
| 315 | Handle<Quote> s0(ext::shared_ptr<Quote>(new SimpleQuote(100))); |
| 316 | |
| 317 | ext::shared_ptr<BatesProcess> batesProcess(new BatesProcess( |
| 318 | riskFreeTS, dividendTS, s0, hestonModel.v0, hestonModel.kappa, hestonModel.theta, |
| 319 | hestonModel.sigma, hestonModel.rho, 2.0, -0.2, 0.1)); |
| 320 | |
| 321 | const Real mcTolerance = 0.5; |
| 322 | ext::shared_ptr<PricingEngine> mcEngine = |
| 323 | MakeMCEuropeanHestonEngine<PseudoRandom>(batesProcess) |
| 324 | .withStepsPerYear(steps: 20) |
| 325 | .withAntitheticVariate() |
| 326 | .withAbsoluteTolerance(tolerance: mcTolerance) |
| 327 | .withSeed(seed: 1234); |
| 328 | |
| 329 | ext::shared_ptr<BatesModel> batesModel(new BatesModel(batesProcess)); |
| 330 | |
| 331 | ext::shared_ptr<PricingEngine> fdEngine( |
| 332 | new FdBatesVanillaEngine(batesModel, 50, 100, 30)); |
| 333 | |
| 334 | ext::shared_ptr<PricingEngine> analyticEngine( |
| 335 | new BatesEngine(batesModel, 160)); |
| 336 | |
| 337 | VanillaOption option(payoff, exercise); |
| 338 | |
| 339 | option.setPricingEngine(mcEngine); |
| 340 | const Real calculated = option.NPV(); |
| 341 | |
| 342 | option.setPricingEngine(analyticEngine); |
| 343 | const Real expected = option.NPV(); |
| 344 | |
| 345 | option.setPricingEngine(fdEngine); |
| 346 | const Real fdCalculated = option.NPV(); |
| 347 | |
| 348 | const Real mcError = std::fabs(x: calculated - expected); |
| 349 | if (mcError > 3*mcTolerance) { |
| 350 | BOOST_FAIL("failed to reproduce Monte-Carlo price for BatesEngine" |
| 351 | << "\n parameter: " << hestonModel.name << std::fixed |
| 352 | << std::setprecision(8) << "\n calculated: " << calculated |
| 353 | << "\n expected: " << expected << "\n error: " << mcError |
| 354 | << "\n tolerance: " << mcTolerance); |
| 355 | } |
| 356 | const Real fdTolerance = 0.2; |
| 357 | const Real fdError = std::fabs(x: fdCalculated - expected); |
| 358 | if (fdError > fdTolerance) { |
| 359 | BOOST_FAIL("failed to reproduce PIDE price for BatesEngine" |
| 360 | << "\n parameter: " << hestonModel.name << std::fixed |
| 361 | << std::setprecision(8) << "\n calculated: " << fdCalculated |
| 362 | << "\n expected: " << expected << "\n error: " << fdError |
| 363 | << "\n tolerance: " << fdTolerance); |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | void BatesModelTest::testDAXCalibration() { |
| 369 | /* this example is taken from A. Sepp |
| 370 | Pricing European-Style Options under Jump Diffusion Processes |
| 371 | with Stochstic Volatility: Applications of Fourier Transform |
| 372 | http://math.ut.ee/~spartak/papers/stochjumpvols.pdf |
| 373 | */ |
| 374 | |
| 375 | BOOST_TEST_MESSAGE( |
| 376 | "Testing Bates model calibration using DAX volatility data..." ); |
| 377 | |
| 378 | using namespace bates_model_test; |
| 379 | |
| 380 | Date settlementDate(5, July, 2002); |
| 381 | Settings::instance().evaluationDate() = settlementDate; |
| 382 | |
| 383 | DayCounter dayCounter = Actual365Fixed(); |
| 384 | Calendar calendar = TARGET(); |
| 385 | |
| 386 | Integer t[] = { 13, 41, 75, 165, 256, 345, 524, 703 }; |
| 387 | Rate r[] = { 0.0357,0.0349,0.0341,0.0355,0.0359,0.0368,0.0386,0.0401 }; |
| 388 | |
| 389 | std::vector<Date> dates; |
| 390 | std::vector<Rate> rates; |
| 391 | dates.push_back(x: settlementDate); |
| 392 | rates.push_back(x: 0.0357); |
| 393 | for (Size i = 0; i < 8; ++i) { |
| 394 | dates.push_back(x: settlementDate + t[i]); |
| 395 | rates.push_back(x: r[i]); |
| 396 | } |
| 397 | Handle<YieldTermStructure> riskFreeTS( |
| 398 | ext::shared_ptr<YieldTermStructure>( |
| 399 | new ZeroCurve(dates, rates, dayCounter))); |
| 400 | |
| 401 | Handle<YieldTermStructure> dividendTS( |
| 402 | flatRate(today: settlementDate, forward: 0.0, dc: dayCounter)); |
| 403 | |
| 404 | Volatility v[] = |
| 405 | { 0.6625,0.4875,0.4204,0.3667,0.3431,0.3267,0.3121,0.3121, |
| 406 | 0.6007,0.4543,0.3967,0.3511,0.3279,0.3154,0.2984,0.2921, |
| 407 | 0.5084,0.4221,0.3718,0.3327,0.3155,0.3027,0.2919,0.2889, |
| 408 | 0.4541,0.3869,0.3492,0.3149,0.2963,0.2926,0.2819,0.2800, |
| 409 | 0.4060,0.3607,0.3330,0.2999,0.2887,0.2811,0.2751,0.2775, |
| 410 | 0.3726,0.3396,0.3108,0.2781,0.2788,0.2722,0.2661,0.2686, |
| 411 | 0.3550,0.3277,0.3012,0.2781,0.2781,0.2661,0.2661,0.2681, |
| 412 | 0.3428,0.3209,0.2958,0.2740,0.2688,0.2627,0.2580,0.2620, |
| 413 | 0.3302,0.3062,0.2799,0.2631,0.2573,0.2533,0.2504,0.2544, |
| 414 | 0.3343,0.2959,0.2705,0.2540,0.2504,0.2464,0.2448,0.2462, |
| 415 | 0.3460,0.2845,0.2624,0.2463,0.2425,0.2385,0.2373,0.2422, |
| 416 | 0.3857,0.2860,0.2578,0.2399,0.2357,0.2327,0.2312,0.2351, |
| 417 | 0.3976,0.2860,0.2607,0.2356,0.2297,0.2268,0.2241,0.2320 }; |
| 418 | |
| 419 | Handle<Quote> s0(ext::shared_ptr<Quote>(new SimpleQuote(4468.17))); |
| 420 | Real strike[] = { 3400,3600,3800,4000,4200,4400, |
| 421 | 4500,4600,4800,5000,5200,5400,5600 }; |
| 422 | |
| 423 | |
| 424 | Real v0 = 0.0433; |
| 425 | ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(std::sqrt(x: v0))); |
| 426 | |
| 427 | const Real kappa = 1.0; |
| 428 | const Real theta = v0; |
| 429 | const Real sigma = 1.0; |
| 430 | const Real rho = 0.0; |
| 431 | const Real lambda = 1.1098; |
| 432 | const Real nu = -0.1285; |
| 433 | const Real delta = 0.1702; |
| 434 | |
| 435 | ext::shared_ptr<BatesProcess> process( |
| 436 | new BatesProcess(riskFreeTS, dividendTS, s0, v0, |
| 437 | kappa, theta, sigma, rho, lambda, nu, delta)); |
| 438 | |
| 439 | ext::shared_ptr<BatesModel> batesModel(new BatesModel(process)); |
| 440 | |
| 441 | ext::shared_ptr<PricingEngine> batesEngine( |
| 442 | new BatesEngine(batesModel, 64)); |
| 443 | |
| 444 | std::vector<ext::shared_ptr<BlackCalibrationHelper> > options; |
| 445 | |
| 446 | for (Size s = 0; s < 13; ++s) { |
| 447 | for (Size m = 0; m < 8; ++m) { |
| 448 | Handle<Quote> vol(ext::shared_ptr<Quote>( |
| 449 | new SimpleQuote(v[s*8+m]))); |
| 450 | |
| 451 | Period maturity((int)((t[m]+3)/7.), Weeks); // round to weeks |
| 452 | |
| 453 | // this is the calibration helper for the bates models |
| 454 | options.push_back(x: ext::shared_ptr<BlackCalibrationHelper>( |
| 455 | new HestonModelHelper(maturity, calendar, |
| 456 | s0->value(), strike[s], vol, |
| 457 | riskFreeTS, dividendTS, |
| 458 | BlackCalibrationHelper::ImpliedVolError))); |
| 459 | options.back()->setPricingEngine(batesEngine); |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | // check calibration engine |
| 464 | LevenbergMarquardt om; |
| 465 | batesModel->calibrate(std::vector<ext::shared_ptr<CalibrationHelper> >(options.begin(), options.end()), |
| 466 | method&: om, endCriteria: EndCriteria(400, 40, 1.0e-8, 1.0e-8, 1.0e-8)); |
| 467 | |
| 468 | Real expected = 36.6; |
| 469 | Real calculated = getCalibrationError(options); |
| 470 | |
| 471 | if (std::fabs(x: calculated - expected) > 2.5) |
| 472 | BOOST_ERROR("failed to calibrate the bates model" |
| 473 | << "\n calculated: " << calculated |
| 474 | << "\n expected: " << expected); |
| 475 | |
| 476 | //check pricing of derived engines |
| 477 | std::vector<ext::shared_ptr<PricingEngine> > pricingEngines; |
| 478 | |
| 479 | process = ext::make_shared<BatesProcess>( |
| 480 | args&: riskFreeTS, args&: dividendTS, args&: s0, args&: v0, |
| 481 | args: kappa, args: theta, args: sigma, args: rho, args: 1.0, args: -0.1, args: 0.1); |
| 482 | |
| 483 | pricingEngines.push_back(x: ext::shared_ptr<PricingEngine>( |
| 484 | new BatesDetJumpEngine( |
| 485 | ext::make_shared<BatesDetJumpModel>( |
| 486 | args&: process), 64)) ); |
| 487 | |
| 488 | ext::shared_ptr<HestonProcess> hestonProcess(new HestonProcess( |
| 489 | riskFreeTS, dividendTS, s0, v0, kappa, theta, sigma, rho)); |
| 490 | |
| 491 | pricingEngines.push_back(x: ext::shared_ptr<PricingEngine>( |
| 492 | new BatesDoubleExpEngine( |
| 493 | ext::make_shared<BatesDoubleExpModel>( |
| 494 | args&: hestonProcess, args: 1.0), 64)) ); |
| 495 | |
| 496 | pricingEngines.push_back(x: ext::shared_ptr<PricingEngine>( |
| 497 | new BatesDoubleExpDetJumpEngine( |
| 498 | ext::make_shared<BatesDoubleExpDetJumpModel>( |
| 499 | args&: hestonProcess, args: 1.0), 64)) ); |
| 500 | |
| 501 | Real expectedValues[] = { 5896.37, |
| 502 | 5499.29, |
| 503 | 6497.89}; |
| 504 | |
| 505 | Real tolerance=0.1; |
| 506 | for (Size i = 0; i < pricingEngines.size(); ++i) { |
| 507 | for (auto& option : options) { |
| 508 | option->setPricingEngine(pricingEngines[i]); |
| 509 | } |
| 510 | |
| 511 | Real calculated = std::fabs(x: getCalibrationError(options)); |
| 512 | if (std::fabs(x: calculated - expectedValues[i]) > tolerance) |
| 513 | BOOST_ERROR("failed to calculated prices for derived Bates models" |
| 514 | << "\n calculated: " << calculated |
| 515 | << "\n expected: " << expectedValues[i]); |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | test_suite* BatesModelTest::suite() { |
| 520 | auto* suite = BOOST_TEST_SUITE("Bates model tests" ); |
| 521 | suite->add(QUANTLIB_TEST_CASE(&BatesModelTest::testAnalyticVsBlack)); |
| 522 | suite->add(QUANTLIB_TEST_CASE(&BatesModelTest::testAnalyticAndMcVsJumpDiffusion)); |
| 523 | suite->add(QUANTLIB_TEST_CASE(&BatesModelTest::testAnalyticVsMCPricing)); |
| 524 | suite->add(QUANTLIB_TEST_CASE(&BatesModelTest::testDAXCalibration)); |
| 525 | return suite; |
| 526 | } |
| 527 | |