[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 Copyright (C) 2021 Marcin Rybacki
4
5 This file is part of QuantLib, a free-software/open-source library
6 for financial quantitative analysts and developers - http://quantlib.org/
7
8 QuantLib is free software: you can redistribute it and/or modify it
9 under the terms of the QuantLib license. You should have received a
10 copy of the license along with this program; if not, please email
11 <quantlib-dev@lists.sf.net>. The license is also available online at
12 <http://quantlib.org/license.shtml>.
13
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include "zerocouponswap.hpp"
20#include "utilities.hpp"
21#include <ql/instruments/zerocouponswap.hpp>
22#include <ql/cashflows/subperiodcoupon.hpp>
23#include <ql/pricingengines/swap/discountingswapengine.hpp>
24#include <ql/indexes/ibor/euribor.hpp>
25#include <ql/time/calendars/target.hpp>
26
27using namespace QuantLib;
28using namespace boost::unit_test_framework;
29
30namespace zerocouponswap_test {
31
32 struct CommonVars {
33
34 Date today, settlement;
35 Calendar calendar;
36 Natural settlementDays, paymentDelay;
37 DayCounter dayCount;
38 BusinessDayConvention businessConvention;
39 Real baseNominal, finalPayment;
40
41 ext::shared_ptr<IborIndex> euribor;
42 RelinkableHandle<YieldTermStructure> euriborHandle;
43 ext::shared_ptr<PricingEngine> discountEngine;
44
45 // utilities
46
47 CommonVars() {
48 settlementDays = 2;
49 paymentDelay = 1;
50 calendar = TARGET();
51 dayCount = Actual365Fixed();
52 businessConvention = ModifiedFollowing;
53 baseNominal = 1.0e6;
54 finalPayment = 1.2e6;
55
56 euribor = ext::shared_ptr<IborIndex>(new Euribor6M(euriborHandle));
57 euribor->addFixing(fixingDate: Date(10, February, 2021), fixing: 0.0085);
58
59 today = calendar.adjust(Date(15, March, 2021));
60 Settings::instance().evaluationDate() = today;
61 settlement = calendar.advance(today, n: settlementDays, unit: Days);
62
63 euriborHandle.linkTo(h: flatRate(today: settlement, forward: 0.007, dc: dayCount));
64 discountEngine =
65 ext::shared_ptr<PricingEngine>(new DiscountingSwapEngine(euriborHandle));
66 }
67
68 ext::shared_ptr<CashFlow> createSubPeriodsCoupon(const Date& start, const Date& end) const {
69 Date paymentDate = calendar.advance(date: end, period: paymentDelay * Days, convention: businessConvention);
70 ext::shared_ptr<FloatingRateCoupon> cpn(new SubPeriodsCoupon(
71 paymentDate, baseNominal, start, end, settlementDays, euribor));
72 cpn->setPricer(
73 ext::shared_ptr<FloatingRateCouponPricer>(new CompoundingRatePricer()));
74 return cpn;
75 }
76
77 ext::shared_ptr<ZeroCouponSwap> createZCSwap(Swap::Type type,
78 const Date& start,
79 const Date& end,
80 Real baseNominal,
81 Real finalPayment) {
82 auto swap = ext::make_shared<ZeroCouponSwap>(args&: type, args&: baseNominal, args: start, args: end, args&: finalPayment,
83 args&: euribor, args&: calendar, args&: businessConvention,
84 args&: paymentDelay);
85 swap->setPricingEngine(discountEngine);
86 return swap;
87 }
88
89 ext::shared_ptr<ZeroCouponSwap> createZCSwap(Swap::Type type,
90 const Date& start,
91 const Date& end,
92 Real finalPayment) {
93 return createZCSwap(type, start, end, baseNominal, finalPayment);
94 }
95
96 ext::shared_ptr<ZeroCouponSwap> createZCSwap(Swap::Type type,
97 const Date& start,
98 const Date& end) {
99 return createZCSwap(type, start, end, finalPayment);
100 }
101
102 ext::shared_ptr<ZeroCouponSwap> createZCSwap(const Date& start,
103 const Date& end,
104 Rate fixedRate) {
105 auto swap = ext::make_shared<ZeroCouponSwap>(args: Swap::Receiver, args&: baseNominal,
106 args: start, args: end, args&: fixedRate, args&: dayCount, args&: euribor,
107 args&: calendar, args&: businessConvention, args&: paymentDelay);
108 swap->setPricingEngine(discountEngine);
109 return swap;
110 }
111 };
112
113 void checkReplicationOfZeroCouponSwapNPV(const Date& start,
114 const Date& end,
115 Swap::Type type = Swap::Receiver) {
116 CommonVars vars;
117 const Real tolerance = 1.0e-8;
118
119 auto zcSwap = vars.createZCSwap(type, start, end);
120
121 Real actualNPV = zcSwap->NPV();
122 Real actualFixedLegNPV = zcSwap->fixedLegNPV();
123 Real actualFloatLegNPV = zcSwap->floatingLegNPV();
124
125 Date paymentDate =
126 vars.calendar.advance(date: end, period: vars.paymentDelay * Days, convention: vars.businessConvention);
127 Real discountAtPayment =
128 paymentDate < vars.settlement ? 0.0 : vars.euriborHandle->discount(d: paymentDate);
129 Real expectedFixedLegNPV = -type * discountAtPayment * vars.finalPayment;
130
131 auto subPeriodCpn = vars.createSubPeriodsCoupon(start, end);
132 Real expectedFloatLegNPV =
133 paymentDate < vars.settlement ? 0.0 : Real(Integer(type) * discountAtPayment * subPeriodCpn->amount());
134
135 Real expectedNPV = expectedFloatLegNPV + expectedFixedLegNPV;
136
137 if ((std::fabs(x: actualNPV - expectedNPV) > tolerance) ||
138 (std::fabs(x: actualFixedLegNPV - expectedFixedLegNPV) > tolerance) ||
139 (std::fabs(x: actualFloatLegNPV - expectedFloatLegNPV) > tolerance))
140 BOOST_ERROR("unable to replicate NPVs of zero coupon swap and its legs\n"
141 << " actual NPV: " << actualNPV << "\n"
142 << " expected NPV: " << expectedNPV << "\n"
143 << " actual fixed leg NPV: " << actualFixedLegNPV << "\n"
144 << " expected fixed leg NPV: " << expectedFixedLegNPV << "\n"
145 << " actual float leg NPV: " << actualFloatLegNPV << "\n"
146 << " expected float leg NPV: " << expectedFloatLegNPV << "\n"
147 << " start: " << start << "\n"
148 << " end: " << end << "\n"
149 << " type: " << type << "\n");
150 }
151
152 void checkFairFixedPayment(const Date& start,
153 const Date& end,
154 Swap::Type type) {
155 CommonVars vars;
156 const Real tolerance = 1.0e-8;
157
158 auto zcSwap = vars.createZCSwap(type, start, end);
159 Real fairFixedPayment = zcSwap->fairFixedPayment();
160 auto parZCSwap = vars.createZCSwap(type, start, end, finalPayment: fairFixedPayment);
161 Real parZCSwapNPV = parZCSwap->NPV();
162
163 if ((std::fabs(x: parZCSwapNPV) > tolerance))
164 BOOST_ERROR("unable to replicate fair fixed payment\n"
165 << " actual NPV: " << parZCSwapNPV << "\n"
166 << " expected NPV: 0.0\n"
167 << " fair fixed payment: " << fairFixedPayment << "\n"
168 << " start: " << start << "\n"
169 << " end: " << end << "\n"
170 << " type: " << type << "\n");
171 }
172
173 void checkFairFixedRate(const Date& start, const Date& end, Swap::Type type) {
174 CommonVars vars;
175 const Real tolerance = 1.0e-8;
176
177 auto zcSwap = vars.createZCSwap(type, start, end);
178 Rate fairFixedRate = zcSwap->fairFixedRate(dayCounter: vars.dayCount);
179 auto parZCSwap = vars.createZCSwap(start, end, fixedRate: fairFixedRate);
180 Real parZCSwapNPV = parZCSwap->NPV();
181
182 if ((std::fabs(x: parZCSwapNPV) > tolerance))
183 BOOST_ERROR("unable to replicate fair fixed rate\n"
184 << " actual NPV: " << parZCSwapNPV << "\n"
185 << " expected NPV: 0.0\n"
186 << " fair fixed rate: " << fairFixedRate << "\n"
187 << " start: " << start << "\n"
188 << " end: " << end << "\n"
189 << " type: " << type << "\n");
190 }
191}
192
193void ZeroCouponSwapTest::testInstrumentValuation() {
194 BOOST_TEST_MESSAGE("Testing zero coupon swap valuation...");
195
196 using namespace zerocouponswap_test;
197
198 // Ongoing instrument
199 checkReplicationOfZeroCouponSwapNPV(start: Date(12, February, 2021), end: Date(12, February, 2041),
200 type: Swap::Receiver);
201 // Forward starting instrument
202 checkReplicationOfZeroCouponSwapNPV(start: Date(15, April, 2021), end: Date(12, February, 2041),
203 type: Swap::Payer);
204
205 // Expired instrument
206 checkReplicationOfZeroCouponSwapNPV(start: Date(12, February, 2000), end: Date(12, February, 2020));
207}
208
209void ZeroCouponSwapTest::testFairFixedPayment() {
210 BOOST_TEST_MESSAGE("Testing fair fixed payment...");
211
212 using namespace zerocouponswap_test;
213
214 // Ongoing instrument
215 checkFairFixedPayment(start: Date(12, February, 2021), end: Date(12, February, 2041),
216 type: Swap::Receiver);
217
218 // Spot starting instrument
219 checkFairFixedPayment(start: Date(17, March, 2021), end: Date(12, February, 2041),
220 type: Swap::Payer);
221}
222
223void ZeroCouponSwapTest::testFairFixedRate() {
224 BOOST_TEST_MESSAGE("Testing fair fixed rate...");
225
226 using namespace zerocouponswap_test;
227
228 // Ongoing instrument
229 checkFairFixedRate(start: Date(12, February, 2021), end: Date(12, February, 2041),
230 type: Swap::Receiver);
231
232 // Spot starting instrument
233 checkFairFixedRate(start: Date(17, March, 2021), end: Date(12, February, 2041), type: Swap::Payer);
234}
235
236void ZeroCouponSwapTest::testFixedPaymentFromRate() {
237 BOOST_TEST_MESSAGE("Testing fixed payment calculation from rate...");
238
239 using namespace zerocouponswap_test;
240
241 CommonVars vars;
242 const Real tolerance = 1.0e-8;
243 const Rate fixedRate = 0.01;
244
245 Date start(12, February, 2021);
246 Date end(12, February, 2041);
247
248 auto zcSwap = vars.createZCSwap(start, end, fixedRate);
249 Real actualFxdPmt = zcSwap->fixedPayment();
250
251 Time T = vars.dayCount.yearFraction(d1: start, d2: end);
252 Real expectedFxdPmt = zcSwap->baseNominal() * (std::pow(x: 1.0 + fixedRate, y: T) - 1.0);
253
254 if ((std::fabs(x: actualFxdPmt - expectedFxdPmt) > tolerance))
255 BOOST_ERROR("unable to replicate fixed payment from rate\n"
256 << " actual fixed payment: " << actualFxdPmt << "\n"
257 << " expected fixed payment: " << expectedFxdPmt << "\n"
258 << " start: " << start << "\n"
259 << " end: " << end << "\n");
260}
261
262void ZeroCouponSwapTest::testArgumentsValidation() {
263 BOOST_TEST_MESSAGE("Testing arguments validation...");
264
265 using namespace zerocouponswap_test;
266
267 CommonVars vars;
268
269 Date start(12, February, 2021);
270 Date end(12, February, 2041);
271
272 // Negative base nominal
273 BOOST_CHECK_THROW(vars.createZCSwap(Swap::Payer, start, end, -1.0e6, 1.0e6),
274 Error);
275
276 // Start date after end date
277 BOOST_CHECK_THROW(vars.createZCSwap(end, start, 0.01), Error);
278}
279
280void ZeroCouponSwapTest::testExpectedCashFlowsInLegs() {
281 BOOST_TEST_MESSAGE("Testing expected cash flows in legs...");
282
283 using namespace zerocouponswap_test;
284
285 CommonVars vars;
286 const Real tolerance = 1.0e-8;
287
288 Date start(12, February, 2021);
289 Date end(12, February, 2041);
290
291 auto zcSwap = vars.createZCSwap(start, end, fixedRate: 0.01);
292 auto fixedCashFlow = zcSwap->fixedLeg()[0];
293 auto floatingCashFlow = zcSwap->floatingLeg()[0];
294
295 Date paymentDate =
296 vars.calendar.advance(date: end, period: vars.paymentDelay * Days, convention: vars.businessConvention);
297 auto subPeriodCpn = vars.createSubPeriodsCoupon(start, end);
298
299 if ((std::fabs(x: fixedCashFlow->amount() - zcSwap->fixedPayment()) > tolerance) ||
300 (fixedCashFlow->date() != paymentDate))
301 BOOST_ERROR("unable to replicate fixed leg\n"
302 << " actual amount: " << fixedCashFlow->amount() << "\n"
303 << " expected amount: " << zcSwap->fixedPayment() << "\n"
304 << " actual payment date: " << fixedCashFlow->date() << "\n"
305 << " expected payment date: " << paymentDate << "\n");
306
307 if ((std::fabs(x: floatingCashFlow->amount() - subPeriodCpn->amount()) > tolerance) ||
308 (floatingCashFlow->date() != paymentDate))
309 BOOST_ERROR("unable to replicate floating leg\n"
310 << " actual amount: " << floatingCashFlow->amount() << "\n"
311 << " expected amount: " << subPeriodCpn->amount() << "\n"
312 << " actual payment date: " << floatingCashFlow->date() << "\n"
313 << " expected payment date: " << paymentDate << "\n");
314}
315
316test_suite* ZeroCouponSwapTest::suite() {
317 auto* suite = BOOST_TEST_SUITE("Zero coupon swap tests");
318
319 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testInstrumentValuation));
320 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testFairFixedPayment));
321 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testFairFixedRate));
322 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testFixedPaymentFromRate));
323 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testArgumentsValidation));
324 suite->add(QUANTLIB_TEST_CASE(&ZeroCouponSwapTest::testExpectedCashFlowsInLegs));
325
326 return suite;
327}
328

source code of quantlib/test-suite/zerocouponswap.cpp