20#include <boost/test/unit_test.hpp>
25#include <ql/cashflows/iborcoupon.hpp>
26#include <ql/indexes/ibor/euribor.hpp>
27#include <ql/indexes/ibor/usdlibor.hpp>
28#include <ql/math/matrix.hpp>
29#include <ql/quotes/simplequote.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
31#include <ql/termstructures/yield/piecewisezerospreadedtermstructure.hpp>
32#include <ql/time/calendars/target.hpp>
33#include <ql/time/calendars/unitedstates.hpp>
35#include <boost/make_shared.hpp>
40using namespace boost::unit_test_framework;
45 TestData() : refDate(Date(22, Aug, 2016)) {
46 Settings::instance().evaluationDate() = refDate;
47 baseDiscount = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
48 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.02)), Actual365Fixed()));
49 baseForward = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
50 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
51 baseDiscountFor = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
52 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.04)), Actual365Fixed()));
53 baseForwardFor = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
54 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.05)), Actual365Fixed()));
55 fxQuote = QuantLib::ext::make_shared<SimpleQuote>(0.90);
56 pillarDates.push_back(refDate + 1 * Years);
57 pillarDates.push_back(refDate + 2 * Years);
58 pillarDates.push_back(refDate + 3 * Years);
59 pillarDates.push_back(refDate + 4 * Years);
60 pillarDates.push_back(refDate + 5 * Years);
61 pillarDates.push_back(refDate + 7 * Years);
62 pillarDates.push_back(refDate + 10 * Years);
63 std::vector<Handle<Quote>> tmpDiscSpr, tmpFwdSpr, tmpDiscSprFor, tmpFwdSprFor;
64 for (Size i = 0; i < pillarDates.size(); ++i) {
65 QuantLib::ext::shared_ptr<SimpleQuote> qd = QuantLib::ext::make_shared<SimpleQuote>(0.0);
66 QuantLib::ext::shared_ptr<SimpleQuote> qf = QuantLib::ext::make_shared<SimpleQuote>(0.0);
67 QuantLib::ext::shared_ptr<SimpleQuote> qdf = QuantLib::ext::make_shared<SimpleQuote>(0.0);
68 QuantLib::ext::shared_ptr<SimpleQuote> qff = QuantLib::ext::make_shared<SimpleQuote>(0.0);
69 discountSpreads.push_back(qd);
70 forwardSpreads.push_back(qf);
71 discountSpreadsFor.push_back(qdf);
72 forwardSpreadsFor.push_back(qff);
73 tmpDiscSpr.push_back(Handle<Quote>(qd));
74 tmpFwdSpr.push_back(Handle<Quote>(qf));
75 tmpDiscSprFor.push_back(Handle<Quote>(qdf));
76 tmpFwdSprFor.push_back(Handle<Quote>(qff));
77 pillarTimes.push_back(baseDiscount->timeFromReference(pillarDates[i]));
80 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
81 baseDiscount, tmpDiscSpr, pillarDates));
83 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
84 baseForward, tmpFwdSpr, pillarDates));
86 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
87 baseDiscountFor, tmpDiscSprFor, pillarDates));
89 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
90 baseForwardFor, tmpFwdSprFor, pillarDates));
91 discountCurve->enableExtrapolation();
92 forwardCurve->enableExtrapolation();
93 discountCurveFor->enableExtrapolation();
94 forwardCurveFor->enableExtrapolation();
95 forwardIndex = QuantLib::ext::make_shared<Euribor>(6 * Months, forwardCurve);
96 forwardIndexFor = QuantLib::ext::make_shared<USDLibor>(3 * Months, forwardCurveFor);
99 Handle<YieldTermStructure> baseDiscount, baseForward, discountCurve, forwardCurve;
100 Handle<YieldTermStructure> baseDiscountFor, baseForwardFor, discountCurveFor, forwardCurveFor;
101 QuantLib::ext::shared_ptr<IborIndex> forwardIndex, forwardIndexFor;
102 QuantLib::ext::shared_ptr<SimpleQuote> fxQuote;
103 std::vector<Date> pillarDates;
104 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> discountSpreads, forwardSpreads;
105 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> discountSpreadsFor, forwardSpreadsFor;
106 std::vector<Real> pillarTimes;
109bool check(
const Real reference,
const Real value) {
110 if (std::fabs(reference) >= 1E-4) {
111 return std::fabs((reference - value) / reference) < 1E-3;
113 return std::fabs(reference - value) < 5E-5;
117void performTest(
const TestData& d,
const QuantLib::ext::shared_ptr<PricingEngine>& engine0,
118 const QuantLib::ext::shared_ptr<PricingEngine>& engine,
const bool receive,
const Real spread,
119 const std::string& config) {
121 BOOST_TEST_MESSAGE(
"Testing npv calculation in DiscountingCurrencySwapEngineDeltaGamma against QuantExt engine ("
122 << config <<
")...");
124 Date settlement = d.refDate + 2;
125 Schedule scheduleEur(settlement, TARGET().advance(settlement, 10 * Years), 6 * Months, TARGET(), ModifiedFollowing,
126 ModifiedFollowing, DateGeneration::Forward,
false);
127 Schedule scheduleUsd(settlement, UnitedStates(UnitedStates::Settlement).advance(settlement, 10 * Years), 3 * Months, UnitedStates(UnitedStates::Settlement),
128 ModifiedFollowing, ModifiedFollowing, DateGeneration::Forward,
false);
129 Leg eurLeg = IborLeg(scheduleEur, d.forwardIndex).withNotionals(10.0);
130 Leg usdLeg = IborLeg(scheduleUsd, d.forwardIndexFor).withNotionals(10.0).withSpreads(spread);
131 std::vector<Leg> legs;
132 legs.push_back(eurLeg);
133 legs.push_back(usdLeg);
134 std::vector<Currency> currencies;
135 currencies.push_back(EURCurrency());
136 currencies.push_back(USDCurrency());
137 std::vector<bool> payer;
138 payer.push_back(receive);
139 payer.push_back(!receive);
142 std::vector<Handle<YieldTermStructure>> discountCurves;
143 discountCurves.push_back(d.discountCurve);
144 discountCurves.push_back(d.discountCurveFor);
146 std::vector<Handle<Quote>> fx;
147 fx.push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.00)));
148 fx.push_back(Handle<Quote>(d.fxQuote));
150 swap.setPricingEngine(engine0);
151 Real npvRef = swap.NPV();
153 swap.setPricingEngine(engine);
154 Real npv = swap.NPV();
156 const Real tol = 1E-7;
157 if (std::fabs(npv - npvRef) > tol)
158 BOOST_ERROR(
"npv (" << npv <<
") is inconsistent to expected value (" << npvRef <<
"), difference is "
159 << npv - npvRef <<
", tolerance is " << tol);
161 std::vector<Real> resultDeltaDsc =
163 std::vector<Real> resultDeltaFwd =
165 std::vector<Real> resultDeltaDscFor =
167 std::vector<Real> resultDeltaFwdFor =
172 Matrix resultGammaFor =
175 Real resultDeltaFxSpot =
180 if (std::fabs(resultFxSpot - d.fxQuote->value()) > tol)
181 BOOST_ERROR(
"fxSpot (" << resultFxSpot <<
") is inconsistent to expected value (" << d.fxQuote->value()
182 <<
"), difference is " << resultFxSpot - d.fxQuote->value() <<
", tolerance is " << tol);
185 "Testing delta calculation in DiscountingCurrencySwapEngineDeltaGamma against bump and revalue results ("
186 << config <<
")...");
191 swap.setPricingEngine(engine0);
193 const Size n = d.pillarDates.size();
196 if (resultDeltaDsc.size() != n)
197 BOOST_ERROR(
"deltaDiscount (EUR) has a wrong dimension (" << resultDeltaDsc.size() <<
"), expected " << n);
198 if (resultDeltaFwd.size() != n)
199 BOOST_ERROR(
"deltaForward (EUR) has a wrong dimension (" << resultDeltaFwd.size() <<
"), expected " << n);
200 if (resultDeltaDscFor.size() != n)
201 BOOST_ERROR(
"deltaDiscount (USD) has a wrong dimension (" << resultDeltaDsc.size() <<
"), expected " << n);
202 if (resultDeltaFwdFor.size() != n)
203 BOOST_ERROR(
"deltaForward (USD) has a wrong dimension (" << resultDeltaFwd.size() <<
"), expected " << n);
204 if (resultGamma.rows() != 2 * n || resultGamma.columns() != 2 * n)
205 BOOST_ERROR(
"gamma result matrix (EUR) has wrong dimensions ("
206 << resultGamma.rows() <<
"x" << resultGamma.columns() <<
"), expected " << 2 * n <<
"x" << 2 * n);
207 if (resultGammaFor.rows() != 2 * n || resultGamma.columns() != 2 * n)
208 BOOST_ERROR(
"gamma result matrix (EUR) has wrong dimensions ("
209 << resultGamma.rows() <<
"x" << resultGamma.columns() <<
"), expected " << 2 * n <<
"x" << 2 * n);
213 const Real bump = 1E-7;
214 Real npv0 = swap.NPV();
215 for (Size i = 0; i < d.pillarDates.size(); ++i) {
216 d.discountSpreads[i]->setValue(bump);
217 Real deltaDsc = (swap.NPV() - npv0) / bump;
218 d.discountSpreads[i]->setValue(0.0);
219 d.forwardSpreads[i]->setValue(bump);
220 Real deltaFwd = (swap.NPV() - npv0) / bump;
221 d.forwardSpreads[i]->setValue(0.0);
222 d.discountSpreadsFor[i]->setValue(bump);
224 Real deltaDscFor = (swap.NPV() - npv0) / bump / d.fxQuote->value();
225 d.discountSpreadsFor[i]->setValue(0.0);
226 d.forwardSpreadsFor[i]->setValue(bump);
227 Real deltaFwdFor = (swap.NPV() - npv0) / bump / d.fxQuote->value();
228 d.forwardSpreadsFor[i]->setValue(0.0);
229 if (!
check(deltaDsc, resultDeltaDsc[i]))
230 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i]
231 <<
" (discount curve, EUR) could not be verified, analytical: "
232 << resultDeltaDsc[i] <<
", bump and revalue: " << deltaDsc);
233 if (!
check(deltaFwd, resultDeltaFwd[i]))
234 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i]
235 <<
" (forward curve, EUR) could not be verified, analytical: "
236 << resultDeltaFwd[i] <<
", bump and revalue: " << deltaFwd);
237 if (!
check(deltaDscFor, resultDeltaDscFor[i]))
238 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i]
239 <<
" (discount curve, USD) could not be verified, analytical: "
240 << resultDeltaDscFor[i] <<
", bump and revalue: " << deltaDscFor);
241 if (!
check(deltaFwdFor, resultDeltaFwdFor[i]))
242 BOOST_ERROR(
"delta on pillar " << d.pillarTimes[i]
243 <<
" (forward curve, USD) could not be verified, analytical: "
244 << resultDeltaFwdFor[i] <<
", bump and revalue: " << deltaFwdFor);
247 d.fxQuote->setValue(d.fxQuote->value() + bump);
248 Real deltaFxSpot = (swap.NPV() - npv0) / bump;
249 d.fxQuote->setValue(d.fxQuote->value() - bump);
250 if (!
check(deltaFxSpot, resultDeltaFxSpot))
251 BOOST_ERROR(
"FXSpot delta could not be verified, analytical: " << resultDeltaFxSpot
252 <<
", bump and revalue: " << deltaFxSpot);
257 "Testing gamma calculation in DiscountingCurrencySwapEngineDeltaGamma against bump and revalue results ("
258 << config <<
")...");
260 const Real bump2 = 1E-5;
261 Matrix bumpGamma(n * 2, n * 2, 0.0), bumpGammaFor(n * 2, n * 2, 0.0);
264 for (Size i = 0; i < n; ++i) {
266 for (Size j = 0; j < i; ++j) {
267 d.discountSpreads[i]->setValue(bump2);
268 d.discountSpreads[j]->setValue(bump2);
269 Real npvpp = swap.NPV();
270 d.discountSpreads[j]->setValue(0.0);
271 Real npvp0 = swap.NPV();
272 d.discountSpreads[i]->setValue(0.0);
273 d.discountSpreads[j]->setValue(bump2);
274 Real npv0p = swap.NPV();
275 d.discountSpreads[j]->setValue(0.0);
276 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
277 bumpGamma[i][j] = bumpGamma[j][i] = gamma;
280 d.discountSpreads[i]->setValue(2.0 * bump2);
281 Real npvpp = swap.NPV();
282 d.discountSpreads[i]->setValue(bump2);
283 Real npvp = swap.NPV();
284 d.discountSpreads[i]->setValue(0.0);
285 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
286 bumpGamma[i][i] = gamma;
290 for (Size i = 0; i < n; ++i) {
292 for (Size j = 0; j < n; ++j) {
293 d.discountSpreads[i]->setValue(bump2);
294 d.forwardSpreads[j]->setValue(bump2);
295 Real npvpp = swap.NPV();
296 d.forwardSpreads[j]->setValue(0.0);
297 Real npvp0 = swap.NPV();
298 d.discountSpreads[i]->setValue(0.0);
299 d.forwardSpreads[j]->setValue(bump2);
300 Real npv0p = swap.NPV();
301 d.forwardSpreads[j]->setValue(0.0);
302 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
303 bumpGamma[i][n + j] = bumpGamma[n + j][i] = gamma;
308 for (Size i = 0; i < n; ++i) {
310 for (Size j = 0; j < i; ++j) {
311 d.forwardSpreads[i]->setValue(bump2);
312 d.forwardSpreads[j]->setValue(bump2);
313 Real npvpp = swap.NPV();
314 d.forwardSpreads[j]->setValue(0.0);
315 Real npvp0 = swap.NPV();
316 d.forwardSpreads[i]->setValue(0.0);
317 d.forwardSpreads[j]->setValue(bump2);
318 Real npv0p = swap.NPV();
319 d.forwardSpreads[j]->setValue(0.0);
320 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
321 bumpGamma[n + i][n + j] = bumpGamma[n + j][n + i] = gamma;
324 d.forwardSpreads[i]->setValue(2.0 * bump2);
325 Real npvpp = swap.NPV();
326 d.forwardSpreads[i]->setValue(bump2);
327 Real npvp = swap.NPV();
328 d.forwardSpreads[i]->setValue(0.0);
329 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
330 bumpGamma[n + i][n + i] = gamma;
334 for (Size i = 0; i < n; ++i) {
336 for (Size j = 0; j < i; ++j) {
337 d.discountSpreadsFor[i]->setValue(bump2);
338 d.discountSpreadsFor[j]->setValue(bump2);
339 Real npvpp = swap.NPV();
340 d.discountSpreadsFor[j]->setValue(0.0);
341 Real npvp0 = swap.NPV();
342 d.discountSpreadsFor[i]->setValue(0.0);
343 d.discountSpreadsFor[j]->setValue(bump2);
344 Real npv0p = swap.NPV();
345 d.discountSpreadsFor[j]->setValue(0.0);
346 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
347 bumpGammaFor[i][j] = bumpGammaFor[j][i] = gamma / d.fxQuote->value();
350 d.discountSpreadsFor[i]->setValue(2.0 * bump2);
351 Real npvpp = swap.NPV();
352 d.discountSpreadsFor[i]->setValue(bump2);
353 Real npvp = swap.NPV();
354 d.discountSpreadsFor[i]->setValue(0.0);
355 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
356 bumpGammaFor[i][i] = gamma / d.fxQuote->value();
360 for (Size i = 0; i < n; ++i) {
362 for (Size j = 0; j < n; ++j) {
363 d.discountSpreadsFor[i]->setValue(bump2);
364 d.forwardSpreadsFor[j]->setValue(bump2);
365 Real npvpp = swap.NPV();
366 d.forwardSpreadsFor[j]->setValue(0.0);
367 Real npvp0 = swap.NPV();
368 d.discountSpreadsFor[i]->setValue(0.0);
369 d.forwardSpreadsFor[j]->setValue(bump2);
370 Real npv0p = swap.NPV();
371 d.forwardSpreadsFor[j]->setValue(0.0);
372 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
373 bumpGammaFor[i][n + j] = bumpGammaFor[n + j][i] = gamma / d.fxQuote->value();
378 for (Size i = 0; i < n; ++i) {
380 for (Size j = 0; j < i; ++j) {
381 d.forwardSpreadsFor[i]->setValue(bump2);
382 d.forwardSpreadsFor[j]->setValue(bump2);
383 Real npvpp = swap.NPV();
384 d.forwardSpreadsFor[j]->setValue(0.0);
385 Real npvp0 = swap.NPV();
386 d.forwardSpreadsFor[i]->setValue(0.0);
387 d.forwardSpreadsFor[j]->setValue(bump2);
388 Real npv0p = swap.NPV();
389 d.forwardSpreadsFor[j]->setValue(0.0);
390 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
391 bumpGammaFor[n + i][n + j] = bumpGammaFor[n + j][n + i] = gamma / d.fxQuote->value();
394 d.forwardSpreadsFor[i]->setValue(2.0 * bump2);
395 Real npvpp = swap.NPV();
396 d.forwardSpreadsFor[i]->setValue(bump2);
397 Real npvp = swap.NPV();
398 d.forwardSpreadsFor[i]->setValue(0.0);
399 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
400 bumpGammaFor[n + i][n + i] = gamma / d.fxQuote->value();
404 for (Size i = 0; i < 2 * n; ++i) {
405 for (Size j = 0; j < 2 * n; ++j) {
406 if (!
check(resultGamma[i][j], bumpGamma[i][j]))
407 BOOST_ERROR(
"gamma (EUR) entry (" << i <<
"," << j <<
") is " << resultGamma[i][j]
408 <<
", bump and revalue result is " << bumpGamma[i][j]);
413 for (Size i = 0; i < 2 * n; ++i) {
414 for (Size j = 0; j < 2 * n; ++j) {
415 if (!
check(resultGammaFor[i][j], bumpGammaFor[i][j]))
416 BOOST_ERROR(
"gamma (USD) entry (" << i <<
"," << j <<
") is " << resultGammaFor[i][j]
417 <<
", bump and revalue result is " << bumpGammaFor[i][j]);
426BOOST_AUTO_TEST_SUITE(DiscountingCurrencySwapEngineDeltaGammaTest)
432 std::vector<Handle<YieldTermStructure>> discountCurves;
433 discountCurves.push_back(d.discountCurve);
434 discountCurves.push_back(d.discountCurveFor);
435 std::vector<Handle<Quote>> fx;
436 fx.push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.00)));
437 fx.push_back(Handle<Quote>(d.fxQuote));
438 std::vector<Currency> currencies;
439 currencies.push_back(EURCurrency());
440 currencies.push_back(USDCurrency());
442 QuantLib::ext::shared_ptr<PricingEngine> engine0 =
443 QuantLib::ext::make_shared<DiscountingCurrencySwapEngine>(discountCurves, fx, currencies, EURCurrency());
444 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<DiscountingCurrencySwapEngineDeltaGamma>(
445 discountCurves, fx, currencies, EURCurrency(), d.pillarTimes,
true,
true);
447 performTest(d, engine0, engine,
false, 0.0,
"payer, zero spread");
448 performTest(d, engine0, engine,
true, 0.0,
"receiver, zero spread");
449 performTest(d, engine0, engine,
false, 0.01,
"payer, positive spread");
450 performTest(d, engine0, engine,
true, 0.01,
"receiver, positive spread");
451 performTest(d, engine0, engine,
false, -0.01,
"payer, negative spread");
452 performTest(d, engine0, engine,
true, -0.01,
"receiver, negative spread");
457BOOST_AUTO_TEST_SUITE_END()
459BOOST_AUTO_TEST_SUITE_END()
Currency Interest Rate Swap
std::map< Currency, std::vector< Real >, CurrencyComparator > result_type_vector
std::map< Currency, Real, CurrencyComparator > result_type_scalar
std::map< Currency, Matrix, CurrencyComparator > result_type_matrix
discounting currency swap engine
discounting currency swap engine providing analytical deltas and gammas for vanilla swaps
BOOST_AUTO_TEST_CASE(testNpvDeltasGammas)
Fixture that can be used at top level.