20#include <boost/test/unit_test.hpp>
24#include <ql/indexes/ibor/euribor.hpp>
25#include <ql/instruments/makevanillaswap.hpp>
26#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
27#include <ql/quotes/simplequote.hpp>
28#include <ql/termstructures/yield/flatforward.hpp>
29#include <ql/termstructures/yield/piecewisezerospreadedtermstructure.hpp>
31#include <boost/make_shared.hpp>
32#include <boost/timer/timer.hpp>
37using namespace boost::unit_test_framework;
42 TestData() : refDate(Date(22, Aug, 2016)) {
43 Settings::instance().evaluationDate() = refDate;
44 baseDiscount = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
45 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.02)), Actual365Fixed()));
46 baseForward = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
47 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
48 pillarDates.push_back(refDate + 1 * Years);
49 pillarDates.push_back(refDate + 2 * Years);
50 pillarDates.push_back(refDate + 3 * Years);
51 pillarDates.push_back(refDate + 4 * Years);
52 pillarDates.push_back(refDate + 5 * Years);
53 pillarDates.push_back(refDate + 7 * Years);
54 pillarDates.push_back(refDate + 10 * Years);
55 pillarDates.push_back(refDate + 15 * Years);
56 pillarDates.push_back(refDate + 20 * Years);
57 std::vector<Handle<Quote>> tmpDiscSpr, tmpFwdSpr;
58 for (Size i = 0; i < pillarDates.size(); ++i) {
59 QuantLib::ext::shared_ptr<SimpleQuote> qd = QuantLib::ext::make_shared<SimpleQuote>(0.0);
60 QuantLib::ext::shared_ptr<SimpleQuote> qf = QuantLib::ext::make_shared<SimpleQuote>(0.0);
61 discountSpreads.push_back(qd);
62 forwardSpreads.push_back(qf);
63 tmpDiscSpr.push_back(Handle<Quote>(qd));
64 tmpFwdSpr.push_back(Handle<Quote>(qf));
65 pillarTimes.push_back(baseDiscount->timeFromReference(pillarDates[i]));
68 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
69 baseDiscount, tmpDiscSpr, pillarDates));
71 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
72 baseForward, tmpFwdSpr, pillarDates));
73 discountCurve->enableExtrapolation();
74 forwardCurve->enableExtrapolation();
75 forwardIndex = QuantLib::ext::make_shared<Euribor>(6 * Months, forwardCurve);
76 lnVol = QuantLib::ext::make_shared<SimpleQuote>(0.20);
77 slnVol = QuantLib::ext::make_shared<SimpleQuote>(0.10);
78 nVol = QuantLib::ext::make_shared<SimpleQuote>(0.0075);
82 Handle<YieldTermStructure> baseDiscount, baseForward, discountCurve, forwardCurve;
83 QuantLib::ext::shared_ptr<IborIndex> forwardIndex;
84 std::vector<Date> pillarDates;
85 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> discountSpreads, forwardSpreads;
86 std::vector<Real> pillarTimes;
87 QuantLib::ext::shared_ptr<SimpleQuote> lnVol, slnVol, nVol;
91bool check(
const Real reference,
const Real value) {
92 if (std::fabs(reference) >= 1E-4) {
93 return std::fabs((reference - value) / reference) < 5E-2;
95 return std::fabs(reference - value) < 5E-5;
100 boost::timer::cpu_timer timer_;
104 void start() { timer_ = boost::timer::cpu_timer(); }
105 void stop() { elapsed_ = timer_.elapsed().wall * 1e-9; }
106 double elapsed()
const {
return elapsed_; }
109void performTest(
const TestData& d,
const QuantLib::ext::shared_ptr<PricingEngine>& engine0,
110 const QuantLib::ext::shared_ptr<PricingEngine>& engine,
const bool receiveFixed,
const Real spread,
111 const std::string& config) {
114 "Testing npv, atm and vega calculation in BlackSwaptionEngineDeltaGamma against QuantLib engine (" << config
117 Size n = d.pillarTimes.size();
119 QuantLib::ext::shared_ptr<VanillaSwap> swap = MakeVanillaSwap(11 * Years, d.forwardIndex, 0.04, 8 * Years)
120 .receiveFixed(receiveFixed)
122 .withFloatingLegSpread(spread);
123 Date exerciseDate = d.refDate + 8 * Years;
124 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
125 QuantLib::ext::shared_ptr<Swaption> swaption = QuantLib::ext::make_shared<Swaption>(swap, exercise);
127 swaption->setPricingEngine(engine0);
128 Real atm0 = swaption->result<Real>(
"atmForward");
129 Real vega0 = swaption->result<Real>(
"vega");
130 Real npv0 = swaption->NPV();
132 swaption->setPricingEngine(engine);
133 Real atm = swaption->result<Real>(
"atmForward");
134 Matrix vega = swaption->result<Matrix>(
"vega");
135 Real npv = swaption->NPV();
138 if (vega.rows() != n || vega.columns() != n)
139 BOOST_ERROR(
"vegaLn dimensions (" << vega.rows() <<
"x" << vega.columns() <<
") are wrong, should be " << n
142 for (Size i = 0; i < n; ++i) {
143 for (Size j = 0; j < n; ++j) {
144 sumVega += vega[i][j];
150 if (std::fabs(atm0 - atm) > tol)
151 BOOST_ERROR(
"atm (" << atm <<
") can not be verified, expected " << atm0);
152 if (std::fabs(npv0 - npv) > tol)
153 BOOST_ERROR(
"npv (" << npv <<
") can not be verified, expected " << npv0);
154 if (std::fabs(vega0 - sumVega) > tol)
155 BOOST_ERROR(
"vega (" << sumVega <<
") can not be verified, expected " << vega0);
157 BOOST_TEST_MESSAGE(
"Testing delta calculation in BlackSwaptionEngineDeltaGamma against bump and revalue results ("
158 << config <<
")...");
160 std::vector<Real> resultDeltaDsc = swaption->result<std::vector<Real>>(
"deltaDiscount");
161 std::vector<Real> resultDeltaFwd = swaption->result<std::vector<Real>>(
"deltaForward");
162 Matrix resultGamma = swaption->result<Matrix>(
"gamma");
167 swaption->setPricingEngine(engine0);
170 if (resultDeltaDsc.size() != n)
171 BOOST_ERROR(
"deltaDiscount result vector has a wrong dimension (" << resultDeltaDsc.size() <<
"), expected "
173 if (resultDeltaFwd.size() != n)
174 BOOST_ERROR(
"deltaForward result vector has a wrong dimension (" << resultDeltaFwd.size() <<
"), expected "
177 if (resultGamma.rows() != 2 * n || resultGamma.columns() != 2 * n)
178 BOOST_ERROR(
"gamma result vector has wrong dimensions (" << resultGamma.rows() <<
"x" << resultGamma.columns()
179 <<
"), expected " << n <<
"x" << n);
183 const Real bump = 1E-7;
185 for (Size i = 0; i < n; ++i) {
186 d.discountSpreads[i]->setValue(bump);
187 Real deltaDsc = (swaption->NPV() - npv0) / bump;
188 d.discountSpreads[i]->setValue(0.0);
189 d.forwardSpreads[i]->setValue(bump);
190 Real deltaFwd = (swaption->NPV() - npv0) / bump;
191 d.forwardSpreads[i]->setValue(0.0);
192 if (!
check(deltaDsc, resultDeltaDsc[i]))
193 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i]
194 <<
" (discount curve) could not be verified, analytical: "
195 << resultDeltaDsc[i] <<
", bump and revalue: " << deltaDsc);
196 if (!
check(deltaFwd, resultDeltaFwd[i]))
197 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i] <<
" (forward curve) could not be verified, analytical: "
198 << resultDeltaFwd[i] <<
", bump and revalue: " << deltaFwd);
203 BOOST_TEST_MESSAGE(
"Testing gamma calculation in BlackSwaptionEngineDeltaGamma against bump and revalue results ("
204 << config <<
")...");
206 const Real bump2 = 1E-5;
207 Matrix bumpGamma(n * 2, n * 2, 0.0);
210 for (Size i = 0; i < n; ++i) {
212 for (Size j = 0; j < i; ++j) {
213 d.discountSpreads[i]->setValue(bump2);
214 d.discountSpreads[j]->setValue(bump2);
215 Real npvpp = swaption->NPV();
216 d.discountSpreads[j]->setValue(0.0);
217 Real npvp0 = swaption->NPV();
218 d.discountSpreads[i]->setValue(0.0);
219 d.discountSpreads[j]->setValue(bump2);
220 Real npv0p = swaption->NPV();
221 d.discountSpreads[j]->setValue(0.0);
222 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
223 bumpGamma[i][j] = bumpGamma[j][i] = gamma;
226 d.discountSpreads[i]->setValue(2.0 * bump2);
227 Real npvpp = swaption->NPV();
228 d.discountSpreads[i]->setValue(bump2);
229 Real npvp = swaption->NPV();
230 d.discountSpreads[i]->setValue(0.0);
231 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
232 bumpGamma[i][i] = gamma;
236 for (Size i = 0; i < n; ++i) {
238 for (Size j = 0; j < n; ++j) {
239 d.discountSpreads[i]->setValue(bump2);
240 d.forwardSpreads[j]->setValue(bump2);
241 Real npvpp = swaption->NPV();
242 d.forwardSpreads[j]->setValue(0.0);
243 Real npvp0 = swaption->NPV();
244 d.discountSpreads[i]->setValue(0.0);
245 d.forwardSpreads[j]->setValue(bump2);
246 Real npv0p = swaption->NPV();
247 d.forwardSpreads[j]->setValue(0.0);
248 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
249 bumpGamma[i][n + j] = bumpGamma[n + j][i] = gamma;
254 for (Size i = 0; i < n; ++i) {
256 for (Size j = 0; j < i; ++j) {
257 d.forwardSpreads[i]->setValue(bump2);
258 d.forwardSpreads[j]->setValue(bump2);
259 Real npvpp = swaption->NPV();
260 d.forwardSpreads[j]->setValue(0.0);
261 Real npvp0 = swaption->NPV();
262 d.forwardSpreads[i]->setValue(0.0);
263 d.forwardSpreads[j]->setValue(bump2);
264 Real npv0p = swaption->NPV();
265 d.forwardSpreads[j]->setValue(0.0);
266 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
267 bumpGamma[n + i][n + j] = bumpGamma[n + j][n + i] = gamma;
270 d.forwardSpreads[i]->setValue(2.0 * bump2);
271 Real npvpp = swaption->NPV();
272 d.forwardSpreads[i]->setValue(bump2);
273 Real npvp = swaption->NPV();
274 d.forwardSpreads[i]->setValue(0.0);
275 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
276 bumpGamma[n + i][n + i] = gamma;
279 for (Size i = 0; i < 2 * n; ++i) {
280 for (Size j = 0; j < 2 * n; ++j) {
281 if (!
check(bumpGamma[i][j], resultGamma[i][j]))
282 BOOST_ERROR(
"gamma entry (" << i <<
"," << j <<
") is " << resultGamma[i][j]
283 <<
", bump and revalue result is " << bumpGamma[i][j]);
290 for (Size i = 0; i < n; ++i) {
291 d.discountSpreads[i]->setValue(bump);
292 d.forwardSpreads[i]->setValue(bump);
295 Real totalDeltaBump = (swaption->NPV() - npv0) / bump;
297 for (Size i = 0; i < n; ++i) {
298 d.discountSpreads[i]->setValue(2.0 * bump2);
299 d.forwardSpreads[i]->setValue(2.0 * bump2);
301 Real npvPP = swaption->NPV();
302 for (Size i = 0; i < n; ++i) {
303 d.discountSpreads[i]->setValue(bump2);
304 d.forwardSpreads[i]->setValue(bump2);
306 Real npvP = swaption->NPV();
307 for (Size i = 0; i < n; ++i) {
308 d.discountSpreads[i]->setValue(0.0);
309 d.forwardSpreads[i]->setValue(0.0);
312 Real totalGammaBump = (npvPP - 2.0 * npvP + npv0) / (bump2 * bump2);
314 Real totalDelta = 0.0, totalGamma = 0.0;
315 for (Size i = 0; i < n; ++i) {
316 totalDelta += resultDeltaDsc[i];
317 totalDelta += resultDeltaFwd[i];
320 for (Size i = 0; i < 2 * n; ++i) {
321 for (Size j = 0; j < 2 * n; ++j) {
322 totalGamma += resultGamma[i][j];
326 if (!
check(totalDeltaBump, totalDelta))
327 BOOST_ERROR(
"total delta (" << totalDelta <<
") can not be verified against bump and revalue result ("
328 << totalDeltaBump <<
")");
330 if (!
check(totalGammaBump, totalGamma))
331 BOOST_ERROR(
"total gamma (" << totalGamma <<
") can not be verified against bump and revalue result ("
332 << totalGammaBump <<
")");
340BOOST_AUTO_TEST_SUITE(BlackSwaptionEngineDeltaGammaTest)
346 QuantLib::ext::shared_ptr<PricingEngine> engineLn0 =
347 QuantLib::ext::make_shared<BlackSwaptionEngine>(d.discountCurve, Handle<Quote>(d.lnVol));
348 QuantLib::ext::shared_ptr<PricingEngine> engineSln0 =
349 QuantLib::ext::make_shared<BlackSwaptionEngine>(d.discountCurve, Handle<Quote>(d.slnVol), Actual365Fixed(), d.slnShift);
350 QuantLib::ext::shared_ptr<PricingEngine> engineN0 =
351 QuantLib::ext::make_shared<BachelierSwaptionEngine>(d.discountCurve, Handle<Quote>(d.nVol));
353 QuantLib::ext::shared_ptr<PricingEngine> engineLn =
354 QuantLib::ext::make_shared<BlackSwaptionEngineDeltaGamma>(d.discountCurve, Handle<Quote>(d.lnVol), Actual365Fixed(),
355 0.0, d.pillarTimes, d.pillarTimes, d.pillarTimes,
true,
true);
356 QuantLib::ext::shared_ptr<PricingEngine> engineSln = QuantLib::ext::make_shared<BlackSwaptionEngineDeltaGamma>(
357 d.discountCurve, Handle<Quote>(d.slnVol), Actual365Fixed(), d.slnShift, d.pillarTimes, d.pillarTimes,
358 d.pillarTimes,
true,
true);
359 QuantLib::ext::shared_ptr<PricingEngine> engineN =
360 QuantLib::ext::make_shared<BachelierSwaptionEngineDeltaGamma>(d.discountCurve, Handle<Quote>(d.nVol), Actual365Fixed(),
361 d.pillarTimes, d.pillarTimes, d.pillarTimes,
true,
true);
363 performTest(d, engineLn0, engineLn,
false, 0.0,
"lognormal model, payer");
364 performTest(d, engineSln0, engineSln,
false, 0.0,
"shifted lognormal model, payer");
365 performTest(d, engineN0, engineN,
false, 0.0,
"normal model, payer");
367 performTest(d, engineLn0, engineLn,
true, 0.0,
"lognormal model, receiver");
368 performTest(d, engineSln0, engineSln,
true, 0.0,
"shifted lognormal model, receiver");
369 performTest(d, engineN0, engineN,
true, 0.0,
"normal model, receiver");
384BOOST_AUTO_TEST_SUITE_END()
386BOOST_AUTO_TEST_SUITE_END()
Swaption engine providing analytical deltas for vanilla swaps.
BOOST_AUTO_TEST_CASE(testNpvDeltasGammaVegas)
Fixture that can be used at top level.