1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4Copyright (C) 2015 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 "doublebinaryoption.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/quotes/simplequote.hpp>
26#include <ql/instruments/doublebarrieroption.hpp>
27#include <ql/models/equity/hestonmodel.hpp>
28#include <ql/pricingengines/barrier/analyticdoublebarrierbinaryengine.hpp>
29#include <ql/pricingengines/barrier/fdhestondoublebarrierengine.hpp>
30#include <ql/experimental/barrieroption/binomialdoublebarrierengine.hpp>
31#include <ql/utilities/dataformatters.hpp>
32
33using namespace QuantLib;
34using namespace boost::unit_test_framework;
35
36#undef REPORT_FAILURE
37#define REPORT_FAILURE(greekName, payoff, exercise, barrierType, barrier_lo, \
38 barrier_hi, s, q, r, today, v, expected, calculated, \
39 error, tolerance) \
40 BOOST_ERROR(payoff->optionType() << " option with " \
41 << barrierType << " barrier type:\n" \
42 << " barrier_lo: " << barrier_lo << "\n" \
43 << " barrier_hi: " << barrier_hi << "\n" \
44 << payoffTypeToString(payoff) << " payoff:\n" \
45 << " spot value: " << s << "\n" \
46 << " strike: " << payoff->strike() << "\n" \
47 << " dividend yield: " << io::rate(q) << "\n" \
48 << " risk-free rate: " << io::rate(r) << "\n" \
49 << " reference date: " << today << "\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 << "\n");
56
57namespace {
58
59 struct DoubleBinaryOptionData {
60 DoubleBarrier::Type barrierType;
61 Real barrier_lo;
62 Real barrier_hi;
63 Real cash; // cash payoff for cash-or-nothing
64 Real s; // spot
65 Rate q; // dividend
66 Rate r; // risk-free rate
67 Time t; // time to maturity
68 Volatility v; // volatility
69 Real result; // expected result
70 Real tol; // tolerance
71 };
72}
73
74
75void DoubleBinaryOptionTest::testHaugValues() {
76
77 BOOST_TEST_MESSAGE("Testing cash-or-nothing double barrier options against Haug's values...");
78
79 DoubleBinaryOptionData values[] = {
80 /* The data below are from
81 "Option pricing formulas 2nd Ed.", E.G. Haug, McGraw-Hill 2007 pag. 181
82 Note: book uses cost of carry b, instead of dividend rate q
83 */
84 // barrierType, bar_lo, bar_hi, cash, spot, q, r, t, vol, value, tol
85 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 9.8716, .tol: 1e-4 },
86 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 8.9307, .tol: 1e-4 },
87 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 6.3272, .tol: 1e-4 },
88 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 1.9094, .tol: 1e-4 },
89
90 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 9.7961, .tol: 1e-4 },
91 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 7.2300, .tol: 1e-4 },
92 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 3.7100, .tol: 1e-4 },
93 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 0.4271, .tol: 1e-4 },
94
95 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 8.9054, .tol: 1e-4 },
96 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 3.6752, .tol: 1e-4 },
97 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 0.7960, .tol: 1e-4 },
98 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 0.0059, .tol: 1e-4 },
99
100 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 3.6323, .tol: 1e-4 },
101 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 0.0911, .tol: 1e-4 },
102 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 0.0002, .tol: 1e-4 },
103 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 0.0000, .tol: 1e-4 },
104
105 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0000, .tol: 1e-4 },
106 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 0.2402, .tol: 1e-4 },
107 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 1.4076, .tol: 1e-4 },
108 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 3.8160, .tol: 1e-4 },
109
110 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0075, .tol: 1e-4 },
111 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 0.9910, .tol: 1e-4 },
112 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 2.8098, .tol: 1e-4 },
113 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 4.6612, .tol: 1e-4 },
114
115 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.2656, .tol: 1e-4 },
116 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 2.7954, .tol: 1e-4 },
117 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 4.4024, .tol: 1e-4 },
118 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 4.9266, .tol: 1e-4 },
119
120 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 2.6285, .tol: 1e-4 },
121 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 4.7523, .tol: 1e-4 },
122 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 4.9096, .tol: 1e-4 },
123 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 4.9675, .tol: 1e-4 },
124
125 // following values calculated with haug's VBA code
126 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0042, .tol: 1e-4 },
127 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 0.9450, .tol: 1e-4 },
128 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 3.5486, .tol: 1e-4 },
129 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 7.9663, .tol: 1e-4 },
130
131 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0797, .tol: 1e-4 },
132 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 2.6458, .tol: 1e-4 },
133 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 6.1658, .tol: 1e-4 },
134 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 9.4486, .tol: 1e-4 },
135
136 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.9704, .tol: 1e-4 },
137 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 6.2006, .tol: 1e-4 },
138 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 9.0798, .tol: 1e-4 },
139 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 9.8699, .tol: 1e-4 },
140
141 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 6.2434, .tol: 1e-4 },
142 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 9.7847, .tol: 1e-4 },
143 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 9.8756, .tol: 1e-4 },
144 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 9.8758, .tol: 1e-4 },
145
146 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0041, .tol: 1e-4 },
147 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 0.7080, .tol: 1e-4 },
148 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 2.1581, .tol: 1e-4 },
149 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 80.00, .barrier_hi: 120.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 4.2061, .tol: 1e-4 },
150
151 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0723, .tol: 1e-4 },
152 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 1.6663, .tol: 1e-4 },
153 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 3.3930, .tol: 1e-4 },
154 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 85.00, .barrier_hi: 115.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 4.8679, .tol: 1e-4 },
155
156 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.7080, .tol: 1e-4 },
157 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 3.4424, .tol: 1e-4 },
158 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 4.7496, .tol: 1e-4 },
159 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 90.00, .barrier_hi: 110.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 5.0475, .tol: 1e-4 },
160
161 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 3.6524, .tol: 1e-4 },
162 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.20, .result: 5.1256, .tol: 1e-4 },
163 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.30, .result: 5.0763, .tol: 1e-4 },
164 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 100.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.50, .result: 5.0275, .tol: 1e-4 },
165
166 // degenerate cases
167 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 80.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0000, .tol: 1e-4 },
168 { .barrierType: DoubleBarrier::KnockOut, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 110.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0000, .tol: 1e-4 },
169 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 80.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 10.0000, .tol: 1e-4 },
170 { .barrierType: DoubleBarrier::KnockIn, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 110.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 10.0000, .tol: 1e-4 },
171 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 80.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 10.0000, .tol: 1e-4 },
172 { .barrierType: DoubleBarrier::KIKO, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 110.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0000, .tol: 1e-4 },
173 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 80.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 0.0000, .tol: 1e-4 },
174 { .barrierType: DoubleBarrier::KOKI, .barrier_lo: 95.00, .barrier_hi: 105.00, .cash: 10.00, .s: 110.00, .q: 0.02, .r: 0.05, .t: 0.25, .v: 0.10, .result: 10.0000, .tol: 1e-4 },
175 };
176
177 DayCounter dc = Actual360();
178 Date today = Date::todaysDate();
179
180 ext::shared_ptr<SimpleQuote> spot(new SimpleQuote(100.0));
181 ext::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.04));
182 ext::shared_ptr<YieldTermStructure> qTS = flatRate(today, forward: qRate, dc);
183 ext::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.01));
184 ext::shared_ptr<YieldTermStructure> rTS = flatRate(today, forward: rRate, dc);
185 ext::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.25));
186 ext::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, volatility: vol, dc);
187
188 for (auto& value : values) {
189
190 ext::shared_ptr<StrikedTypePayoff> payoff(
191 new CashOrNothingPayoff(Option::Call, 0, value.cash));
192
193 Date exDate = today + timeToDays(t: value.t);
194 ext::shared_ptr<Exercise> exercise;
195 if (value.barrierType == DoubleBarrier::KIKO || value.barrierType == DoubleBarrier::KOKI)
196 exercise.reset(p: new AmericanExercise(today, exDate));
197 else
198 exercise.reset(p: new EuropeanExercise(exDate));
199
200 spot->setValue(value.s);
201 qRate->setValue(value.q);
202 rRate->setValue(value.r);
203 vol->setValue(value.v);
204
205 ext::shared_ptr<BlackScholesMertonProcess> stochProcess(new
206 BlackScholesMertonProcess(Handle<Quote>(spot),
207 Handle<YieldTermStructure>(qTS),
208 Handle<YieldTermStructure>(rTS),
209 Handle<BlackVolTermStructure>(volTS)));
210
211 // checking with analytic engine
212 ext::shared_ptr<PricingEngine> engine(
213 new AnalyticDoubleBarrierBinaryEngine(stochProcess));
214 DoubleBarrierOption opt(value.barrierType, value.barrier_lo, value.barrier_hi, 0, payoff,
215 exercise);
216 opt.setPricingEngine(engine);
217
218 Real calculated = opt.NPV();
219 Real expected = value.result;
220 Real error = std::fabs(x: calculated-expected);
221 if (error > value.tol) {
222 REPORT_FAILURE("value", payoff, exercise, value.barrierType, value.barrier_lo,
223 value.barrier_hi, value.s, value.q, value.r, today, value.v,
224 value.result, calculated, error, value.tol);
225 }
226
227 Size steps = 500;
228 // checking with binomial engine
229 engine = ext::shared_ptr<PricingEngine>(
230 new BinomialDoubleBarrierEngine<CoxRossRubinstein,
231 DiscretizedDoubleBarrierOption>(stochProcess,
232 steps));
233 opt.setPricingEngine(engine);
234 calculated = opt.NPV();
235 expected = value.result;
236 error = std::fabs(x: calculated-expected);
237 double tol = 0.22;
238 if (error>tol) {
239 REPORT_FAILURE("Binomial value", payoff, exercise, value.barrierType, value.barrier_lo,
240 value.barrier_hi, value.s, value.q, value.r, today, value.v,
241 value.result, calculated, error, tol);
242 }
243 }
244}
245
246void DoubleBinaryOptionTest::testPdeDoubleBarrierWithAnalytical() {
247 BOOST_TEST_MESSAGE("Testing cash-or-nothing double barrier options "
248 "against PDE Heston version...");
249
250 const DayCounter dc = Actual360();
251 const Date todaysDate(30, Jan, 2023);
252 const Date maturityDate = todaysDate + Period(1, Years);
253 Settings::instance().evaluationDate() = todaysDate;
254
255 const Handle<Quote> spot(ext::make_shared<SimpleQuote>(args: 100));
256 const Rate r = 0.075;
257 const Rate q = 0.03;
258 const Volatility vol = 0.4;
259
260 const Real kappa = 1.0;
261 const Real theta = vol*vol;
262 const Real rho = 0.0;
263 const Real sigma = 1e-4;
264 const Real v0 = theta;
265
266 const Handle<YieldTermStructure> rTS(flatRate(forward: r, dc));
267 const Handle<YieldTermStructure> qTS(flatRate(forward: q, dc));
268
269 const ext::shared_ptr<HestonModel> hestonModel =
270 ext::make_shared<HestonModel>(
271 args: ext::make_shared<HestonProcess>(
272 args: rTS, args: qTS, args: spot, args: v0, args: kappa, args: theta, args: sigma, args: rho));
273
274 const ext::shared_ptr<BlackScholesMertonProcess> bsProcess =
275 ext::make_shared<BlackScholesMertonProcess>(
276 args: Handle<Quote>(spot),
277 args: Handle<YieldTermStructure>(qTS),
278 args: Handle<YieldTermStructure>(rTS),
279 args: Handle<BlackVolTermStructure>(flatVol(today: todaysDate, volatility: vol, dc)));
280
281 const ext::shared_ptr<PricingEngine> analyticEngine =
282 ext::make_shared<AnalyticDoubleBarrierBinaryEngine>(args: bsProcess);
283
284 const ext::shared_ptr<PricingEngine> fdEngine =
285 ext::make_shared<FdHestonDoubleBarrierEngine>(
286 args: hestonModel, args: 201, args: 101, args: 3, args: 0,
287 args: FdmSchemeDesc::Hundsdorfer());
288
289 const ext::shared_ptr<Exercise> europeanExercise(
290 ext::make_shared<EuropeanExercise>(args: maturityDate));
291
292 const Real tol = 5e-3;
293 for (Size i=5; i < 18; i+=2) {
294 const Real dist = 10.0+5.0*i;
295
296 const Real barrier_lo = std::max(a: spot->value() - dist, b: 1e-2);
297 const Real barrier_hi = spot->value() + dist;
298 DoubleBarrierOption doubleBarrier(
299 DoubleBarrier::KnockOut, barrier_lo, barrier_hi, 0.0,
300 ext::make_shared<CashOrNothingPayoff>(
301 args: Option::Call, args: 0.0, args: 1.0),
302 europeanExercise);
303
304 doubleBarrier.setPricingEngine(analyticEngine);
305 const Real bsNPV = doubleBarrier.NPV();
306
307 doubleBarrier.setPricingEngine(fdEngine);
308 const Real slvNPV = doubleBarrier.NPV();
309
310 const Real diff = slvNPV - bsNPV;
311 if (std::fabs(x: diff) > tol) {
312 BOOST_ERROR(
313 "Failed to reproduce price difference for a Double-No-Touch "
314 << "option between Black-Scholes and "
315 << "Heston Stochastic Local Volatility model"
316 << "\n Barrier Low : " << barrier_lo
317 << "\n Barrier High : " << barrier_hi
318 << "\n Black-Scholes Price: " << bsNPV
319 << "\n Heston SLV Price : " << slvNPV
320 << "\n diff : " << diff
321 << "\n tolerance : " << tol);
322 }
323 }
324}
325
326
327test_suite* DoubleBinaryOptionTest::suite() {
328 auto* suite = BOOST_TEST_SUITE("DoubleBinary");
329 suite->add(QUANTLIB_TEST_CASE(&DoubleBinaryOptionTest::testHaugValues));
330 suite->add(QUANTLIB_TEST_CASE(&DoubleBinaryOptionTest::testPdeDoubleBarrierWithAnalytical));
331 return suite;
332}
333

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