1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 /*
4  Copyright (C) 2004, 2007 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 "forwardoption.hpp"
21 #include "utilities.hpp"
22 #include <ql/time/daycounters/actual360.hpp>
23 #include <ql/instruments/forwardvanillaoption.hpp>
24 #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
25 #include <ql/pricingengines/vanilla/binomialengine.hpp>
26 #include <ql/pricingengines/forward/forwardengine.hpp>
27 #include <ql/pricingengines/forward/forwardperformanceengine.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 
33 using namespace QuantLib;
34 using namespace boost::unit_test_framework;
35 
36 #undef REPORT_FAILURE
37 #define REPORT_FAILURE(greekName, payoff, exercise, s, q, r, today, \
38                        v, moneyness, reset, expected, calculated, \
39                        error, tolerance) \
40     BOOST_FAIL("Forward " << exerciseTypeToString(exercise) << " " \
41                << payoff->optionType() << " option with " \
42                << payoffTypeToString(payoff) << " payoff:\n" \
43                << "    spot value:        " << s << "\n" \
44                << "    strike:            " << payoff->strike() <<"\n" \
45                << "    moneyness:         " << moneyness << "\n" \
46                << "    dividend yield:    " << io::rate(q) << "\n" \
47                << "    risk-free rate:    " << io::rate(r) << "\n" \
48                << "    reference date:    " << today << "\n" \
49                << "    reset date:        " << reset << "\n" \
50                << "    maturity:          " << exercise->lastDate() << "\n" \
51                << "    volatility:        " << io::volatility(v) << "\n\n" \
52                << "    expected   " << greekName << ": " << expected << "\n" \
53                << "    calculated " << greekName << ": " << calculated << "\n"\
54                << "    error:            " << error << "\n" \
55                << "    tolerance:        " << tolerance);
56 
57 namespace {
58 
59     struct ForwardOptionData {
60         Option::Type type;
61         Real moneyness;
62         Real s;          // spot
63         Rate q;          // dividend
64         Rate r;          // risk-free rate
65         Time start;      // time to reset
66         Time t;          // time to maturity
67         Volatility v;    // volatility
68         Real result;     // expected result
69         Real tol;        // tolerance
70     };
71 
72 }
73 
74 
testValues()75 void ForwardOptionTest::testValues() {
76 
77     BOOST_TEST_MESSAGE("Testing forward option values...");
78 
79     /* The data below are from
80        "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
81     */
82     ForwardOptionData values[] = {
83         //  type, moneyness, spot,  div, rate,start,   t,  vol, result, tol
84         // "Option pricing formulas", pag. 37
85         { Option::Call, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 4.4064, 1.0e-4 },
86         // "Option pricing formulas", VBA code
87         {  Option::Put, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 8.2971, 1.0e-4 }
88     };
89 
90     DayCounter dc = Actual360();
91     Date today = Date::todaysDate();
92 
93     ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
94     ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
95     Handle<YieldTermStructure> qTS(flatRate(today, qRate, dc));
96     ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
97     Handle<YieldTermStructure> rTS(flatRate(today, rRate, dc));
98     ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
99     Handle<BlackVolTermStructure> volTS(flatVol(today, vol, dc));
100 
101     ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
102          new BlackScholesMertonProcess(Handle<Quote>(spot),
103                                        Handle<YieldTermStructure>(qTS),
104                                        Handle<YieldTermStructure>(rTS),
105                                        Handle<BlackVolTermStructure>(volTS)));
106 
107     ext::shared_ptr<PricingEngine> engine(
108               new ForwardVanillaEngine<AnalyticEuropeanEngine>(stochProcess));
109 
110     for (Size i=0; i<LENGTH(values); i++) {
111 
112         ext::shared_ptr<StrikedTypePayoff> payoff(
113                                  new PlainVanillaPayoff(values[i].type, 0.0));
114         Date exDate = today + Integer(values[i].t*360+0.5);
115         ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
116         Date reset = today + Integer(values[i].start*360+0.5);
117 
118         spot ->setValue(values[i].s);
119         qRate->setValue(values[i].q);
120         rRate->setValue(values[i].r);
121         vol  ->setValue(values[i].v);
122 
123         ForwardVanillaOption option(values[i].moneyness, reset,
124                                     payoff, exercise);
125         option.setPricingEngine(engine);
126 
127         Real calculated = option.NPV();
128         Real error = std::fabs(calculated-values[i].result);
129         Real tolerance = 1e-4;
130         if (error>tolerance) {
131             REPORT_FAILURE("value", payoff, exercise, values[i].s,
132                            values[i].q, values[i].r, today,
133                            values[i].v, values[i].moneyness, reset,
134                            values[i].result, calculated,
135                            error, tolerance);
136         }
137     }
138 
139 }
140 
141 
testPerformanceValues()142 void ForwardOptionTest::testPerformanceValues() {
143 
144     BOOST_TEST_MESSAGE("Testing forward performance option values...");
145 
146     /* The data below are the performance equivalent of the
147        forward options tested above and taken from
148        "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
149     */
150     ForwardOptionData values[] = {
151         //  type, moneyness, spot,  div, rate,start, maturity,  vol,                       result, tol
152         { Option::Call, 1.1, 60.0, 0.04, 0.08, 0.25,      1.0, 0.30, 4.4064/60*std::exp(-0.04*0.25), 1.0e-4 },
153         {  Option::Put, 1.1, 60.0, 0.04, 0.08, 0.25,      1.0, 0.30, 8.2971/60*std::exp(-0.04*0.25), 1.0e-4 }
154     };
155 
156     DayCounter dc = Actual360();
157     Date today = Date::todaysDate();
158 
159     ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
160     ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
161     Handle<YieldTermStructure> qTS(flatRate(today, qRate, dc));
162     ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
163     Handle<YieldTermStructure> rTS(flatRate(today, rRate, dc));
164     ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
165     Handle<BlackVolTermStructure> volTS(flatVol(today, vol, dc));
166 
167     ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
168          new BlackScholesMertonProcess(Handle<Quote>(spot),
169                                        Handle<YieldTermStructure>(qTS),
170                                        Handle<YieldTermStructure>(rTS),
171                                        Handle<BlackVolTermStructure>(volTS)));
172 
173     ext::shared_ptr<PricingEngine> engine(
174         new ForwardPerformanceVanillaEngine<AnalyticEuropeanEngine>(
175                                                                stochProcess));
176 
177     for (Size i=0; i<LENGTH(values); i++) {
178 
179         ext::shared_ptr<StrikedTypePayoff> payoff(
180                                  new PlainVanillaPayoff(values[i].type, 0.0));
181         Date exDate = today + Integer(values[i].t*360+0.5);
182         ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
183         Date reset = today + Integer(values[i].start*360+0.5);
184 
185         spot ->setValue(values[i].s);
186         qRate->setValue(values[i].q);
187         rRate->setValue(values[i].r);
188         vol  ->setValue(values[i].v);
189 
190         ForwardVanillaOption option(values[i].moneyness, reset,
191                                     payoff, exercise);
192         option.setPricingEngine(engine);
193 
194         Real calculated = option.NPV();
195         Real error = std::fabs(calculated-values[i].result);
196         Real tolerance = 1e-4;
197         if (error>tolerance) {
198             REPORT_FAILURE("value", payoff, exercise, values[i].s,
199                            values[i].q, values[i].r, today,
200                            values[i].v, values[i].moneyness, reset,
201                            values[i].result, calculated,
202                            error, tolerance);
203         }
204     }
205 
206 }
207 
208 
209 namespace {
210 
211     template <template <class> class Engine>
testForwardGreeks()212     void testForwardGreeks() {
213 
214         std::map<std::string,Real> calculated, expected, tolerance;
215         tolerance["delta"]   = 1.0e-5;
216         tolerance["gamma"]   = 1.0e-5;
217         tolerance["theta"]   = 1.0e-5;
218         tolerance["rho"]     = 1.0e-5;
219         tolerance["divRho"]  = 1.0e-5;
220         tolerance["vega"]    = 1.0e-5;
221 
222         Option::Type types[] = { Option::Call, Option::Put };
223         Real moneyness[] = { 0.9, 1.0, 1.1 };
224         Real underlyings[] = { 100.0 };
225         Rate qRates[] = { 0.04, 0.05, 0.06 };
226         Rate rRates[] = { 0.01, 0.05, 0.15 };
227         Integer lengths[] = { 1, 2 };
228         Integer startMonths[] = { 6, 9 };
229         Volatility vols[] = { 0.11, 0.50, 1.20 };
230 
231         DayCounter dc = Actual360();
232         Date today = Date::todaysDate();
233         Settings::instance().evaluationDate() = today;
234 
235         ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
236         ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
237         Handle<YieldTermStructure> qTS(flatRate(qRate, dc));
238         ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
239         Handle<YieldTermStructure> rTS(flatRate(rRate, dc));
240         ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
241         Handle<BlackVolTermStructure> volTS(flatVol(vol, dc));
242 
243         ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
244           new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
245 
246         ext::shared_ptr<PricingEngine> engine(
247                             new Engine<AnalyticEuropeanEngine>(stochProcess));
248 
249         for (Size i=0; i<LENGTH(types); i++) {
250           for (Size j=0; j<LENGTH(moneyness); j++) {
251             for (Size k=0; k<LENGTH(lengths); k++) {
252               for (Size h=0; h<LENGTH(startMonths); h++) {
253 
254                 Date exDate = today + lengths[k]*Years;
255                 ext::shared_ptr<Exercise> exercise(
256                                                 new EuropeanExercise(exDate));
257 
258                 Date reset = today + startMonths[h]*Months;
259 
260                 ext::shared_ptr<StrikedTypePayoff> payoff(
261                                        new PlainVanillaPayoff(types[i], 0.0));
262 
263                 ForwardVanillaOption option(moneyness[j], reset,
264                                             payoff, exercise);
265                 option.setPricingEngine(engine);
266 
267                 for (Size l=0; l<LENGTH(underlyings); l++) {
268                   for (Size m=0; m<LENGTH(qRates); m++) {
269                     for (Size n=0; n<LENGTH(rRates); n++) {
270                       for (Size p=0; p<LENGTH(vols); p++) {
271 
272                         Real u = underlyings[l];
273                         Rate q = qRates[m],
274                              r = rRates[n];
275                         Volatility v = vols[p];
276                         spot->setValue(u);
277                         qRate->setValue(q);
278                         rRate->setValue(r);
279                         vol->setValue(v);
280 
281                         Real value = option.NPV();
282                         calculated["delta"]   = option.delta();
283                         calculated["gamma"]   = option.gamma();
284                         calculated["theta"]   = option.theta();
285                         calculated["rho"]     = option.rho();
286                         calculated["divRho"]  = option.dividendRho();
287                         calculated["vega"]    = option.vega();
288 
289                         if (value > spot->value()*1.0e-5) {
290                           // perturb spot and get delta and gamma
291                           Real du = u*1.0e-4;
292                           spot->setValue(u+du);
293                           Real value_p = option.NPV(),
294                                delta_p = option.delta();
295                           spot->setValue(u-du);
296                           Real value_m = option.NPV(),
297                                delta_m = option.delta();
298                           spot->setValue(u);
299                           expected["delta"] = (value_p - value_m)/(2*du);
300                           expected["gamma"] = (delta_p - delta_m)/(2*du);
301 
302                           // perturb rates and get rho and dividend rho
303                           Spread dr = r*1.0e-4;
304                           rRate->setValue(r+dr);
305                           value_p = option.NPV();
306                           rRate->setValue(r-dr);
307                           value_m = option.NPV();
308                           rRate->setValue(r);
309                           expected["rho"] = (value_p - value_m)/(2*dr);
310 
311                           Spread dq = q*1.0e-4;
312                           qRate->setValue(q+dq);
313                           value_p = option.NPV();
314                           qRate->setValue(q-dq);
315                           value_m = option.NPV();
316                           qRate->setValue(q);
317                           expected["divRho"] = (value_p - value_m)/(2*dq);
318 
319                           // perturb volatility and get vega
320                           Volatility dv = v*1.0e-4;
321                           vol->setValue(v+dv);
322                           value_p = option.NPV();
323                           vol->setValue(v-dv);
324                           value_m = option.NPV();
325                           vol->setValue(v);
326                           expected["vega"] = (value_p - value_m)/(2*dv);
327 
328                           // perturb date and get theta
329                           Time dT = dc.yearFraction(today-1, today+1);
330                           Settings::instance().evaluationDate() = today-1;
331                           value_m = option.NPV();
332                           Settings::instance().evaluationDate() = today+1;
333                           value_p = option.NPV();
334                           Settings::instance().evaluationDate() = today;
335                           expected["theta"] = (value_p - value_m)/dT;
336 
337                           // compare
338                           std::map<std::string,Real>::iterator it;
339                           for (it = calculated.begin();
340                                it != calculated.end(); ++it) {
341                               std::string greek = it->first;
342                               Real expct = expected  [greek],
343                                    calcl = calculated[greek],
344                                    tol   = tolerance [greek];
345                               Real error = relativeError(expct,calcl,u);
346                               if (error>tol) {
347                                   REPORT_FAILURE(greek, payoff, exercise,
348                                                  u, q, r, today, v,
349                                                  moneyness[j], reset,
350                                                  expct, calcl, error, tol);
351                               }
352                           }
353                         }
354                       }
355                     }
356                   }
357                 }
358               }
359             }
360           }
361         }
362     }
363 
364 }
365 
366 
testGreeks()367 void ForwardOptionTest::testGreeks() {
368 
369     BOOST_TEST_MESSAGE("Testing forward option greeks...");
370 
371     SavedSettings backup;
372 
373     testForwardGreeks<ForwardVanillaEngine>();
374 }
375 
376 
testPerformanceGreeks()377 void ForwardOptionTest::testPerformanceGreeks() {
378 
379     BOOST_TEST_MESSAGE("Testing forward performance option greeks...");
380 
381     SavedSettings backup;
382 
383     testForwardGreeks<ForwardPerformanceVanillaEngine>();
384 }
385 
386 
387 class TestBinomialEngine : public BinomialVanillaEngine<CoxRossRubinstein>
388 {
389 private:
390 public:
TestBinomialEngine(const ext::shared_ptr<GeneralizedBlackScholesProcess> & process)391    explicit TestBinomialEngine(
392            const ext::shared_ptr<GeneralizedBlackScholesProcess > &process)
393    : BinomialVanillaEngine<CoxRossRubinstein>(process, 300) // fixed steps
394     {}
395 };
396 
397 
testGreeksInitialization()398 void ForwardOptionTest::testGreeksInitialization() {
399    BOOST_TEST_MESSAGE("Testing forward option greeks initialization...");
400 
401    DayCounter dc = Actual360();
402    SavedSettings backup;
403    Date today = Date::todaysDate();
404    Settings::instance().evaluationDate() = today;
405 
406    ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
407    ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
408    Handle<YieldTermStructure> qTS(flatRate(qRate, dc));
409    ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
410    Handle<YieldTermStructure> rTS(flatRate(rRate, dc));
411    ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.11));
412    Handle<BlackVolTermStructure> volTS(flatVol(vol, dc));
413 
414    ext::shared_ptr<BlackScholesMertonProcess> stochProcess(
415       new BlackScholesMertonProcess(Handle<Quote>(spot), qTS, rTS, volTS));
416 
417    ext::shared_ptr<PricingEngine> engine(
418                         new ForwardVanillaEngine<TestBinomialEngine>(stochProcess));
419    Date exDate = today + 1*Years;
420    ext::shared_ptr<Exercise> exercise(
421                                  new EuropeanExercise(exDate));
422    Date reset = today + 6*Months;
423    ext::shared_ptr<StrikedTypePayoff> payoff(
424                         new PlainVanillaPayoff(Option::Call, 0.0));
425 
426    ForwardVanillaOption option(0.9, reset, payoff, exercise);
427    option.setPricingEngine(engine);
428 
429    ext::shared_ptr<PricingEngine> ctrlengine(
430                         new TestBinomialEngine(stochProcess));
431    VanillaOption ctrloption(payoff, exercise);
432    ctrloption.setPricingEngine(ctrlengine);
433 
434    Real delta = 0;
435    try
436    {
437       delta = ctrloption.delta();
438    }
439    catch (const QuantLib::Error &) {
440       // if normal option can't calculate delta,
441       // nor should forward
442       try
443       {
444          delta   = option.delta();
445       }
446       catch (const QuantLib::Error &) {
447          delta = Null<Real>();
448       }
449       QL_REQUIRE(delta == Null<Real>(), "Forward delta invalid");
450    }
451 
452    Real rho  = 0;
453    try
454    {
455       rho = ctrloption.rho();
456    }
457    catch (const QuantLib::Error &) {
458       // if normal option can't calculate rho,
459       // nor should forward
460       try
461       {
462          rho = option.rho();
463       }
464       catch (const QuantLib::Error &) {
465          rho = Null<Real>();
466       }
467       QL_REQUIRE(rho == Null<Real>(), "Forward rho invalid");
468    }
469 
470    Real divRho = 0;
471    try
472    {
473       divRho = ctrloption.dividendRho();
474    }
475    catch (const QuantLib::Error &) {
476       // if normal option can't calculate divRho,
477       // nor should forward
478       try
479       {
480          divRho = option.dividendRho();
481       }
482       catch (const QuantLib::Error &) {
483          divRho = Null<Real>();
484       }
485       QL_REQUIRE(divRho == Null<Real>(), "Forward dividendRho invalid");
486    }
487 
488    Real vega = 0;
489    try
490    {
491       vega = ctrloption.vega();
492    }
493    catch (const QuantLib::Error &) {
494       // if normal option can't calculate vega,
495       // nor should forward
496       try
497       {
498          vega = option.vega();
499       }
500       catch (const QuantLib::Error &) {
501          vega = Null<Real>();
502       }
503       QL_REQUIRE(vega == Null<Real>(), "Forward vega invalid");
504    }
505 }
506 
507 
508 
suite()509 test_suite* ForwardOptionTest::suite() {
510     test_suite* suite = BOOST_TEST_SUITE("Forward option tests");
511     suite->add(QUANTLIB_TEST_CASE(&ForwardOptionTest::testValues));
512     suite->add(QUANTLIB_TEST_CASE(&ForwardOptionTest::testGreeks));
513     suite->add(QUANTLIB_TEST_CASE(&ForwardOptionTest::testPerformanceValues));
514     suite->add(QUANTLIB_TEST_CASE(&ForwardOptionTest::testPerformanceGreeks));
515     suite->add(QUANTLIB_TEST_CASE(&ForwardOptionTest::testGreeksInitialization));
516 
517     return suite;
518 }
519 
520