20#include <boost/make_shared.hpp>
21#include <boost/test/unit_test.hpp>
22#include <ql/time/calendars/target.hpp>
23#include <ql/time/daycounters/actualactual.hpp>
26using namespace boost::unit_test_framework;
37 BOOST_TEST_MESSAGE(
"Testing QuantExt::BlackVarianceSurfaceSparse with market data...");
45 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
46 Date today = Settings::instance().evaluationDate();
51 vector<Time> all_times({ 0.025, 0.101, 0.197, 0.274, 0.523, 0.772, 1.769, 2.267, 2.784, 3.781, 4.778, 5.774 });
56 vector<vector<Real> > volData(
57 { { 51.31, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 33.66, 32.91 },
58 { 58.64, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 31.78, 31.29, 30.08 },
59 { 65.97, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 30.19, 29.76, 29.75 },
60 { 73.30, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 28.63, 28.48, 28.48 },
61 { 76.97, 00.00, 00.00, 00.00, 32.62, 30.79, 30.01, 28.43 },
62 { 80.63, 00.00, 00.00, 00.00, 30.58, 29.36, 28.76, 27.53, 27.13, 27.11, 27.11, 27.22, 28.09 },
63 { 84.30, 00.00, 00.00, 00.00, 28.87, 27.98, 27.50, 26.66 },
65 { 87.96, 32.16, 29.06, 27.64, 27.17, 26.63, 26.37, 25.75, 25.55, 25.80, 25.85, 26.11, 26.93 },
66 { 89.79, 30.43, 27.97, 26.72 },
67 { 91.63, 28.80, 26.90, 25.78, 25.57, 25.31, 25.19, 24.97 },
68 { 93.46, 27.24, 25.90, 24.89 },
69 { 95.29, 25.86, 24.88, 24.05, 24.07, 24.04, 24.11, 24.18, 24.10, 24.48, 24.69, 25.01, 25.84 },
70 { 97.12, 24.66, 23.90, 23.29 },
71 { 98.96, 23.58, 23.00, 22.53, 22.69, 22.84, 22.99, 23.47 },
72 { 100.79, 22.47, 22.13, 21.84 },
73 { 102.62, 21.59, 21.40, 21.23, 21.42, 21.73, 21.98, 22.83, 22.75, 23.22, 23.84, 23.92, 24.86 },
74 { 104.45, 20.91, 20.76, 20.69 },
75 { 106.29, 20.56, 20.24, 20.25, 20.39, 20.74, 21.04, 22.13 },
76 { 108.12, 20.45, 19.82, 19.84 },
77 { 109.95, 20.25, 19.59, 19.44, 19.62, 19.88, 20.22, 21.51, 21.61, 22.19, 22.69, 23.05, 23.99 },
78 { 111.78, 19.33, 19.29, 19.20 },
79 { 113.62, 00.00, 00.00, 00.00, 19.02, 19.14, 19.50, 20.91 },
80 { 117.28, 00.00, 00.00, 00.00, 18.85, 18.54, 18.88, 20.39, 20.58, 21.22, 21.86, 22.23, 23.21 },
81 { 120.95, 00.00, 00.00, 00.00, 18.67, 18.11, 18.39, 19.90 },
82 { 124.61, 00.00, 00.00, 00.00, 18.71, 17.85, 17.93, 19.45, 00.00, 20.54, 21.03, 21.64, 22.51 },
83 { 131.94, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 19.88, 20.54, 21.05, 21.90 },
84 { 139.27, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 19.30, 20.02, 20.54, 21.35 },
85 { 146.60, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 00.00, 18.49, 19.64, 20.12 } });
90 vector<Volatility> vols;
93 for (
auto vd : volData) {
94 Real strike = spot * vd[0];
95 for (Size i = 1; i < vd.size(); i++) {
96 Volatility vol = vd[i] / 100.0;
98 Date d = today + int(all_times[i - 1] * 365);
107 Calendar cal = TARGET();
108 DayCounter dc = ActualActual(ActualActual::ISDA);
110 auto surface = QuantLib::ext::make_shared<QuantExt::BlackVarianceSurfaceSparse>(today, cal, dates,
strikes, vols, dc);
113 for (
auto vd : volData) {
114 Real strike = spot * vd[0];
115 for (Size i = 1; i < vd.size(); i++) {
116 Volatility expectedVol = vd[i] / 100.0;
117 if (expectedVol > 0.0001) {
118 Date d = today + int(all_times[i - 1] * 365);
119 Volatility vol = surface->blackVol(d, strike);
120 BOOST_CHECK_CLOSE(vol, expectedVol, 1e-12);
126 vector<Real> all_strikes;
127 vector<Date> all_dates;
128 for (
auto vd : volData)
129 all_strikes.push_back(spot * vd[0]);
130 for (
auto t : all_times)
131 all_dates.push_back(today +
int(t * 365));
133 for (
auto strike : all_strikes) {
134 for (
auto d : all_dates)
135 BOOST_CHECK(surface->blackVol(d, strike) > 0.0001);
136 for (
auto t : all_times)
137 BOOST_CHECK(surface->blackVol(t, strike) > 0.0001);
143 BOOST_TEST_MESSAGE(
"Testing QuantExt::BlackVarianceSurfaceSparse with constant vol data...");
145 SavedSettings backup;
147 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
148 Date today = Settings::instance().evaluationDate();
153 vector<Date> dates = { Date(1, Mar, 2011), Date(1, Mar, 2011), Date(1, Mar, 2012), Date(1, Mar, 2012),
154 Date(1, Mar, 2013) };
155 vector<Real>
strikes = { 2000, 3000, 2500, 3500, 3000 };
156 vector<Volatility> vols(
strikes.size(), 0.1);
158 Calendar cal = TARGET();
159 DayCounter dc = ActualActual(ActualActual::ISDA);
164 for (Time t = 0.2; t < 20; t += 0.2) {
165 for (Real strike = 1500; strike < 6000; strike += 100) {
166 BOOST_CHECK_CLOSE(surface.blackVol(t, strike), 0.1, 1e-12);
172 BOOST_TEST_MESSAGE(
"Testing QuantExt::BlackVarianceSurfaceSparse with erroneous inputs");
174 SavedSettings backup;
176 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
177 Date today = Settings::instance().evaluationDate();
181 vector<Date> dates = { Date(1, Mar, 2011), Date(1, Mar, 2011), Date(1, Mar, 2012), Date(1, Mar, 2012) };
182 vector<Real>
strikes = { 2000, 3000, 2500, 3500, 3000 };
183 vector<Volatility> vols(
strikes.size(), 0.1);
185 Calendar cal = TARGET();
186 DayCounter dc = ActualActual(ActualActual::ISDA);
193 BOOST_TEST_MESSAGE(
"Testing edge cases");
195 SavedSettings backup;
197 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
198 Date today = Settings::instance().evaluationDate();
203 vector<Date> dates = { Date(1, Mar, 2011), Date(1, Mar, 2011), Date(1, Mar, 2012), Date(1, Mar, 2012),
204 Date(1, Mar, 2013) };
205 vector<Real>
strikes = { 2000, 3000, 2500, 3500, 3000 };
206 vector<Volatility> vols(
strikes.size(), 0.1);
208 Calendar cal = TARGET();
209 DayCounter dc = ActualActual(ActualActual::ISDA);
213 Real strike2 =
strikes.front();
214 Real strike3 = 1500.0;
217 Real strikeNeg = -1000;
220 BOOST_CHECK_CLOSE(surface.blackVol(t, strike1), 0.1, 1e-12);
221 BOOST_CHECK_CLOSE(surface.blackVol(t, strike2), 0.1, 1e-12);
222 BOOST_CHECK_CLOSE(surface.blackVol(t, strike3), 0.1, 1e-12);
223 BOOST_CHECK_CLOSE(surface.blackVol(t, strike4), 0.1, 1e-12);
226 Time t1 = surface.timeFromReference(Date(1, Mar, 2014));
227 Real expectedVol = 0.1;
228 BOOST_CHECK_CLOSE(surface.blackVol(t1, strike2), expectedVol, 1e-12);
231 BOOST_CHECK_THROW(surface.blackVol(t, strikeNeg), QuantLib::Error);
235 BOOST_TEST_MESSAGE(
"Testing surface from single point");
238 SavedSettings backup;
240 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
241 Date today = Settings::instance().evaluationDate();
245 vector<Date> dates = { Date(1, Mar, 2011) };
246 vector<Real>
strikes = { 2500 };
247 vector<Volatility> vols(
strikes.size(), 0.1);
249 Calendar cal = TARGET();
250 DayCounter dc = ActualActual(ActualActual::ISDA);
255 for (Time t = 0.0; t < 20; t += 0.2) {
256 for (Real strike = 1500; strike < 6000; strike += 100) {
257 BOOST_CHECK_CLOSE(surface.blackVol(t, strike), 0.1, 1e-12);
264 BOOST_TEST_MESSAGE(
"Testing QuantExt::BlackVarianceSurfaceSparse axis interpolations");
266 SavedSettings backup;
268 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
269 Date today = Settings::instance().evaluationDate();
274 vector<Date> dates = { Date(1, Mar, 2011), Date(1, Mar, 2011), Date(1, Mar, 2012), Date(1, Mar, 2012) };
275 vector<Real>
strikes = { 2000, 3000, 2000, 3000 };
276 vector<Volatility> vols = { 0.105, 0.12, 0.17, 0.15 };
278 Calendar cal = TARGET();
279 DayCounter dc = ActualActual(ActualActual::ISDA);
281 auto surface = QuantLib::ext::make_shared<QuantExt::BlackVarianceSurfaceSparse>(today, cal, dates,
strikes, vols, dc);
284 auto t1 = surface->timeFromReference(Date(1, Mar, 2011));
285 auto t2 = surface->timeFromReference(Date(1, Sep, 2011));
286 auto t3 = surface->timeFromReference(Date(1, Mar, 2012));
292 Volatility e1 = 0.151634737915710;
293 Volatility e2 = 0.112749722837797;
294 Volatility e3 = 0.146315408895419;
295 Volatility e4 = 0.160312195418814;
296 Volatility e5 = 0.140795255664746;
299 BOOST_CHECK_CLOSE(e1, surface->blackVol(t2, s1), 1e-12);
300 BOOST_CHECK_CLOSE(e2, surface->blackVol(t1, s2), 1e-12);
301 BOOST_CHECK_CLOSE(e3, surface->blackVol(t2, s2), 1e-12);
302 BOOST_CHECK_CLOSE(e4, surface->blackVol(t3, s2), 1e-12);
303 BOOST_CHECK_CLOSE(e5, surface->blackVol(t2, s3), 1e-12);
308 BOOST_TEST_MESSAGE(
"Testing QuantExt::BlackVarianceSurfaceSparse flat extrapolation");
310 SavedSettings backup;
312 Settings::instance().evaluationDate() = Date(1, Mar, 2010);
313 Date today = Settings::instance().evaluationDate();
317 vector<Date> dates = { Date(1, Mar, 2011), Date(1, Mar, 2011), Date(1, Mar, 2012), Date(1, Mar, 2012) };
318 vector<Real>
strikes = { 2000, 3000, 2000, 3000 };
319 vector<Volatility> vols = { 0.105, 0.12, 0.17, 0.15 };
321 Calendar cal = TARGET();
322 DayCounter dc = ActualActual(ActualActual::ISDA);
324 auto surface = QuantLib::ext::make_shared<QuantExt::BlackVarianceSurfaceSparse>(today, cal, dates,
strikes, vols, dc,
true,
331 Time tb = surface->times().back();
332 Real edgeVol1 = surface->blackVol(tb, s1);
333 Real edgeVol2 = surface->blackVol(tb, s2);
334 Real edgeVol3 = surface->blackVol(tb, s3);
336 Real edgeVar1 = surface->blackVariance(tb, s1);
337 Real edgeVar2 = surface->blackVariance(tb, s2);
338 Real edgeVar3 = surface->blackVariance(tb, s3);
340 for (Real t = 1; t < 10; t++) {
341 BOOST_CHECK_CLOSE(edgeVol1, surface->blackVol(tb + t, s1), 1e-12);
342 BOOST_CHECK_CLOSE(edgeVol2, surface->blackVol(tb + t, s2), 1e-12);
343 BOOST_CHECK_CLOSE(edgeVol3, surface->blackVol(tb + t, s3), 1e-12);
345 BOOST_CHECK_CLOSE(edgeVar1, surface->blackVariance(tb + t, s1) * tb / (tb + t), 1e-12);
346 BOOST_CHECK_CLOSE(edgeVar2, surface->blackVariance(tb + t, s2) * tb / (tb + t), 1e-12);
347 BOOST_CHECK_CLOSE(edgeVar3, surface->blackVariance(tb + t, s3) * tb / (tb + t), 1e-12);
351BOOST_AUTO_TEST_SUITE_END()
353BOOST_AUTO_TEST_SUITE_END()
Black volatility surface modeled as variance surface.
BOOST_AUTO_TEST_CASE(testBlackVarianceSurface)
Fixture that can be used at top level.