20#include <boost/test/unit_test.hpp>
24#include <ql/indexes/ibor/euribor.hpp>
25#include <ql/instruments/makevanillaswap.hpp>
26#include <ql/pricingengines/swap/discountingswapengine.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 std::vector<Handle<Quote>> tmpDiscSpr, tmpFwdSpr;
56 for (Size i = 0; i < pillarDates.size(); ++i) {
57 QuantLib::ext::shared_ptr<SimpleQuote> qd = QuantLib::ext::make_shared<SimpleQuote>(0.0);
58 QuantLib::ext::shared_ptr<SimpleQuote> qf = QuantLib::ext::make_shared<SimpleQuote>(0.0);
59 discountSpreads.push_back(qd);
60 forwardSpreads.push_back(qf);
61 tmpDiscSpr.push_back(Handle<Quote>(qd));
62 tmpFwdSpr.push_back(Handle<Quote>(qf));
63 pillarTimes.push_back(baseDiscount->timeFromReference(pillarDates[i]));
66 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
67 baseDiscount, tmpDiscSpr, pillarDates));
69 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
70 baseForward, tmpFwdSpr, pillarDates));
71 discountCurve->enableExtrapolation();
72 forwardCurve->enableExtrapolation();
73 forwardIndex = QuantLib::ext::make_shared<Euribor>(6 * Months, forwardCurve);
76 Handle<YieldTermStructure> baseDiscount, baseForward, discountCurve, forwardCurve;
77 QuantLib::ext::shared_ptr<IborIndex> forwardIndex;
78 std::vector<Date> pillarDates;
79 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> discountSpreads, forwardSpreads;
80 std::vector<Real> pillarTimes;
83bool check(
const Real reference,
const Real value) {
84 if (std::fabs(reference) >= 1E-4) {
85 return std::fabs((reference - value) / reference) < 1E-3;
87 return std::fabs(reference - value) < 5E-5;
92 boost::timer::cpu_timer timer_;
96 void start() { timer_ = boost::timer::cpu_timer(); }
97 void stop() { elapsed_ = timer_.elapsed().wall * 1e-9; }
98 double elapsed()
const {
return elapsed_; }
101void performTest(
const TestData& d,
const QuantLib::ext::shared_ptr<PricingEngine>& engine0,
102 const QuantLib::ext::shared_ptr<PricingEngine>& engine,
const bool receiveFixed,
const Real spread,
103 const std::string& config) {
104 BOOST_TEST_MESSAGE(
"Testing npv and bps calculation in DiscountingSwapEngineDeltaGamma against QuantLib engine ("
105 << config <<
")...");
107 VanillaSwap swap = MakeVanillaSwap(13 * Years, d.forwardIndex, 0.04, 0 * Days)
108 .receiveFixed(receiveFixed)
110 .withFloatingLegSpread(spread);
112 swap.setPricingEngine(engine0);
115 Real npvRef = swap.NPV();
118 Real bps1Ref = swap.legBPS(0) * 10000.0;
119 Real bps2Ref = swap.legBPS(1) * 10000.0;
121 swap.setPricingEngine(engine);
123 Real npv = swap.NPV();
126 Real bps1 = swap.legBPS(0);
127 Real bps2 = swap.legBPS(1);
129 const Real tol0 = 1E-10;
130 if (std::fabs(npv - npvRef) > tol0)
131 BOOST_ERROR(
"npv (" << npv <<
") is inconsistent to expected value (" << npvRef <<
"), difference is "
132 << npv - npvRef <<
", tolerance is " << tol0);
133 if (std::fabs(bps1 - bps1Ref) > tol0)
134 BOOST_ERROR(
"bps leg #1 (" << bps1 <<
") is inconsistent to expected value (" << bps1Ref <<
"), difference is "
135 << bps1 - bps1Ref <<
", tolerance is " << tol0);
136 if (std::fabs(bps2 - bps2Ref) > tol0)
137 BOOST_ERROR(
"bps leg #2 (" << bps2 <<
") is inconsistent to expected value (" << bps2Ref <<
"), difference is "
138 << bps2 - bps2Ref <<
", tolerance is " << tol0);
140 BOOST_TEST_MESSAGE(
"Testing delta calculation in DiscountingSwapEngineDeltaGamma against bump and revalue results ("
141 << config <<
")...");
143 std::vector<Real> resultDeltaDsc = swap.result<std::vector<Real>>(
"deltaDiscount");
144 std::vector<Real> resultDeltaFwd = swap.result<std::vector<Real>>(
"deltaForward");
145 Matrix resultGamma = swap.result<Matrix>(
"gamma");
146 std::vector<std::vector<Real>> resultDeltaBPS = swap.result<std::vector<std::vector<Real>>>(
"deltaBPS");
147 std::vector<Matrix> resultGammaBPS = swap.result<std::vector<Matrix>>(
"gammaBPS");
152 swap.setPricingEngine(engine0);
154 const Size n = d.pillarDates.size();
157 if (resultDeltaDsc.size() != n)
158 BOOST_ERROR(
"deltaDiscount result vector has a wrong dimension (" << resultDeltaDsc.size() <<
"), expected "
160 if (resultDeltaFwd.size() != n)
161 BOOST_ERROR(
"deltaForward result vector has a wrong dimension (" << resultDeltaFwd.size() <<
"), expected "
163 for (Size l = 0; l < 2; ++l) {
164 if (resultDeltaBPS[l].size() != n)
165 BOOST_ERROR(
"deltaBPS result vector for leg #" << l <<
" has a wrong dimension (" << resultDeltaBPS.size()
166 <<
"), expected " << n);
167 if (resultGammaBPS[l].rows() != n || resultGammaBPS[l].columns() != n)
168 BOOST_ERROR(
"gamma result matrix has wrong dimensions ("
169 << resultGammaBPS[l].rows() <<
"x" << resultGammaBPS[l].columns() <<
"), expected " << 2 * n
173 if (resultGamma.rows() != 2 * n || resultGamma.columns() != 2 * n)
174 BOOST_ERROR(
"gamma result matrix has wrong dimensions (" << resultGamma.rows() <<
"x" << resultGamma.columns()
175 <<
"), expected " << 2 * n <<
"x" << 2 * n);
179 const Real bump = 1E-7;
180 Real npv0 = swap.NPV();
181 std::vector<Real> legBPS0;
182 legBPS0.push_back(swap.legBPS(0) * 1E4);
183 legBPS0.push_back(swap.legBPS(1) * 1E4);
185 for (Size i = 0; i < n; ++i) {
186 d.discountSpreads[i]->setValue(bump);
187 Real deltaDsc = (swap.NPV() - npv0) / bump;
188 d.discountSpreads[i]->setValue(0.0);
189 d.forwardSpreads[i]->setValue(bump);
190 Real deltaFwd = (swap.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 for (Size l = 0; l < 2; ++l) {
204 for (Size i = 0; i < n; ++i) {
205 d.discountSpreads[i]->setValue(bump);
206 Real deltaDsc = (swap.legBPS(l) * 1E4 - legBPS0[l]) / bump;
207 d.discountSpreads[i]->setValue(0.0);
208 if (!
check(deltaDsc / 1E4, resultDeltaBPS[l][i] / 1E4))
209 BOOST_ERROR(
"bps-delta for leg #" << (l + 1) <<
" on pillar " << d.pillarTimes[i]
210 <<
" (discount curve) could not be verified, analytical: "
211 << resultDeltaBPS[l][i] <<
", bump and revalue: " << deltaDsc);
217 const Real bump2 = 1E-5;
218 Matrix bumpGamma(n * 2, n * 2, 0.0);
221 for (Size i = 0; i < n; ++i) {
223 for (Size j = 0; j < i; ++j) {
224 d.discountSpreads[i]->setValue(bump2);
225 d.discountSpreads[j]->setValue(bump2);
226 Real npvpp = swap.NPV();
227 d.discountSpreads[j]->setValue(0.0);
228 Real npvp0 = swap.NPV();
229 d.discountSpreads[i]->setValue(0.0);
230 d.discountSpreads[j]->setValue(bump2);
231 Real npv0p = swap.NPV();
232 d.discountSpreads[j]->setValue(0.0);
233 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
234 bumpGamma[i][j] = bumpGamma[j][i] = gamma;
237 d.discountSpreads[i]->setValue(2.0 * bump2);
238 Real npvpp = swap.NPV();
239 d.discountSpreads[i]->setValue(bump2);
240 Real npvp = swap.NPV();
241 d.discountSpreads[i]->setValue(0.0);
242 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
243 bumpGamma[i][i] = gamma;
247 for (Size i = 0; i < n; ++i) {
249 for (Size j = 0; j < n; ++j) {
250 d.discountSpreads[i]->setValue(bump2);
251 d.forwardSpreads[j]->setValue(bump2);
252 Real npvpp = swap.NPV();
253 d.forwardSpreads[j]->setValue(0.0);
254 Real npvp0 = swap.NPV();
255 d.discountSpreads[i]->setValue(0.0);
256 d.forwardSpreads[j]->setValue(bump2);
257 Real npv0p = swap.NPV();
258 d.forwardSpreads[j]->setValue(0.0);
259 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
260 bumpGamma[i][n + j] = bumpGamma[n + j][i] = gamma;
265 for (Size i = 0; i < n; ++i) {
267 for (Size j = 0; j < i; ++j) {
268 d.forwardSpreads[i]->setValue(bump2);
269 d.forwardSpreads[j]->setValue(bump2);
270 Real npvpp = swap.NPV();
271 d.forwardSpreads[j]->setValue(0.0);
272 Real npvp0 = swap.NPV();
273 d.forwardSpreads[i]->setValue(0.0);
274 d.forwardSpreads[j]->setValue(bump2);
275 Real npv0p = swap.NPV();
276 d.forwardSpreads[j]->setValue(0.0);
277 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
278 bumpGamma[n + i][n + j] = bumpGamma[n + j][n + i] = gamma;
281 d.forwardSpreads[i]->setValue(2.0 * bump2);
282 Real npvpp = swap.NPV();
283 d.forwardSpreads[i]->setValue(bump2);
284 Real npvp = swap.NPV();
285 d.forwardSpreads[i]->setValue(0.0);
286 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
287 bumpGamma[n + i][n + i] = gamma;
290 for (Size i = 0; i < 2 * n; ++i) {
291 for (Size j = 0; j < 2 * n; ++j) {
292 if (!
check(bumpGamma[i][j], resultGamma[i][j]))
293 BOOST_ERROR(
"gamma entry (" << i <<
"," << j <<
") is " << resultGamma[i][j]
294 <<
", bump and revalue result is " << bumpGamma[i][j]);
300 for (Size l = 0; l < 2; ++l) {
301 Matrix bumpGammaBPS(n, n, 0.0);
302 for (Size i = 0; i < n; ++i) {
304 for (Size j = 0; j < i; ++j) {
305 d.discountSpreads[i]->setValue(bump2);
306 d.discountSpreads[j]->setValue(bump2);
307 Real bpspp = swap.legBPS(l) * 1E4;
308 d.discountSpreads[j]->setValue(0.0);
309 Real bpsp0 = swap.legBPS(l) * 1E4;
310 d.discountSpreads[i]->setValue(0.0);
311 d.discountSpreads[j]->setValue(bump2);
312 Real bps0p = swap.legBPS(l) * 1E4;
313 d.discountSpreads[j]->setValue(0.0);
314 Real gamma = (bpspp - bpsp0 - bps0p + legBPS0[l]) / (bump2 * bump2);
315 bumpGammaBPS[i][j] = bumpGammaBPS[j][i] = gamma;
318 d.discountSpreads[i]->setValue(2.0 * bump2);
319 Real bpspp = swap.legBPS(l) * 1E4;
320 d.discountSpreads[i]->setValue(bump2);
321 Real bpsp = swap.legBPS(l) * 1E4;
322 d.discountSpreads[i]->setValue(0.0);
323 Real gamma = (bpspp - 2.0 * bpsp + legBPS0[l]) / (bump2 * bump2);
324 bumpGammaBPS[i][i] = gamma;
326 for (Size i = 0; i < n; ++i) {
327 for (Size j = 0; j < n; ++j) {
328 if (!
check(bumpGammaBPS[i][j] / 1E4, resultGammaBPS[l][i][j] / 1E4))
329 BOOST_ERROR(
"bps-gamma for leg #"
330 << (l + 1) <<
" at (" << i <<
"," << j <<
")"
331 <<
" could not be verified, analytical: " << resultGammaBPS[l][i][j]
332 <<
", bump and revalue: " << bumpGammaBPS[i][j]);
338 "Testing sum of deltas and gammas in DiscountingSwapEngineDeltaGamma against parallel bump of all yields ("
339 << config <<
")...");
344 for (Size i = 0; i < n; ++i) {
345 d.discountSpreads[i]->setValue(bump);
346 d.forwardSpreads[i]->setValue(bump);
349 Real totalDeltaBump = (swap.NPV() - npv0) / bump;
351 for (Size i = 0; i < n; ++i) {
352 d.discountSpreads[i]->setValue(2.0 * bump2);
353 d.forwardSpreads[i]->setValue(2.0 * bump2);
355 Real npvPP = swap.NPV();
356 for (Size i = 0; i < n; ++i) {
357 d.discountSpreads[i]->setValue(bump2);
358 d.forwardSpreads[i]->setValue(bump2);
360 Real npvP = swap.NPV();
361 for (Size i = 0; i < n; ++i) {
362 d.discountSpreads[i]->setValue(0.0);
363 d.forwardSpreads[i]->setValue(0.0);
366 Real totalGammaBump = (npvPP - 2.0 * npvP + npv0) / (bump2 * bump2);
368 Real totalDelta = 0.0, totalGamma = 0.0;
369 for (Size i = 0; i < n; ++i) {
370 totalDelta += resultDeltaDsc[i];
371 totalDelta += resultDeltaFwd[i];
374 for (Size i = 0; i < 2 * n; ++i) {
375 for (Size j = 0; j < 2 * n; ++j) {
376 totalGamma += resultGamma[i][j];
380 if (!
check(totalDeltaBump, totalDelta))
381 BOOST_ERROR(
"total delta (" << totalDelta <<
") can not be verified against bump and revalue result ("
382 << totalDeltaBump <<
")");
384 if (!
check(totalGammaBump, totalGamma))
385 BOOST_ERROR(
"total gamma (" << totalGamma <<
") can not be verified against bump and revalue result ("
386 << totalGammaBump <<
")");
394BOOST_AUTO_TEST_SUITE(DiscountingSwapEngineDeltaGammaTest)
400 QuantLib::ext::shared_ptr<PricingEngine> engine0 = QuantLib::ext::make_shared<DiscountingSwapEngine>(d.discountCurve);
401 QuantLib::ext::shared_ptr<PricingEngine> engine =
402 QuantLib::ext::make_shared<DiscountingSwapEngineDeltaGamma>(d.discountCurve, d.pillarTimes,
true,
true,
true);
404 performTest(d, engine0, engine,
false, 0.0,
"payer, zero spread");
405 performTest(d, engine0, engine,
true, 0.0,
"receiver, zero spread");
406 performTest(d, engine0, engine,
false, 0.01,
"payer, positive spread");
407 performTest(d, engine0, engine,
true, 0.01,
"receiver, positive spread");
408 performTest(d, engine0, engine,
false, -0.01,
"payer, negative spread");
409 performTest(d, engine0, engine,
true, -0.01,
"receiver, negative spread");
414BOOST_AUTO_TEST_SUITE_END()
416BOOST_AUTO_TEST_SUITE_END()
Swap engine providing analytical deltas and gammas for vanilla swaps.
BOOST_AUTO_TEST_CASE(testNpvDeltasGammas)
Fixture that can be used at top level.