1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2013 Yue Tian
5 Copyright (C) 2015 Thema Consulting SA
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include "doublebarrieroption.hpp"
22#include "utilities.hpp"
23#include <ql/time/calendars/nullcalendar.hpp>
24#include <ql/time/calendars/target.hpp>
25#include <ql/time/daycounters/actual360.hpp>
26#include <ql/math/functional.hpp>
27#include <ql/math/interpolations/bicubicsplineinterpolation.hpp>
28#include <ql/pricingengines/blackformula.hpp>
29#include <ql/instruments/doublebarrieroption.hpp>
30#include <ql/pricingengines/barrier/analyticdoublebarrierengine.hpp>
31#include <ql/pricingengines/barrier/fdhestondoublebarrierengine.hpp>
32#include <ql/experimental/barrieroption/binomialdoublebarrierengine.hpp>
33#include <ql/experimental/barrieroption/suowangdoublebarrierengine.hpp>
34#include <ql/experimental/barrieroption/vannavolgadoublebarrierengine.hpp>
35#include <ql/experimental/barrieroption/mcdoublebarrierengine.hpp>
36#include <ql/termstructures/yield/zerocurve.hpp>
37#include <ql/termstructures/yield/flatforward.hpp>
38#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
39#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
40#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
41#include <ql/utilities/dataformatters.hpp>
42#include <ql/instruments/vanillaoption.hpp>
43#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
44#include <ql/models/equity/hestonmodel.hpp>
45
46using namespace QuantLib;
47using namespace boost::unit_test_framework;
48
49#undef REPORT_FAILURE
50#define REPORT_FAILURE(greekName, barrierType, barrierlo, barrierhi, \
51 payoff, exercise, s, q, r, today, v, expected, \
52 calculated, error, tolerance) \
53 BOOST_ERROR("\n" << barrierType << " " \
54 << exerciseTypeToString(exercise) << " " \
55 << payoff->optionType() << " option with " \
56 << payoffTypeToString(payoff) << " payoff:\n" \
57 << " underlying value: " << s << "\n" \
58 << " strike: " << payoff->strike() << "\n" \
59 << " barrier low: " << barrierlo << "\n" \
60 << " barrier high: " << barrierhi << "\n" \
61 << " dividend yield: " << io::rate(q) << "\n" \
62 << " risk-free rate: " << io::rate(r) << "\n" \
63 << " reference date: " << today << "\n" \
64 << " maturity: " << exercise->lastDate() << "\n" \
65 << " volatility: " << io::volatility(v) << "\n\n" \
66 << " expected " << greekName << ": " << expected << "\n" \
67 << " calculated " << greekName << ": " << calculated << "\n"\
68 << " error: " << error << "\n" \
69 << " tolerance: " << tolerance);
70
71#undef REPORT_FAILURE_VANNAVOLGA
72#define REPORT_FAILURE_VANNAVOLGA(greekName, barrierType, \
73 barrier1, barrier2, rebate, payoff, \
74 exercise, s, q, r, today, \
75 vol25Put, atmVol, vol25Call, v, \
76 expected, calculated, error, tolerance) \
77 BOOST_ERROR("\n" <<"Double Barrier Option " \
78 << barrierType << " " \
79 << exerciseTypeToString(exercise) << " " \
80 << payoff->optionType() << " option with " \
81 << payoffTypeToString(payoff) << " payoff:\n" \
82 << " underlying value: " << s << "\n" \
83 << " strike: " << payoff->strike() << "\n" \
84 << " barrier1: " << barrier1 << "\n" \
85 << " barrier2: " << barrier2 << "\n" \
86 << " rebate: " << rebate << "\n" \
87 << " dividend yield: " << io::rate(q) << "\n" \
88 << " risk-free rate: " << io::rate(r) << "\n" \
89 << " reference date: " << today << "\n" \
90 << " maturity: " << exercise->lastDate() << "\n" \
91 << " 25PutVol: " << io::volatility(vol25Put) << "\n" \
92 << " atmVol: " << io::volatility(atmVol) << "\n" \
93 << " 25CallVol: " << io::volatility(vol25Call) << "\n" \
94 << " volatility: " << io::volatility(v) << "\n\n" \
95 << " expected " << greekName << ": " << expected << "\n" \
96 << " calculated " << greekName << ": " << calculated << "\n"\
97 << " error: " << error << "\n" \
98 << " tolerance: " << tolerance);
99
100#undef REPORT_FAILURE_DOUBLE_BARRIER_MC
101#define REPORT_FAILURE_DOUBLE_BARRIER_MC(analytical, monteCarlo, diff) \
102 BOOST_ERROR("\n" <<"Double Barrier Option " \
103 << "Threshold exceeded: " << diff << "\n" \
104 << "Analytical: " << analytical << "\n" \
105 << "Monte Carlo: " << monteCarlo << "\n");
106
107namespace double_barrier_option_test {
108
109 struct NewBarrierOptionData {
110 DoubleBarrier::Type barrierType;
111 Real barrierlo;
112 Real barrierhi;
113 Option::Type type;
114 Exercise::Type exType;
115 Real strike;
116 Real s; // spot
117 Rate q; // dividend
118 Rate r; // risk-free rate
119 Time t; // time to maturity
120 Volatility v; // volatility
121 Real result; // result
122 Real tol; // tolerance
123 };
124
125 struct DoubleBarrierFxOptionData {
126 DoubleBarrier::Type barrierType;
127 Real barrier1;
128 Real barrier2;
129 Real rebate;
130 Option::Type type;
131 Real strike;
132 Real s; // spot
133 Rate q; // dividend
134 Rate r; // risk-free rate
135 Time t; // time to maturity
136 Volatility vol25Put; // 25 delta put vol
137 Volatility volAtm; // atm vol
138 Volatility vol25Call; // 25 delta call vol
139 Volatility v; // volatility at strike
140 Real result; // result
141 Real tol; // tolerance
142 };
143
144}
145
146
147void DoubleBarrierOptionTest::testEuropeanHaugValues() {
148
149 BOOST_TEST_MESSAGE("Testing double barrier european options against Haug's values...");
150
151 using namespace double_barrier_option_test;
152
153 Exercise::Type european = Exercise::European;
154 NewBarrierOptionData values[] = {
155 /* The data below are from
156 "The complete guide to option pricing formulas 2nd Ed",E.G. Haug, McGraw-Hill, p.156 and following.
157
158 Note:
159 The book uses b instead of q (q=r-b)
160 */
161 // BarrierType, barr.lo, barr.hi, type, exercise,strk, s, q, r, t, v, result, tol
162 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 4.3515, .tol: 1.0e-4},
163 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 6.1644, .tol: 1.0e-4},
164 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 7.0373, .tol: 1.0e-4},
165 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 6.9853, .tol: 1.0e-4},
166 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 7.9336, .tol: 1.0e-4},
167 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 6.5088, .tol: 1.0e-4},
168
169 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 4.3505, .tol: 1.0e-4},
170 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 5.8500, .tol: 1.0e-4},
171 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 5.7726, .tol: 1.0e-4},
172 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 6.8082, .tol: 1.0e-4},
173 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 6.3383, .tol: 1.0e-4},
174 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 4.3841, .tol: 1.0e-4},
175
176 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 4.3139, .tol: 1.0e-4},
177 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 4.8293, .tol: 1.0e-4},
178 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 3.7765, .tol: 1.0e-4},
179 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 5.9697, .tol: 1.0e-4},
180 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 4.0004, .tol: 1.0e-4},
181 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 2.2563, .tol: 1.0e-4},
182
183 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 3.7516, .tol: 1.0e-4},
184 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 2.6387, .tol: 1.0e-4},
185 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 1.4903, .tol: 1.0e-4},
186 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 3.5805, .tol: 1.0e-4},
187 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 1.5098, .tol: 1.0e-4},
188 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 0.5635, .tol: 1.0e-4},
189
190 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 1.2055, .tol: 1.0e-4},
191 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 0.3098, .tol: 1.0e-4},
192 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 0.0477, .tol: 1.0e-4},
193 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 0.5537, .tol: 1.0e-4},
194 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 0.0441, .tol: 1.0e-4},
195 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 0.0011, .tol: 1.0e-4},
196
197 // BarrierType, barr.lo, barr.hi, type, exercise,strk, s, q, r, t, v, result, tol
198 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 1.8825, .tol: 1.0e-4},
199 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 3.7855, .tol: 1.0e-4},
200 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 5.7191, .tol: 1.0e-4},
201 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 2.1374, .tol: 1.0e-4},
202 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 4.7033, .tol: 1.0e-4},
203 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 7.1683, .tol: 1.0e-4},
204
205 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 1.8825, .tol: 1.0e-4},
206 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 3.7845, .tol: 1.0e-4},
207 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 5.6060, .tol: 1.0e-4},
208 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 2.1374, .tol: 1.0e-4},
209 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 4.6236, .tol: 1.0e-4},
210 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 6.1062, .tol: 1.0e-4},
211
212 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 1.8825, .tol: 1.0e-4},
213 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 3.7014, .tol: 1.0e-4},
214 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 4.6472, .tol: 1.0e-4},
215 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 2.1325, .tol: 1.0e-4},
216 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 3.8944, .tol: 1.0e-4},
217 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 3.5868, .tol: 1.0e-4},
218
219 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 1.8600, .tol: 1.0e-4},
220 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 2.6866, .tol: 1.0e-4},
221 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 2.0719, .tol: 1.0e-4},
222 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 1.8883, .tol: 1.0e-4},
223 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 1.7851, .tol: 1.0e-4},
224 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 0.8244, .tol: 1.0e-4},
225
226 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 0.9473, .tol: 1.0e-4},
227 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 0.3449, .tol: 1.0e-4},
228 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 0.0578, .tol: 1.0e-4},
229 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 0.4555, .tol: 1.0e-4},
230 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 0.0491, .tol: 1.0e-4},
231 { .barrierType: DoubleBarrier::KnockOut, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Put, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 0.0013, .tol: 1.0e-4},
232
233 // BarrierType, barr.lo, barr.hi, type, strk, s, q, r, t, v, result, tol
234 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 0.0000, .tol: 1.0e-4},
235 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 0.0900, .tol: 1.0e-4},
236 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 1.1537, .tol: 1.0e-4},
237 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 0.0292, .tol: 1.0e-4},
238 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 1.6487, .tol: 1.0e-4},
239 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 50.0, .barrierhi: 150.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 5.7321, .tol: 1.0e-4},
240
241 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 0.0010, .tol: 1.0e-4},
242 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 0.4045, .tol: 1.0e-4},
243 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 2.4184, .tol: 1.0e-4},
244 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 0.2062, .tol: 1.0e-4},
245 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 3.2439, .tol: 1.0e-4},
246 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 60.0, .barrierhi: 140.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 7.8569, .tol: 1.0e-4},
247
248 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 0.0376, .tol: 1.0e-4},
249 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 1.4252, .tol: 1.0e-4},
250 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 4.4145, .tol: 1.0e-4},
251 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 1.0447, .tol: 1.0e-4},
252 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 5.5818, .tol: 1.0e-4},
253 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 70.0, .barrierhi: 130.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 9.9846, .tol: 1.0e-4},
254
255 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 0.5999, .tol: 1.0e-4},
256 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 3.6158, .tol: 1.0e-4},
257 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 6.7007, .tol: 1.0e-4},
258 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 3.4340, .tol: 1.0e-4},
259 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 8.0724, .tol: 1.0e-4},
260 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 80.0, .barrierhi: 120.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 11.6774, .tol: 1.0e-4},
261
262 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.15, .result: 3.1460, .tol: 1.0e-4},
263 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.25, .result: 5.9447, .tol: 1.0e-4},
264 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.25, .v: 0.35, .result: 8.1432, .tol: 1.0e-4},
265 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.15, .result: 6.4608, .tol: 1.0e-4},
266 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.25, .result: 9.5382, .tol: 1.0e-4},
267 { .barrierType: DoubleBarrier::KnockIn, .barrierlo: 90.0, .barrierhi: 110.0, .type: Option::Call, .exType: european, .strike: 100, .s: 100.0, .q: 0.0, .r: 0.1, .t: 0.50, .v: 0.35, .result: 12.2398, .tol: 1.0e-4},
268
269 };
270
271 DayCounter dc = Actual360();
272 Date today = Date::todaysDate();
273
274 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
275 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
276 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
277 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
278 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
279 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
280 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
281
282 for (auto& value : values) {
283 Date exDate = today + timeToDays(t: value.t);
284 ext::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
285
286 spot->setValue(value.s);
287 qRate->setValue(value.q);
288 rRate->setValue(value.r);
289 vol->setValue(value.v);
290
291 ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(value.type, value.strike));
292
293 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
294 BlackScholesMertonProcess(Handle<Quote>(spot),
295 Handle<YieldTermStructure>(qTS),
296 Handle<YieldTermStructure>(rTS),
297 Handle<BlackVolTermStructure>(volTS)));
298
299 DoubleBarrierOption opt(value.barrierType, value.barrierlo, value.barrierhi,
300 0, // no rebate
301 payoff, exercise);
302
303 // Ikeda/Kunitomo engine
304 ext::shared_ptr<PricingEngine> engine(
305 new AnalyticDoubleBarrierEngine(stochProcess));
306 opt.setPricingEngine(engine);
307
308 Real calculated = opt.NPV();
309 Real expected = value.result;
310 Real error = std::fabs(x: calculated-expected);
311 if (error > value.tol) {
312 REPORT_FAILURE("Ikeda/Kunitomo value", value.barrierType, value.barrierlo,
313 value.barrierhi, payoff, exercise, value.s, value.q, value.r, today,
314 value.v, expected, calculated, error, value.tol);
315 }
316
317 // Wulin Suo/Yong Wang engine
318 engine = ext::shared_ptr<PricingEngine>(
319 new SuoWangDoubleBarrierEngine(stochProcess));
320 opt.setPricingEngine(engine);
321
322 calculated = opt.NPV();
323 expected = value.result;
324 error = std::fabs(x: calculated-expected);
325 if (error > value.tol) {
326 REPORT_FAILURE("Wulin/Yong value", value.barrierType, value.barrierlo, value.barrierhi,
327 payoff, exercise, value.s, value.q, value.r, today, value.v, expected,
328 calculated, error, value.tol);
329 }
330
331 engine = ext::shared_ptr<PricingEngine>(
332 new BinomialDoubleBarrierEngine<CoxRossRubinstein,
333 DiscretizedDoubleBarrierOption>(stochProcess,
334 300));
335 opt.setPricingEngine(engine);
336 calculated = opt.NPV();
337 expected = value.result;
338 error = std::fabs(x: calculated-expected);
339 double tol = 0.28;
340 if (error>tol) {
341 REPORT_FAILURE("Binomial value", value.barrierType, value.barrierlo, value.barrierhi,
342 payoff, exercise, value.s, value.q, value.r, today, value.v, expected,
343 calculated, error, tol);
344 }
345
346 engine = ext::shared_ptr<PricingEngine>(
347 new BinomialDoubleBarrierEngine<CoxRossRubinstein,
348 DiscretizedDermanKaniDoubleBarrierOption>(
349 stochProcess, 300));
350 opt.setPricingEngine(engine);
351 calculated = opt.NPV();
352 expected = value.result;
353 error = std::fabs(x: calculated-expected);
354 tol = 0.033; // error one order of magnitude lower than plain binomial
355 if (error>tol) {
356 REPORT_FAILURE("Binomial (Derman) value", value.barrierType, value.barrierlo,
357 value.barrierhi, payoff, exercise, value.s, value.q, value.r, today,
358 value.v, expected, calculated, error, tol);
359 }
360
361 if (value.barrierType == DoubleBarrier::KnockOut) {
362 engine = ext::make_shared<FdHestonDoubleBarrierEngine>(
363 args: ext::make_shared<HestonModel>(
364 args: ext::make_shared<HestonProcess>(
365 args: Handle<YieldTermStructure>(rTS),
366 args: Handle<YieldTermStructure>(qTS),
367 args: Handle<Quote>(spot),
368 args: squared(x: vol->value()), args: 1.0,
369 args: squared(x: vol->value()), args: 0.001, args: 0.0)), args: 251, args: 76, args: 3);
370
371 opt.setPricingEngine(engine);
372 calculated = opt.NPV();
373 expected = value.result;
374 error = std::fabs(x: calculated-expected);
375
376 tol = 0.025; // error one order of magnitude lower than plain binomial
377 if (error>tol) {
378 REPORT_FAILURE("Heston value", value.barrierType, value.barrierlo, value.barrierhi,
379 payoff, exercise, value.s, value.q, value.r, today, value.v,
380 expected, calculated, error, tol);
381 }
382 }
383 }
384}
385
386void DoubleBarrierOptionTest::testVannaVolgaDoubleBarrierValues() {
387 BOOST_TEST_MESSAGE(
388 "Testing double-barrier FX options against Vanna/Volga values...");
389
390 using namespace double_barrier_option_test;
391
392 DoubleBarrierFxOptionData values[] = {
393
394 // BarrierType, barr.1, barr.2, rebate, type, strike, s, q, r, t, vol25Put, volAtm,vol25Call, vol, result, tol
395
396 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Call, .strike: 1.13321, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.11638, .result: 0.14413, .tol: 1.0e-4},
397 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Call, .strike: 1.22687, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.10088, .result: 0.07456, .tol: 1.0e-4},
398 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Call, .strike: 1.31179, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08925, .result: 0.02710, .tol: 1.0e-4},
399 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Call, .strike: 1.38843, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08463, .result: 0.00569, .tol: 1.0e-4},
400 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Call, .strike: 1.46047, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08412, .result: 0.00013, .tol: 1.0e-4},
401
402 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Put, .strike: 1.13321, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.11638, .result: 0.00017, .tol: 1.0e-4},
403 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Put, .strike: 1.22687, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.10088, .result: 0.00353, .tol: 1.0e-4},
404 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Put, .strike: 1.31179, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08925, .result: 0.02221, .tol: 1.0e-4},
405 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Put, .strike: 1.38843, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08463, .result: 0.06049, .tol: 1.0e-4},
406 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.1, .barrier2: 1.5, .rebate: 0.0, .type: Option::Put, .strike: 1.46047, .s: 1.30265, .q: 0.0003541, .r: 0.0033871, .t: 1.0, .vol25Put: 0.10087, .volAtm: 0.08925, .vol25Call: 0.08463, .v: 0.08412, .result: 0.11103, .tol: 1.0e-4},
407
408 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Call, .strike: 1.06145, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.12511, .result: 0.19981, .tol: 1.0e-4},
409 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Call, .strike: 1.19545, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.10890, .result: 0.10389, .tol: 1.0e-4},
410 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Call, .strike: 1.32238, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09444, .result: 0.03555, .tol: 1.0e-4},
411 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Call, .strike: 1.44298, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09197, .result: 0.00634, .tol: 1.0e-4},
412 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Call, .strike: 1.56345, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09261, .result: 0.00000, .tol: 1.0e-4},
413
414 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Put, .strike: 1.06145, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.12511, .result: 0.00000, .tol: 1.0e-4},
415 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Put, .strike: 1.19545, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.10890, .result: 0.00436, .tol: 1.0e-4},
416 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Put, .strike: 1.32238, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09444, .result: 0.03173, .tol: 1.0e-4},
417 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Put, .strike: 1.44298, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09197, .result: 0.09346, .tol: 1.0e-4},
418 { .barrierType: DoubleBarrier::KnockOut, .barrier1: 1.0, .barrier2: 1.6, .rebate: 0.0, .type: Option::Put, .strike: 1.56345, .s: 1.30265, .q: 0.0009418, .r: 0.0039788, .t: 2.0, .vol25Put: 0.10891, .volAtm: 0.09525, .vol25Call: 0.09197, .v: 0.09261, .result: 0.17704, .tol: 1.0e-4}
419
420 };
421
422 DayCounter dc = Actual360();
423 Date today(05, Mar, 2013);
424 Settings::instance().evaluationDate() = today;
425
426 ext::shared_ptr<SimpleQuote> spot = ext::make_shared<SimpleQuote>(args: 0.0);
427 ext::shared_ptr<SimpleQuote> qRate = ext::make_shared<SimpleQuote>(args: 0.0);
428 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
429 ext::shared_ptr<SimpleQuote> rRate = ext::make_shared<SimpleQuote>(args: 0.0);
430 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
431 ext::shared_ptr<SimpleQuote> vol25Put = ext::make_shared<SimpleQuote>(args: 0.0);
432 ext::shared_ptr<SimpleQuote> volAtm = ext::make_shared<SimpleQuote>(args: 0.0);
433 ext::shared_ptr<SimpleQuote> vol25Call = ext::make_shared<SimpleQuote>(args: 0.0);
434
435 for (auto& value : values) {
436
437 for (Size j=0; j<=1; j++) {
438
439 auto barrierType = static_cast<DoubleBarrier::Type>(j);
440
441 spot->setValue(value.s);
442 qRate->setValue(value.q);
443 rRate->setValue(value.r);
444 vol25Put->setValue(value.vol25Put);
445 volAtm->setValue(value.volAtm);
446 vol25Call->setValue(value.vol25Call);
447
448 ext::shared_ptr<StrikedTypePayoff> payoff =
449 ext::make_shared<PlainVanillaPayoff>(args&: value.type, args&: value.strike);
450
451 Date exDate = today + timeToDays(t: value.t, daysPerYear: 365);
452 ext::shared_ptr<Exercise> exercise =
453 ext::make_shared<EuropeanExercise>(args&: exDate);
454
455 Handle<DeltaVolQuote> volAtmQuote = Handle<DeltaVolQuote>(
456 ext::make_shared<DeltaVolQuote>(args: Handle<Quote>(volAtm), args: DeltaVolQuote::Fwd, args&: value.t,
457 args: DeltaVolQuote::AtmDeltaNeutral));
458
459 // always delta neutral atm
460 Handle<DeltaVolQuote> vol25PutQuote(
461 Handle<DeltaVolQuote>(ext::make_shared<DeltaVolQuote>(
462 args: -0.25, args: Handle<Quote>(vol25Put), args&: value.t, args: DeltaVolQuote::Fwd)));
463
464 Handle<DeltaVolQuote> vol25CallQuote(
465 Handle<DeltaVolQuote>(ext::make_shared<DeltaVolQuote>(
466 args: 0.25, args: Handle<Quote>(vol25Call), args&: value.t, args: DeltaVolQuote::Fwd)));
467
468 DoubleBarrierOption doubleBarrierOption(barrierType, value.barrier1, value.barrier2,
469 value.rebate, payoff, exercise);
470
471 Real bsVanillaPrice =
472 blackFormula(optionType: value.type, strike: value.strike,
473 forward: spot->value() * qTS->discount(t: value.t) / rTS->discount(t: value.t),
474 stdDev: value.v * sqrt(x: value.t), discount: rTS->discount(t: value.t));
475 ext::shared_ptr<PricingEngine> vannaVolgaEngine =
476 ext::make_shared<VannaVolgaDoubleBarrierEngine<SuoWangDoubleBarrierEngine> >(
477 args&: volAtmQuote,
478 args&: vol25PutQuote,
479 args&: vol25CallQuote,
480 args: Handle<Quote> (spot),
481 args: Handle<YieldTermStructure> (rTS),
482 args: Handle<YieldTermStructure> (qTS),
483 args: true,
484 args&: bsVanillaPrice);
485 doubleBarrierOption.setPricingEngine(vannaVolgaEngine);
486
487 // Expected result for KO is given in array, for KI is evaluated as vanilla - KO
488 Real expected = 0;
489 if (barrierType == DoubleBarrier::KnockOut)
490 expected = value.result;
491 else if (barrierType == DoubleBarrier::KnockIn)
492 expected = (bsVanillaPrice - value.result);
493
494 Real calculated = doubleBarrierOption.NPV();
495 Real error = std::fabs(x: calculated-expected);
496 if (error > value.tol) {
497 REPORT_FAILURE_VANNAVOLGA("value", barrierType, value.barrier1, value.barrier2,
498 value.rebate, payoff, exercise, value.s, value.q, value.r,
499 today, value.vol25Put, value.volAtm, value.vol25Call,
500 value.v, expected, calculated, error, value.tol);
501 }
502
503 vannaVolgaEngine =
504 ext::make_shared<VannaVolgaDoubleBarrierEngine<AnalyticDoubleBarrierEngine> >(
505 args&: volAtmQuote,
506 args&: vol25PutQuote,
507 args&: vol25CallQuote,
508 args: Handle<Quote> (spot),
509 args: Handle<YieldTermStructure> (rTS),
510 args: Handle<YieldTermStructure> (qTS),
511 args: true,
512 args&: bsVanillaPrice);
513 doubleBarrierOption.setPricingEngine(vannaVolgaEngine);
514
515 calculated = doubleBarrierOption.NPV();
516 error = std::fabs(x: calculated-expected);
517 Real maxtol = 5.0e-3; // different engines have somewhat different results
518 if (error>maxtol) {
519 REPORT_FAILURE_VANNAVOLGA("value", barrierType, value.barrier1, value.barrier2,
520 value.rebate, payoff, exercise, value.s, value.q, value.r,
521 today, value.vol25Put, value.volAtm, value.vol25Call,
522 value.v, expected, calculated, error, value.tol);
523 }
524 }
525 }
526}
527
528void DoubleBarrierOptionTest::testMonteCarloDoubleBarrierWithAnalytical() {
529 BOOST_TEST_MESSAGE("Testing MC double-barrier options against analytical values...");
530
531 using namespace double_barrier_option_test;
532
533 Real tolerance = 0.01; //percentage difference between analytical and monte carlo values to be tolerated
534
535 // set up dates
536 Calendar calendar = TARGET();
537 Date todaysDate(15, May, 1998);
538 Date settlementDate(17, May, 1998);
539 Settings::instance().evaluationDate() = todaysDate;
540
541 // our options
542 Option::Type type(Option::Put);
543 Real underlying = 36;
544 Real strike = 40;
545 Spread dividendYield = 0.00;
546 Rate riskFreeRate = 0.06;
547 Volatility volatility = 0.20;
548 Date maturity(17, May, 1999);
549 DayCounter dayCounter = Actual365Fixed();
550
551 std::vector<Date> exerciseDates;
552 for (Integer i=1; i<=4; i++)
553 exerciseDates.push_back(x: settlementDate + 3*i*Months);
554
555 ext::shared_ptr<Exercise> europeanExercise(
556 new EuropeanExercise(maturity));
557
558 Handle<Quote> underlyingH(
559 ext::shared_ptr<Quote>(new SimpleQuote(underlying)));
560
561 // bootstrap the yield/dividend/vol curves
562 Handle<YieldTermStructure> flatTermStructure(
563 ext::shared_ptr<YieldTermStructure>(
564 new FlatForward(settlementDate, riskFreeRate, dayCounter)));
565 Handle<YieldTermStructure> flatDividendTS(
566 ext::shared_ptr<YieldTermStructure>(
567 new FlatForward(settlementDate, dividendYield, dayCounter)));
568 Handle<BlackVolTermStructure> flatVolTS(
569 ext::shared_ptr<BlackVolTermStructure>(
570 new BlackConstantVol(settlementDate, calendar, volatility,
571 dayCounter)));
572 ext::shared_ptr<StrikedTypePayoff> payoff(
573 new PlainVanillaPayoff(type, strike));
574 ext::shared_ptr<BlackScholesMertonProcess> bsmProcess(
575 new BlackScholesMertonProcess(underlyingH, flatDividendTS,
576 flatTermStructure, flatVolTS));
577
578 Real barrierLow = underlying * 0.9;
579 Real barrierHigh = underlying * 1.1;
580
581 DoubleBarrierOption knockIndoubleBarrierOption(DoubleBarrier::KnockIn,
582 barrierLow,
583 barrierHigh,
584 0,
585 payoff,
586 europeanExercise);
587
588 ext::shared_ptr<PricingEngine> analyticdoublebarrierengine(new AnalyticDoubleBarrierEngine(bsmProcess));
589 knockIndoubleBarrierOption.setPricingEngine(analyticdoublebarrierengine);
590 Real analytical = knockIndoubleBarrierOption.NPV();
591
592 ext::shared_ptr<PricingEngine> mcdoublebarrierengine;
593 mcdoublebarrierengine = MakeMCDoubleBarrierEngine<PseudoRandom>(bsmProcess)
594 .withSteps(steps: 5000)
595 .withAntitheticVariate()
596 .withAbsoluteTolerance(tolerance: 0.5)
597 .withSeed(seed: 1);
598 knockIndoubleBarrierOption.setPricingEngine(mcdoublebarrierengine);
599 Real monteCarlo = knockIndoubleBarrierOption.NPV();
600
601 Real percentageDiff = std::abs(x: analytical - monteCarlo) / analytical;
602
603 if (percentageDiff > tolerance){
604 REPORT_FAILURE_DOUBLE_BARRIER_MC(analytical, monteCarlo, percentageDiff);
605 }
606
607 DoubleBarrierOption knockOutDoubleBarrierOption(DoubleBarrier::KnockOut,
608 barrierLow,
609 barrierHigh,
610 0,
611 payoff,
612 europeanExercise);
613
614 knockOutDoubleBarrierOption.setPricingEngine(analyticdoublebarrierengine);
615 analytical = knockOutDoubleBarrierOption.NPV();
616
617 tolerance = 0.01;
618
619 mcdoublebarrierengine = MakeMCDoubleBarrierEngine<PseudoRandom>(bsmProcess)
620 .withSteps(steps: 5000)
621 .withAntitheticVariate()
622 .withAbsoluteTolerance(tolerance)
623 .withSeed(seed: 10);
624 knockOutDoubleBarrierOption.setPricingEngine(mcdoublebarrierengine);
625 monteCarlo = knockOutDoubleBarrierOption.NPV();
626
627 Real diff = std::abs(x: analytical - monteCarlo);
628
629 if (diff > tolerance){
630 REPORT_FAILURE_DOUBLE_BARRIER_MC(analytical, monteCarlo, diff);
631 }
632
633}
634
635test_suite* DoubleBarrierOptionTest::suite(SpeedLevel) {
636 auto* suite = BOOST_TEST_SUITE("DoubleBarrier");
637 suite->add(QUANTLIB_TEST_CASE(&DoubleBarrierOptionTest::testEuropeanHaugValues));
638
639 return suite;
640}
641
642test_suite* DoubleBarrierOptionTest::experimental(SpeedLevel speed) {
643 auto* suite = BOOST_TEST_SUITE("DoubleBarrier_experimental");
644 suite->add(QUANTLIB_TEST_CASE(&DoubleBarrierOptionTest::testVannaVolgaDoubleBarrierValues));
645
646 if (speed <= Fast) {
647 suite->add(QUANTLIB_TEST_CASE(&DoubleBarrierOptionTest::testMonteCarloDoubleBarrierWithAnalytical));
648 }
649
650 return suite;
651}
652

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