1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 /*
4 Copyright (C) 2004, 2007, 2008 StatPro Italia srl
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 "cliquetoption.hpp"
21 #include "utilities.hpp"
22 #include <ql/time/daycounters/actual360.hpp>
23 #include <ql/instruments/cliquetoption.hpp>
24 #include <ql/pricingengines/cliquet/analyticcliquetengine.hpp>
25 #include <ql/pricingengines/cliquet/analyticperformanceengine.hpp>
26 #include <ql/pricingengines/cliquet/mcperformanceengine.hpp>
27 #include <ql/processes/blackscholesprocess.hpp>
28 #include <ql/termstructures/yield/flatforward.hpp>
29 #include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
30 #include <ql/time/daycounters/actual360.hpp>
31 #include <ql/utilities/dataformatters.hpp>
32 #include <ql/time/period.hpp>
33 #include <map>
34
35 using namespace QuantLib;
36 using namespace boost::unit_test_framework;
37
38 #undef REPORT_FAILURE
39 #define REPORT_FAILURE(greekName, payoff, exercise, s, q, r, today, v, \
40 expected, calculated, error, tolerance) \
41 BOOST_ERROR(payoff->optionType() << " option:\n" \
42 << " spot value: " << s << "\n" \
43 << " moneyness: " << payoff->strike() << "\n" \
44 << " dividend yield: " << io::rate(q) << "\n" \
45 << " risk-free rate: " << io::rate(r) << "\n" \
46 << " reference date: " << today << "\n" \
47 << " maturity: " << exercise->lastDate() << "\n" \
48 << " volatility: " << io::volatility(v) << "\n\n" \
49 << " expected " << greekName << ": " << expected << "\n" \
50 << " calculated " << greekName << ": " << calculated << "\n"\
51 << " error: " << error << "\n" \
52 << " tolerance: " << tolerance);
53
54
testValues()55 void CliquetOptionTest::testValues() {
56
57 BOOST_TEST_MESSAGE("Testing Cliquet option values...");
58
59 Date today = Date::todaysDate();
60 DayCounter dc = Actual360();
61
62 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(60.0));
63 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
64 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, qRate, dc);
65 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.08));
66 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, rRate, dc);
67 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.30));
68 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, vol, dc);
69
70 ext::shared_ptr<BlackScholesMertonProcess> process(
71 new BlackScholesMertonProcess(Handle<Quote>(spot),
72 Handle<YieldTermStructure>(qTS),
73 Handle<YieldTermStructure>(rTS),
74 Handle<BlackVolTermStructure>(volTS)));
75 ext::shared_ptr<PricingEngine> engine(new AnalyticCliquetEngine(process));
76
77 std::vector<Date> reset;
78 reset.push_back(today + 90);
79 Date maturity = today + 360;
80 Option::Type type = Option::Call;
81 Real moneyness = 1.1;
82
83 ext::shared_ptr<PercentageStrikePayoff> payoff(
84 new PercentageStrikePayoff(type, moneyness));
85 ext::shared_ptr<EuropeanExercise> exercise(
86 new EuropeanExercise(maturity));
87
88 CliquetOption option(payoff, exercise, reset);
89 option.setPricingEngine(engine);
90
91 Real calculated = option.NPV();
92 Real expected = 4.4064; // Haug, p.37
93 Real error = std::fabs(calculated-expected);
94 Real tolerance = 1e-4;
95 if (error > tolerance) {
96 REPORT_FAILURE("value", payoff, exercise, spot->value(),
97 qRate->value(), rRate->value(), today,
98 vol->value(), expected, calculated,
99 error, tolerance);
100 }
101 }
102
103
104 namespace {
105
106 template <class T>
testOptionGreeks()107 void testOptionGreeks() {
108
109 SavedSettings backup;
110
111 std::map<std::string,Real> calculated, expected, tolerance;
112 tolerance["delta"] = 1.0e-5;
113 tolerance["gamma"] = 1.0e-5;
114 tolerance["theta"] = 1.0e-5;
115 tolerance["rho"] = 1.0e-5;
116 tolerance["divRho"] = 1.0e-5;
117 tolerance["vega"] = 1.0e-5;
118
119 Option::Type types[] = { Option::Call, Option::Put };
120 Real moneyness[] = { 0.9, 1.0, 1.1 };
121 Real underlyings[] = { 100.0 };
122 Rate qRates[] = { 0.04, 0.05, 0.06 };
123 Rate rRates[] = { 0.01, 0.05, 0.15 };
124 Integer lengths[] = { 1, 2 };
125 Frequency frequencies[] = { Semiannual, Quarterly };
126 Volatility vols[] = { 0.11, 0.50, 1.20 };
127
128 DayCounter dc = Actual360();
129 Date today = Date::todaysDate();
130 Settings::instance().evaluationDate() = today;
131
132 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
133 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
134 Handle<YieldTermStructure> qTS(flatRate(qRate, dc));
135 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
136 Handle<YieldTermStructure> rTS(flatRate(rRate, dc));
137 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
138 Handle<BlackVolTermStructure> volTS(flatVol(vol, dc));
139
140 ext::shared_ptr<BlackScholesMertonProcess> process(
141 new BlackScholesMertonProcess(Handle<Quote>(spot),
142 qTS, rTS, volTS));
143
144 for (Size i=0; i<LENGTH(types); i++) {
145 for (Size j=0; j<LENGTH(moneyness); j++) {
146 for (Size k=0; k<LENGTH(lengths); k++) {
147 for (Size kk=0; kk<LENGTH(frequencies); kk++) {
148
149 ext::shared_ptr<EuropeanExercise> maturity(
150 new EuropeanExercise(today + lengths[k]*Years));
151
152 ext::shared_ptr<PercentageStrikePayoff> payoff(
153 new PercentageStrikePayoff(types[i], moneyness[j]));
154
155 std::vector<Date> reset;
156 for (Date d = today + Period(frequencies[kk]);
157 d < maturity->lastDate();
158 d += Period(frequencies[kk]))
159 reset.push_back(d);
160
161 ext::shared_ptr<PricingEngine> engine(new T(process));
162
163 CliquetOption option(payoff, maturity, reset);
164 option.setPricingEngine(engine);
165
166 for (Size l=0; l<LENGTH(underlyings); l++) {
167 for (Size m=0; m<LENGTH(qRates); m++) {
168 for (Size n=0; n<LENGTH(rRates); n++) {
169 for (Size p=0; p<LENGTH(vols); p++) {
170
171 Real u = underlyings[l];
172 Rate q = qRates[m],
173 r = rRates[n];
174 Volatility v = vols[p];
175 spot->setValue(u);
176 qRate->setValue(q);
177 rRate->setValue(r);
178 vol->setValue(v);
179
180 Real value = option.NPV();
181 calculated["delta"] = option.delta();
182 calculated["gamma"] = option.gamma();
183 calculated["theta"] = option.theta();
184 calculated["rho"] = option.rho();
185 calculated["divRho"] = option.dividendRho();
186 calculated["vega"] = option.vega();
187
188 if (value > spot->value()*1.0e-5) {
189 // perturb spot and get delta and gamma
190 Real du = u*1.0e-4;
191 spot->setValue(u+du);
192 Real value_p = option.NPV(),
193 delta_p = option.delta();
194 spot->setValue(u-du);
195 Real value_m = option.NPV(),
196 delta_m = option.delta();
197 spot->setValue(u);
198 expected["delta"] = (value_p - value_m)/(2*du);
199 expected["gamma"] = (delta_p - delta_m)/(2*du);
200
201 // perturb rates and get rho and dividend rho
202 Spread dr = r*1.0e-4;
203 rRate->setValue(r+dr);
204 value_p = option.NPV();
205 rRate->setValue(r-dr);
206 value_m = option.NPV();
207 rRate->setValue(r);
208 expected["rho"] = (value_p - value_m)/(2*dr);
209
210 Spread dq = q*1.0e-4;
211 qRate->setValue(q+dq);
212 value_p = option.NPV();
213 qRate->setValue(q-dq);
214 value_m = option.NPV();
215 qRate->setValue(q);
216 expected["divRho"] = (value_p - value_m)/(2*dq);
217
218 // perturb volatility and get vega
219 Volatility dv = v*1.0e-4;
220 vol->setValue(v+dv);
221 value_p = option.NPV();
222 vol->setValue(v-dv);
223 value_m = option.NPV();
224 vol->setValue(v);
225 expected["vega"] = (value_p - value_m)/(2*dv);
226
227 // perturb date and get theta
228 Time dT = dc.yearFraction(today-1, today+1);
229 Settings::instance().evaluationDate() = today-1;
230 value_m = option.NPV();
231 Settings::instance().evaluationDate() = today+1;
232 value_p = option.NPV();
233 Settings::instance().evaluationDate() = today;
234 expected["theta"] = (value_p - value_m)/dT;
235
236 // compare
237 std::map<std::string,Real>::iterator it;
238 for (it = calculated.begin();
239 it != calculated.end(); ++it) {
240 std::string greek = it->first;
241 Real expct = expected [greek],
242 calcl = calculated[greek],
243 tol = tolerance [greek];
244 Real error = relativeError(expct,calcl,u);
245 if (error>tol) {
246 REPORT_FAILURE(greek, payoff, maturity,
247 u, q, r, today, v,
248 expct, calcl, error, tol);
249 }
250 }
251 }
252 }
253 }
254 }
255 }
256 }
257 }
258 }
259 }
260 }
261
262 }
263
264
testGreeks()265 void CliquetOptionTest::testGreeks() {
266 BOOST_TEST_MESSAGE("Testing Cliquet option greeks...");
267 testOptionGreeks<AnalyticCliquetEngine>();
268 }
269
270
testPerformanceGreeks()271 void CliquetOptionTest::testPerformanceGreeks() {
272 BOOST_TEST_MESSAGE("Testing performance option greeks...");
273 testOptionGreeks<AnalyticPerformanceEngine>();
274 }
275
276
testMcPerformance()277 void CliquetOptionTest::testMcPerformance() {
278 BOOST_TEST_MESSAGE(
279 "Testing Monte Carlo performance engine against analytic results...");
280
281 SavedSettings backup;
282
283 Option::Type types[] = { Option::Call, Option::Put };
284 Real moneyness[] = { 0.9, 1.1 };
285 Real underlyings[] = { 100.0 };
286 Rate qRates[] = { 0.04, 0.06 };
287 Rate rRates[] = { 0.01, 0.10 };
288 Integer lengths[] = { 2, 4 };
289 Frequency frequencies[] = { Semiannual, Quarterly };
290 Volatility vols[] = { 0.10, 0.90 };
291
292 DayCounter dc = Actual360();
293 Date today = Date::todaysDate();
294 Settings::instance().evaluationDate() = today;
295
296 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
297 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
298 Handle<YieldTermStructure> qTS(flatRate(qRate, dc));
299 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
300 Handle<YieldTermStructure> rTS(flatRate(rRate, dc));
301 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
302 Handle<BlackVolTermStructure> volTS(flatVol(vol, dc));
303
304 ext::shared_ptr<BlackScholesMertonProcess> process(
305 new BlackScholesMertonProcess(Handle<Quote>(spot),
306 qTS, rTS, volTS));
307
308 for (Size i=0; i<LENGTH(types); i++) {
309 for (Size j=0; j<LENGTH(moneyness); j++) {
310 for (Size k=0; k<LENGTH(lengths); k++) {
311 for (Size kk=0; kk<LENGTH(frequencies); kk++) {
312
313 Period tenor = Period(frequencies[kk]);
314 ext::shared_ptr<EuropeanExercise> maturity(
315 new EuropeanExercise(today + lengths[k]*tenor));
316
317 ext::shared_ptr<PercentageStrikePayoff> payoff(
318 new PercentageStrikePayoff(types[i], moneyness[j]));
319
320 std::vector<Date> reset;
321 for (Date d = today + tenor; d < maturity->lastDate(); d += tenor)
322 reset.push_back(d);
323
324 CliquetOption option(payoff, maturity, reset);
325
326 ext::shared_ptr<PricingEngine> refEngine(
327 new AnalyticPerformanceEngine(process));
328
329 ext::shared_ptr<PricingEngine> mcEngine =
330 MakeMCPerformanceEngine<PseudoRandom>(process)
331 .withBrownianBridge()
332 .withAbsoluteTolerance(5.0e-3)
333 .withSeed(42);
334
335 for (Size l=0; l<LENGTH(underlyings); l++) {
336 for (Size m=0; m<LENGTH(qRates); m++) {
337 for (Size n=0; n<LENGTH(rRates); n++) {
338 for (Size p=0; p<LENGTH(vols); p++) {
339
340 Real u = underlyings[l];
341 Rate q = qRates[m],
342 r = rRates[n];
343 Volatility v = vols[p];
344 spot->setValue(u);
345 qRate->setValue(q);
346 rRate->setValue(r);
347 vol->setValue(v);
348
349 option.setPricingEngine(refEngine);
350 Real refValue = option.NPV();
351
352 option.setPricingEngine(mcEngine);
353 Real value = option.NPV();
354
355 Real error = std::fabs(refValue-value);
356 Real tolerance = 1.5e-2;
357 if (error > tolerance) {
358 REPORT_FAILURE("value", payoff, maturity,
359 u, q, r, today, v,
360 refValue, value,
361 error, tolerance);
362 }
363 }
364 }
365 }
366 }
367 }
368 }
369 }
370 }
371 }
372
373
suite()374 test_suite* CliquetOptionTest::suite() {
375 test_suite* suite = BOOST_TEST_SUITE("Cliquet option tests");
376 suite->add(QUANTLIB_TEST_CASE(&CliquetOptionTest::testValues));
377 suite->add(QUANTLIB_TEST_CASE(&CliquetOptionTest::testGreeks));
378 suite->add(QUANTLIB_TEST_CASE(&CliquetOptionTest::testPerformanceGreeks));
379 suite->add(QUANTLIB_TEST_CASE(&CliquetOptionTest::testMcPerformance));
380 return suite;
381 }
382
383