[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2014 Thema Consulting SA
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 "binaryoption.hpp"
21#include "utilities.hpp"
22#include <ql/time/calendars/nullcalendar.hpp>
23#include <ql/time/calendars/target.hpp>
24#include <ql/time/daycounters/actual360.hpp>
25#include <ql/math/interpolations/bicubicsplineinterpolation.hpp>
26#include <ql/instruments/barrieroption.hpp>
27#include <ql/models/equity/hestonmodel.hpp>
28#include <ql/pricingengines/barrier/analyticbinarybarrierengine.hpp>
29#include <ql/termstructures/yield/zerocurve.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
31#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
32#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
33#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
34#include <ql/utilities/dataformatters.hpp>
35
36using namespace QuantLib;
37using namespace boost::unit_test_framework;
38
39#undef REPORT_FAILURE
40#define REPORT_FAILURE(greekName, payoff, exercise, barrierType, barrier, s, q,\
41 r, today, v, expected, calculated, error, tolerance) \
42 BOOST_FAIL(payoff->optionType() << " option with " \
43 << barrierTypeToString(barrierType) << " barrier type:\n" \
44 << " barrier: " << barrier << "\n" \
45 << payoffTypeToString(payoff) << " payoff:\n" \
46 << " spot value: " << s << "\n" \
47 << " strike: " << payoff->strike() << "\n" \
48 << " dividend yield: " << io::rate(q) << "\n" \
49 << " risk-free rate: " << io::rate(r) << "\n" \
50 << " reference date: " << today << "\n" \
51 << " maturity: " << exercise->lastDate() << "\n" \
52 << " volatility: " << io::volatility(v) << "\n\n" \
53 << " expected " << greekName << ": " << expected << "\n" \
54 << " calculated " << greekName << ": " << calculated << "\n"\
55 << " error: " << error << "\n" \
56 << " tolerance: " << tolerance << "\n");
57
58namespace binary_option_test {
59
60 std::string barrierTypeToString(Barrier::Type type) {
61 switch(type){
62 case Barrier::DownIn:
63 return std::string("Down-and-in");
64 case Barrier::UpIn:
65 return std::string("Up-and-in");
66 case Barrier::DownOut:
67 return std::string("Down-and-out");
68 case Barrier::UpOut:
69 return std::string("Up-and-out");
70 default:
71 QL_FAIL("unknown exercise type");
72 }
73 }
74
75 struct BinaryOptionData {
76 Barrier::Type barrierType;
77 Real barrier;
78 Real cash; // cash payoff for cash-or-nothing
79 Option::Type type;
80 Real strike;
81 Real s; // spot
82 Rate q; // dividend
83 Rate r; // risk-free rate
84 Time t; // time to maturity
85 Volatility v; // volatility
86 Real result; // expected result
87 Real tol; // tolerance
88 };
89}
90
91
92void BinaryOptionTest::testCashOrNothingHaugValues() {
93
94 BOOST_TEST_MESSAGE("Testing cash-or-nothing barrier options against Haug's values...");
95
96 using namespace binary_option_test;
97
98 BinaryOptionData values[] = {
99 /* The data below are from
100 "Option pricing formulas 2nd Ed.", E.G. Haug, McGraw-Hill 2007 pag. 180 - cases 13,14,17,18,21,22,25,26
101 Note:
102 q is the dividend rate, while the book gives b, the cost of carry (q=r-b)
103 */
104 // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value, tol
105 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 4.9289, .tol: 1e-4 },
106 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 6.2150, .tol: 1e-4 },
107 // following value is wrong in book.
108 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 5.8926, .tol: 1e-4 },
109 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 7.4519, .tol: 1e-4 },
110 // 17,18
111 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 4.4314, .tol: 1e-4 },
112 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 3.1454, .tol: 1e-4 },
113 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 5.3297, .tol: 1e-4 },
114 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 3.7704, .tol: 1e-4 },
115 // 21,22
116 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 4.8758, .tol: 1e-4 },
117 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 4.9081, .tol: 1e-4 },
118 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
119 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0407, .tol: 1e-4 },
120 // 25,26
121 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0323, .tol: 1e-4 },
122 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
123 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 3.0461, .tol: 1e-4 },
124 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 3.0054, .tol: 1e-4 },
125
126 // other values calculated with book vba
127 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 95.00,.q: -0.14, .r: 0.10, .t: 0.5, .v: 0.20, .result: 8.6806, .tol: 1e-4 },
128 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 102.00, .s: 95.00, .q: 0.03, .r: 0.10, .t: 0.5, .v: 0.20, .result: 5.3112, .tol: 1e-4 },
129 // degenerate conditions (barrier touched)
130 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 7.4926, .tol: 1e-4 },
131 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 11.1231, .tol: 1e-4 },
132 // 17,18
133 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 98.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 7.1344, .tol: 1e-4 },
134 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 102.00, .s: 101.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 5.9299, .tol: 1e-4 },
135 // 21,22
136 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 99.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
137 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Call, .strike: 98.00, .s: 101.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
138 // 25,26
139 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 99.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
140 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 15.00, .type: Option::Put, .strike: 98.00, .s: 101.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
141 };
142
143 DayCounter dc = Actual360();
144 Date today = Date::todaysDate();
145
146 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
147 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
148 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
149 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
150 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
151 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
152 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
153
154 for (auto& value : values) {
155
156 ext::shared_ptr<StrikedTypePayoff> payoff(
157 new CashOrNothingPayoff(value.type, value.strike, value.cash));
158
159 Date exDate = today + timeToDays(t: value.t);
160 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today,
161 exDate,
162 true));
163
164 spot->setValue(value.s);
165 qRate->setValue(value.q);
166 rRate->setValue(value.r);
167 vol->setValue(value.v);
168
169 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
170 BlackScholesMertonProcess(Handle<Quote>(spot),
171 Handle<YieldTermStructure>(qTS),
172 Handle<YieldTermStructure>(rTS),
173 Handle<BlackVolTermStructure>(volTS)));
174 ext::shared_ptr<PricingEngine> engine(
175 new AnalyticBinaryBarrierEngine(stochProcess));
176
177 BarrierOption opt(value.barrierType, value.barrier, 0, payoff, amExercise);
178
179 opt.setPricingEngine(engine);
180
181 Real calculated = opt.NPV();
182 Real error = std::fabs(x: calculated - value.result);
183 if (error > value.tol) {
184 REPORT_FAILURE("value", payoff, amExercise, value.barrierType, value.barrier, value.s,
185 value.q, value.r, today, value.v, value.result, calculated, error,
186 value.tol);
187 }
188 }
189}
190
191void BinaryOptionTest::testAssetOrNothingHaugValues() {
192
193 BOOST_TEST_MESSAGE("Testing asset-or-nothing barrier options against Haug's values...");
194
195 using namespace binary_option_test;
196
197 BinaryOptionData values[] = {
198 /* The data below are from
199 "Option pricing formulas 2nd Ed.", E.G. Haug, McGraw-Hill 2007 pag. 180 - cases 15,16,19,20,23,24,27,28
200 Note:
201 q is the dividend rate, while the book gives b, the cost of carry (q=r-b)
202 */
203 // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value, tol
204 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 37.2782, .tol: 1e-4 },
205 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 45.8530, .tol: 1e-4 },
206 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 44.5294, .tol: 1e-4 },
207 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 54.9262, .tol: 1e-4 },
208 // 19,20
209 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 27.5644, .tol: 1e-4 },
210 { .barrierType: Barrier::DownIn, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 18.9896, .tol: 1e-4 },
211 // following value is wrong in book.
212 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 33.1723, .tol: 1e-4 },
213 { .barrierType: Barrier::UpIn, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 22.7755, .tol: 1e-4 },
214 // 23,24
215 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 39.9391, .tol: 1e-4 },
216 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 40.1574, .tol: 1e-4 },
217 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
218 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 0.00, .type: Option::Call, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.2676, .tol: 1e-4 },
219 // 27,28
220 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 102.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.2183, .tol: 1e-4 },
221 { .barrierType: Barrier::DownOut, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 98.00, .s: 105.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 0.0000, .tol: 1e-4 },
222 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 102.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 17.2983, .tol: 1e-4 },
223 { .barrierType: Barrier::UpOut, .barrier: 100.00, .cash: 0.00, .type: Option::Put, .strike: 98.00, .s: 95.00, .q: 0.00, .r: 0.10, .t: 0.5, .v: 0.20, .result: 17.0306, .tol: 1e-4 },
224 };
225
226 DayCounter dc = Actual360();
227 Date today = Date::todaysDate();
228
229 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
230 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
231 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
232 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
233 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
234 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
235 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
236
237 for (auto& value : values) {
238
239 ext::shared_ptr<StrikedTypePayoff> payoff(
240 new AssetOrNothingPayoff(value.type, value.strike));
241
242 Date exDate = today + timeToDays(t: value.t);
243 ext::shared_ptr<Exercise> amExercise(new AmericanExercise(today, exDate, true));
244
245 spot->setValue(value.s);
246 qRate->setValue(value.q);
247 rRate->setValue(value.r);
248 vol->setValue(value.v);
249
250 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
251 BlackScholesMertonProcess(Handle<Quote>(spot),
252 Handle<YieldTermStructure>(qTS),
253 Handle<YieldTermStructure>(rTS),
254 Handle<BlackVolTermStructure>(volTS)));
255 ext::shared_ptr<PricingEngine> engine(
256 new AnalyticBinaryBarrierEngine(stochProcess));
257
258 BarrierOption opt(value.barrierType, value.barrier, 0, payoff, amExercise);
259
260 opt.setPricingEngine(engine);
261
262 Real calculated = opt.NPV();
263 Real error = std::fabs(x: calculated - value.result);
264 if (error > value.tol) {
265 REPORT_FAILURE("value", payoff, amExercise, value.barrierType, value.barrier, value.s,
266 value.q, value.r, today, value.v, value.result, calculated, error,
267 value.tol);
268 }
269 }
270}
271
272test_suite* BinaryOptionTest::suite() {
273 auto* suite = BOOST_TEST_SUITE("Binary");
274 suite->add(QUANTLIB_TEST_CASE(&BinaryOptionTest::testCashOrNothingHaugValues));
275 suite->add(QUANTLIB_TEST_CASE(&BinaryOptionTest::testAssetOrNothingHaugValues));
276 return suite;
277}
278

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