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