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