1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4Copyright (C) 2010 Adrian O' Neill
5Copyright (C) 2018 Roy Zywina
6
7This file is part of QuantLib, a free-software/open-source library
8for financial quantitative analysts and developers - http://quantlib.org/
9
10QuantLib is free software: you can redistribute it and/or modify it
11under the terms of the QuantLib license. You should have received a
12copy 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
16This program is distributed in the hope that it will be useful, but WITHOUT
17ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include "variancegamma.hpp"
22#include "utilities.hpp"
23#include <ql/time/daycounters/actual360.hpp>
24#include <ql/time/daycounters/thirty360.hpp>
25#include <ql/instruments/europeanoption.hpp>
26#include <ql/experimental/variancegamma/analyticvariancegammaengine.hpp>
27#include <ql/experimental/variancegamma/fftvariancegammaengine.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
33using namespace QuantLib;
34using namespace boost::unit_test_framework;
35
36#undef REPORT_FAILURE
37#define REPORT_FAILURE(greekName, payoff, exercise, s, q, r, today, sigma, \
38 nu, theta, expected, calculated, \
39 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 << " sigma: " << sigma << "\n" \
50 << " nu: " << nu << "\n" \
51 << " theta: " << theta << "\n\n" \
52 << " expected " << greekName << ": " << expected << "\n" \
53 << " calculated " << greekName << ": " << calculated << "\n"\
54 << " error: " << error << "\n" \
55 << " tolerance: " << tolerance);
56
57namespace {
58
59 struct VarianceGammaProcessData {
60 Real s; // spot
61 Rate q; // dividend
62 Rate r; // risk-free rate
63 Real sigma;
64 Real nu;
65 Real theta;
66
67 };
68
69 struct VarianceGammaOptionData {
70 Option::Type type;
71 Real strike;
72 Time t; // time to maturity
73 };
74}
75
76
77void VarianceGammaTest::testVarianceGamma() {
78
79 BOOST_TEST_MESSAGE("Testing variance-gamma model for European options...");
80
81 VarianceGammaProcessData processes[] = {
82 // spot, q, r,sigma, nu, theta
83 { .s: 6000, .q: 0.00, .r: 0.05, .sigma: 0.20, .nu: 0.05, .theta: -0.50 },
84 { .s: 6000, .q: 0.02, .r: 0.05, .sigma: 0.15, .nu: 0.01, .theta: -0.50 }
85 };
86
87 VarianceGammaOptionData options[] = {
88 // type,strike, t
89 { .type: Option::Call, .strike: 5550, .t: 1.0},
90 { .type: Option::Call, .strike: 5600, .t: 1.0},
91 { .type: Option::Call, .strike: 5650, .t: 1.0},
92 { .type: Option::Call, .strike: 5700, .t: 1.0},
93 { .type: Option::Call, .strike: 5750, .t: 1.0},
94 { .type: Option::Call, .strike: 5800, .t: 1.0},
95 { .type: Option::Call, .strike: 5850, .t: 1.0},
96 { .type: Option::Call, .strike: 5900, .t: 1.0},
97 { .type: Option::Call, .strike: 5950, .t: 1.0},
98 { .type: Option::Call, .strike: 6000, .t: 1.0},
99 { .type: Option::Call, .strike: 6050, .t: 1.0},
100 { .type: Option::Call, .strike: 6100, .t: 1.0},
101 { .type: Option::Call, .strike: 6150, .t: 1.0},
102 { .type: Option::Call, .strike: 6200, .t: 1.0},
103 { .type: Option::Call, .strike: 6250, .t: 1.0},
104 { .type: Option::Call, .strike: 6300, .t: 1.0},
105 { .type: Option::Call, .strike: 6350, .t: 1.0},
106 { .type: Option::Call, .strike: 6400, .t: 1.0},
107 { .type: Option::Call, .strike: 6450, .t: 1.0},
108 { .type: Option::Call, .strike: 6500, .t: 1.0},
109 { .type: Option::Call, .strike: 6550, .t: 1.0},
110 { .type: Option::Put, .strike: 5550, .t: 1.0}
111 };
112
113 Real results[LENGTH(processes)][LENGTH(options)] = {
114 {
115 955.1637, 922.7529, 890.9872, 859.8739, 829.4197, 799.6303, 770.5104, 742.0640,
116 714.2943, 687.2032, 660.7921, 635.0613, 610.0103, 585.6379, 561.9416, 538.9186,
117 516.5649, 494.8760, 473.8464, 453.4700, 433.7400, 234.4870
118 },
119 {
120 732.8705, 698.5542, 665.1404, 632.6498, 601.1002, 570.5068, 540.8824, 512.2367,
121 484.5766, 457.9064, 432.2273, 407.5381, 383.8346, 361.1102, 339.3559, 318.5599,
122 298.7087, 279.7864, 261.7751, 244.6552, 228.4057, 130.9974
123 }
124 };
125
126 Real tol = 0.01;
127
128 DayCounter dc = Actual360();
129 Date today = Date::todaysDate();
130
131 for (Size i=0; i<LENGTH(processes); i++) {
132 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(processes[i].s));
133 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(processes[i].q));
134 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
135 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(processes[i].r));
136 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
137
138 ext::shared_ptr<VarianceGammaProcess> stochProcess(
139 new VarianceGammaProcess(Handle<Quote>(spot),
140 Handle<YieldTermStructure>(qTS),
141 Handle<YieldTermStructure>(rTS),
142 processes[i].sigma,
143 processes[i].nu,
144 processes[i].theta));
145
146 // Analytic engine
147 ext::shared_ptr<PricingEngine> analyticEngine(
148 new VarianceGammaEngine(stochProcess));
149
150 // FFT engine
151 ext::shared_ptr<FFTVarianceGammaEngine> fftEngine(
152 new FFTVarianceGammaEngine(stochProcess));
153
154 // which requires a list of options
155 std::vector<ext::shared_ptr<Instrument> > optionList;
156
157 std::vector<ext::shared_ptr<StrikedTypePayoff> > payoffs;
158 for (Size j=0; j<LENGTH(options); j++)
159 {
160 Date exDate = today + timeToDays(t: options[j].t);
161 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
162
163 ext::shared_ptr<StrikedTypePayoff> payoff(new
164 PlainVanillaPayoff(options[j].type, options[j].strike));
165 payoffs.push_back(x: payoff);
166
167 // Test analytic engine
168 ext::shared_ptr<EuropeanOption> option(new EuropeanOption(payoff, exercise));
169 option->setPricingEngine(analyticEngine);
170
171 Real calculated = option->NPV();
172 Real expected = results[i][j];
173 Real error = std::fabs(x: calculated-expected);
174
175 if (error>tol) {
176 REPORT_FAILURE("analytic value", payoff, exercise,
177 processes[i].s, processes[i].q, processes[i].r,
178 today, processes[i].sigma, processes[i].nu,
179 processes[i].theta, expected, calculated,
180 error, tol);
181 }
182 optionList.push_back(x: option);
183 }
184
185 // Test FFT engine
186 // FFT engine is extremely efficient when sent a list of options to calculate first
187 fftEngine->precalculate(optionList);
188 for (Size j=0; j<LENGTH(options); j++)
189 {
190 ext::shared_ptr<VanillaOption> option = ext::static_pointer_cast<VanillaOption>(r: optionList[j]);
191 option->setPricingEngine(fftEngine);
192
193 Real calculated = option->NPV();
194 Real expected = results[i][j];
195 Real error = std::fabs(x: calculated-expected);
196 if (error>tol) {
197 ext::shared_ptr<StrikedTypePayoff> payoff =
198 ext::dynamic_pointer_cast<StrikedTypePayoff>(r: option->payoff());
199 REPORT_FAILURE("fft value", payoff, option->exercise(),
200 processes[i].s, processes[i].q, processes[i].r,
201 today, processes[i].sigma, processes[i].nu,
202 processes[i].theta, expected, calculated,
203 error, tol);
204 }
205 }
206 }
207}
208
209void VarianceGammaTest::testSingularityAtZero() {
210
211 BOOST_TEST_MESSAGE(
212 "Testing variance-gamma model integration around zero...");
213
214 Real stock = 100;
215 Real strike = 98;
216 Volatility sigma = 0.12;
217 Real mu = -0.14;
218 Real kappa = 0.2;
219
220 Date valuation(1,Jan,2017);
221 Date maturity(10,Jan,2017);
222 DayCounter discountCounter = Thirty360(Thirty360::BondBasis);
223
224 Settings::instance().evaluationDate() = valuation;
225
226 ext::shared_ptr<Exercise> exercise =
227 ext::make_shared<EuropeanExercise>(args&: maturity);
228 ext::shared_ptr<StrikedTypePayoff> payoff =
229 ext::make_shared<PlainVanillaPayoff>(args: Option::Call, args&: strike);
230 VanillaOption option(payoff, exercise);
231
232 Handle<YieldTermStructure> dividend(
233 ext::make_shared<FlatForward>(args&: valuation,args: 0.0,args&: discountCounter));
234 Handle<YieldTermStructure> disc(
235 ext::make_shared<FlatForward>(args&: valuation,args: 0.05,args&: discountCounter));
236 Handle<Quote> S0(ext::make_shared<SimpleQuote>(args&: stock));
237 ext::shared_ptr<QuantLib::VarianceGammaProcess> process =
238 ext::make_shared<VarianceGammaProcess>(args&: S0, args&: dividend, args&: disc,
239 args&: sigma, args&: kappa, args&: mu);
240
241 option.setPricingEngine(ext::make_shared<VarianceGammaEngine>(args&: process));
242 // without the fix, the call below goes into an infinite loop,
243 // which is hard to test for. We're just happy to see the test
244 // case finish, hence the lack of an assertion.
245 option.NPV();
246}
247
248
249test_suite* VarianceGammaTest::suite() {
250 auto* suite = BOOST_TEST_SUITE("Variance Gamma tests");
251
252 suite->add(QUANTLIB_TEST_CASE(&VarianceGammaTest::testVarianceGamma));
253 suite->add(QUANTLIB_TEST_CASE(&VarianceGammaTest::testSingularityAtZero));
254 return suite;
255}
256

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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.