Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fxvolsmile.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19/*! \file fxvolsmile.cpp
20 \brief fx vol smile
21*/
22
23#include "toplevelfixture.hpp"
24#include <boost/make_shared.hpp>
25#include <boost/test/unit_test.hpp>
26#include <ql/math/matrix.hpp>
27#include <ql/quotes/simplequote.hpp>
28#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
29#include <ql/termstructures/yield/discountcurve.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
31#include <ql/time/calendars/target.hpp>
32#include <ql/time/daycounters/actualactual.hpp>
33#include <ql/utilities/dataparsers.hpp>
37
38using namespace QuantLib;
39using namespace QuantExt;
40using namespace boost::unit_test_framework;
41using namespace std;
42
43namespace {
44
45struct CommonVars {
46
47 /* ------ GLOBAL VARIABLES ------ */
48 Date today;
49 DayCounter dc;
50 vector<Date> dates;
51 vector<Real> strikes;
52 Matrix vols;
53 vector<Real> atmVols;
54
55 vector<Volatility> rrs;
56 vector<Volatility> bfs;
57
58 Handle<Quote> baseSpot;
59 Handle<YieldTermStructure> baseDomesticYield;
60 Handle<YieldTermStructure> baseForeignYield;
61
62 CommonVars() {
63
64 today = Date(1, Jan, 2014);
65 dc = ActualActual(ActualActual::ISDA);
66
67 Settings::instance().evaluationDate() = today;
68
69 dates.push_back(Date(1, Feb, 2014));
70 dates.push_back(Date(1, Mar, 2014));
71 dates.push_back(Date(1, Apr, 2014));
72 dates.push_back(Date(1, Jan, 2015));
73
74 strikes.push_back(90);
75 strikes.push_back(100);
76 strikes.push_back(110);
77
78 vols = Matrix(3, 4);
79 vols[0][0] = 0.12;
80 vols[1][0] = 0.10;
81 vols[2][0] = 0.13;
82 vols[0][1] = 0.22;
83 vols[1][1] = 0.20;
84 vols[2][1] = 0.23;
85 vols[0][2] = 0.32;
86 vols[1][2] = 0.30;
87 vols[2][2] = 0.33;
88 vols[0][3] = 0.42;
89 vols[1][3] = 0.40;
90 vols[2][3] = 0.43;
91
92 atmVols.push_back(0.1);
93 atmVols.push_back(0.2);
94 atmVols.push_back(0.3);
95 atmVols.push_back(0.4);
96
97 rrs = vector<Volatility>(atmVols.size(), 0.01);
98 bfs = vector<Volatility>(atmVols.size(), 0.001);
99
100 baseSpot = Handle<Quote>(QuantLib::ext::shared_ptr<Quote>(new SimpleQuote(100)));
101
102 baseDomesticYield = Handle<YieldTermStructure>(
103 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), dc));
104 baseForeignYield = Handle<YieldTermStructure>(
105 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.01)), dc));
106 }
107};
108
109struct VolData {
110 const char* tenor;
111 Volatility atm;
112 Volatility rr;
113 Volatility bf;
114 Time time;
115 Real df_d;
116 Real df_f;
117};
118
119} // namespace
120
121BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
122
123BOOST_AUTO_TEST_SUITE(FxVolSmileTest)
124
125BOOST_AUTO_TEST_CASE(testVannaVolgaFxSmileSection) {
126
127 BOOST_TEST_MESSAGE("Testing fx vanna volga smile");
128
129 SavedSettings backup;
130
131 // test numbers from Castagna & Mercurio (2006)
132 // http://papers.ssrn.com/sol3/papers.cfm?abstract_id=873788
133 // page 5
134 Settings::instance().evaluationDate() = Date(1, July, 2005);
135 Time t = 94 / (double)365;
136 Real S0 = 1.205;
137 Volatility sig_atm = 0.0905;
138 Volatility sig_rr = -0.005;
139 Volatility sig_bf = 0.0013;
140 // page 11
141 DiscountFactor df_usd = 0.9902752;
142 DiscountFactor df_eur = 0.9945049;
143
144 // Rates
145 Real rd = -::log(df_usd) / t;
146 Real rf = -::log(df_eur) / t;
147
148 VannaVolgaSmileSection vvss(S0, rd, rf, t, sig_atm, sig_rr, sig_bf);
149
150 // Check the Strike and Vol values from the paper
151 Real tolerance = 0.0001; // 4 decimal places
152 if (fabs(vvss.k_atm() - 1.2114) > tolerance)
153 BOOST_FAIL("VannaVolgaSmileSection failed to calculte ATM strike, got " << vvss.k_atm());
154 if (fabs(vvss.k_p() - 1.1733) > tolerance)
155 BOOST_FAIL("VannaVolgaSmileSection failed to calculate 25P strike, got " << vvss.k_p());
156 if (fabs(vvss.k_c() - 1.2487) > tolerance)
157 BOOST_FAIL("VannaVolgaSmileSection failed to calculate 25C strike, got " << vvss.k_c());
158 if (fabs(vvss.vol_atm() - 0.0905) > tolerance)
159 BOOST_FAIL("VannaVolgaSmileSection failed to calculate ATM vol, got " << vvss.vol_atm());
160 if (fabs(vvss.vol_p() - 0.0943) > tolerance)
161 BOOST_FAIL("VannaVolgaSmileSection failed to calculate 25P vol, got " << vvss.vol_p());
162 if (fabs(vvss.vol_c() - 0.0893) > tolerance)
163 BOOST_FAIL("VannaVolgaSmileSection failed to calculate 25C vol, got " << vvss.vol_c());
164
165 // Now check that our smile returns these
166 if (fabs(vvss.volatility(vvss.k_atm()) - vvss.vol_atm()) > tolerance)
167 BOOST_FAIL("VannaVolgaSmileSection failed to recover ATM vol, got " << vvss.volatility(vvss.k_atm()));
168 if (fabs(vvss.volatility(vvss.k_p()) - vvss.vol_p()) > tolerance)
169 BOOST_FAIL("VannaVolgaSmileSection failed to recover 25P vol, got " << vvss.volatility(vvss.k_p()));
170 if (fabs(vvss.volatility(vvss.k_c()) - vvss.vol_c()) > tolerance)
171 BOOST_FAIL("VannaVolgaSmileSection failed to recover 25C vol, got " << vvss.volatility(vvss.k_c()));
172
173 // To graph the smile, uncomment this code
174 /*
175 cout << "strike,vol" << endl;
176 //for (Real k = 1.1; k < 1.35; k += 0.002) // normal (as per paper)
177 //for (Real k = 0.9; k < 1.5; k += 0.01) // large
178 for (Real k = 0.1; k < 3; k += 0.05) // extreme
179 cout << k << "," << vvss.volatility(k) << endl;
180 */
181}
182
183BOOST_AUTO_TEST_CASE(testVannaVolgaFxVolSurface) {
184
185 BOOST_TEST_MESSAGE("Testing fx vanna volga surface");
186
187 SavedSettings backup;
188
189 // Data from
190 // "Consistent pricing and hedging of an FX options book" (2005)
191 // L. Bisesti, A. Castagna and F. Mercurio
192 // http://www.fabiomercurio.it/fxbook.pdf
193 Date asof(12, Feb, 2004);
194 Settings::instance().evaluationDate() = asof;
195
196 Handle<Quote> fxSpot(QuantLib::ext::shared_ptr<Quote>(new SimpleQuote(1.2832)));
197
198 // vols are % here
199 // tenor, atm, rr, bf, T, p_d, p_f
200 VolData volData[] = { { "1W", 11.75, 0.50, 0.190, 0.0192, 0.999804, 0.999606 },
201 { "2W", 11.60, 0.50, 0.190, 0.0384, 0.999595, 0.999208 },
202 { "1M", 11.50, 0.60, 0.190, 0.0877, 0.999044, 0.998179 },
203 { "2M", 11.25, 0.60, 0.210, 0.1726, 0.998083, 0.996404 },
204 { "3M", 11.00, 0.60, 0.220, 0.2493, 0.997187, 0.994803 },
205 { "6M", 10.87, 0.65, 0.235, 0.5014, 0.993959, 0.989548 },
206 { "9M", 10.83, 0.69, 0.235, 0.7589, 0.990101, 0.984040 },
207 { "1Y", 10.80, 0.70, 0.240, 1.0110, 0.985469, 0.978479 },
208 { "2Y", 10.70, 0.65, 0.255, 2.0110, 0.960102, 0.951092 } };
209
210 // Assume act/act
211 DayCounter dc = ActualActual(ActualActual::ISDA);
212 Calendar cal = TARGET();
213
214 // set up vectors
215 Size len = sizeof(volData) / sizeof(volData[0]);
216 vector<Date> dates(len);
217 vector<Volatility> atm(len);
218 vector<Volatility> rr(len);
219 vector<Volatility> bf(len);
220 // For DiscountCurve we need the T=0 points.
221 vector<Date> discountDates(len + 1);
222 vector<DiscountFactor> dfDom(len + 1);
223 vector<DiscountFactor> dfFor(len + 1);
224 discountDates[0] = asof;
225 dfDom[0] = 1.0;
226 dfFor[0] = 1.0;
227
228 for (Size i = 0; i < sizeof(volData) / sizeof(volData[0]); i++) {
229 dates[i] = asof + PeriodParser::parse(volData[i].tenor);
230 // check time == volData[i].time
231 /*
232 if (fabs(dc.yearFraction(asof, dates[i]) - volData[i].time) > 0.001)
233 BOOST_FAIL("Did not match vol data time (" << volData[i].time <<
234 ") with aosf " << asof << " and maturity " << dates[i] <<
235 " got year fraction of " << dc.yearFraction(asof, dates[i]));
236 */
237
238 atm[i] = volData[i].atm / 100;
239 rr[i] = volData[i].rr / 100;
240 bf[i] = volData[i].bf / 100;
241
242 discountDates[i + 1] = dates[i];
243 dfDom[i + 1] = volData[i].df_d;
244 dfFor[i + 1] = volData[i].df_f;
245 }
246
247 // Now build discount curves
248 Handle<YieldTermStructure> domYTS(
249 QuantLib::ext::shared_ptr<YieldTermStructure>(new DiscountCurve(discountDates, dfDom, dc)));
250 Handle<YieldTermStructure> forYTS(
251 QuantLib::ext::shared_ptr<YieldTermStructure>(new DiscountCurve(discountDates, dfFor, dc)));
252
253 // build surface
254 FxBlackVannaVolgaVolatilitySurface volSurface(asof, dates, atm, rr, bf, dc, cal, fxSpot, domYTS, forYTS);
255
256 // 1.55,1.75,0.121507
257 Real vol = volSurface.blackVol(1.75, 1.55);
258 Real expected = 0.121507;
259 if (fabs(vol - expected) > 0.00001)
260 BOOST_FAIL("Failed to get expected vol from surface " << vol);
261 /*
262 cout << "strike,time,vol" << endl;
263 for (Real k = 1.0; k < 1.6; k += 0.01) // extreme
264 for (Time tt = 0.1; tt < 2; tt+= 0.05)
265 cout << k << "," << tt << "," << volSurface.blackVol(tt, k) << endl;
266 */
267
268 // Test if Null<Real>() or 0 strike returns atm vol
269 for (Size i = 0; i < len; i++) {
270 Real vol = volSurface.blackVol(dates[i], Null<Real>());
271 Real vol2 = volSurface.blackVol(dates[i], 0);
272 if (fabs(vol - atm[i]) > 0.00001)
273 BOOST_FAIL("Failed to get expected atm vol from surface: " << vol);
274 if (fabs(vol2 - atm[i]) > 0.00001)
275 BOOST_FAIL("Failed to get expected atm vol from surface: " << vol);
276 }
277}
278
279BOOST_AUTO_TEST_CASE(testInvertedVolTermStructure) {
280
281 BOOST_TEST_MESSAGE("Testing inverted vol term structure");
282
283 SavedSettings backup;
284
285 CommonVars vars;
286
287 Handle<BlackVolTermStructure> surface(QuantLib::ext::shared_ptr<BlackVolTermStructure>(
288 new BlackVarianceSurface(vars.today, TARGET(), vars.dates, vars.strikes, vars.vols, vars.dc)));
289
290 BlackInvertedVolTermStructure bivt(surface);
291
292 if (surface->maxDate() != bivt.maxDate())
293 BOOST_FAIL("inverted maxDate() vol surface does not match base");
294
295 if (surface->referenceDate() != bivt.referenceDate())
296 BOOST_FAIL("inverted referenceDate() vol surface does not match base");
297
298 // base spot is 100
299 // test cases <Time, Strike>
300 double testCases[][2] = { { 0.1, 104 }, { 0.5, 90 }, { 0.6, 110 }, { 0.9, 90 },
301 { 0.9, 95 }, { 0.9, 100 }, { 0.9, 105 }, { 0.9, 110 } };
302
303 for (Size i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) {
304 Time t = testCases[i][0];
305 Real k = testCases[i][1];
306
307 Real vol1 = surface->blackVol(t, k);
308
309 Real invertedStrike = 1.0 / k;
310 Real vol2 = bivt.blackVol(t, invertedStrike);
311 if (fabs(vol1 - vol2) > 0.00001)
312 BOOST_FAIL("Failed to get expected vol (" << vol1 << ") from inverted vol surface, got (" << vol2 << ")");
313 }
314}
315
316BOOST_AUTO_TEST_SUITE_END()
317
318BOOST_AUTO_TEST_SUITE_END()
Black volatility surface that inverts an existing surface.
Black volatility surface that inverts an existing surface.
Fx Black vanna volga volatility surface.
Volatility volatility(Real strike) const override
Real k_atm() const
getters for unit test
FX Black volatility surface that incorporates an FxSmile.
FX smile section assuming a strike/volatility space using vanna volga method.
BOOST_AUTO_TEST_CASE(testVannaVolgaFxSmileSection)
Definition: fxvolsmile.cpp:125
CompiledFormula log(CompiledFormula x)
vector< Real > strikes
Fixture that can be used at top level.