Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
crossccyfixfloatswaphelper.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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#include "toplevelfixture.hpp"
20#include <boost/make_shared.hpp>
21#include <boost/test/unit_test.hpp>
22#include <ql/currencies/all.hpp>
23#include <ql/indexes/ibor/usdlibor.hpp>
24#include <ql/quotes/simplequote.hpp>
25#include <ql/termstructures/yield/discountcurve.hpp>
26#include <ql/termstructures/yield/flatforward.hpp>
27#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
28#include <ql/time/calendars/all.hpp>
29#include <ql/time/daycounters/actual360.hpp>
30#include <ql/types.hpp>
33
34using namespace std;
35using namespace boost::unit_test_framework;
36using namespace QuantLib;
37using namespace QuantExt;
38
39namespace {
40
41struct CommonVars {
42
43 Date asof;
44 Natural settlementDays;
45 Calendar payCalendar;
46 BusinessDayConvention payConvention;
47 Natural payLag;
48 Period tenor;
49 Currency fixedCurrency;
50 Frequency fixedFrequency;
51 DayCounter fixedDayCount;
52 Real usdNominal;
53
54 // 5Y TRY annual fixed rate vs. USD 3M Libor
55 Handle<Quote> rate;
56 // USD/TRY spot FX rate
57 QuantLib::ext::shared_ptr<SimpleQuote> spotFx;
58 // Spread on float leg of swap
59 QuantLib::ext::shared_ptr<SimpleQuote> spread;
60 // USD Libor 3M projection curve
61 Handle<YieldTermStructure> liborProjCurve;
62 // USD Libor 3M index
63 QuantLib::ext::shared_ptr<IborIndex> index;
64 // USD discount curve
65 Handle<YieldTermStructure> usdDiscCurve;
66
67 // Hold the helper created during testing
68 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwapHelper> helper;
69
70 CommonVars() {
71 asof = Date(11, Sep, 2018);
72 settlementDays = 2;
73 payCalendar = JointCalendar(UnitedStates(UnitedStates::Settlement), UnitedKingdom(), Turkey());
74 payConvention = Following;
75 payLag = 0;
76 tenor = 5 * Years;
77 fixedCurrency = TRYCurrency();
78 fixedFrequency = Annual;
79 fixedDayCount = Actual360();
80 usdNominal = 10000000.0;
81
82 rate = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.25));
83 spotFx = QuantLib::ext::make_shared<SimpleQuote>(6.4304);
84 spread = QuantLib::ext::make_shared<SimpleQuote>(0.0);
85 liborProjCurve =
86 Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.029773, Actual365Fixed()));
87 index = QuantLib::ext::make_shared<USDLibor>(3 * Months, liborProjCurve);
88 usdDiscCurve =
89 Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.026727, Actual365Fixed()));
90 }
91};
92
93QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> makeTestSwap(const CommonVars& vars,
94 const Handle<YieldTermStructure>& discCurve) {
95
96 // Swap start and end date
97 Date referenceDate = Settings::instance().evaluationDate();
98 referenceDate = vars.payCalendar.adjust(referenceDate);
99 Date start = vars.payCalendar.advance(referenceDate, vars.settlementDays * Days);
100 Date end = start + vars.tenor;
101
102 // Fixed TRY schedule
103 Schedule fixedSchedule(start, end, Period(vars.fixedFrequency), vars.payCalendar, vars.payConvention,
104 vars.payConvention, DateGeneration::Backward, false);
105
106 // Float USD schedule
107 Schedule floatSchedule(start, end, vars.index->tenor(), vars.payCalendar, vars.payConvention, vars.payConvention,
108 DateGeneration::Backward, false);
109
110 // Create swap
111 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> swap(new CrossCcyFixFloatSwap(
112 CrossCcyFixFloatSwap::Payer, vars.usdNominal * vars.spotFx->value(), vars.fixedCurrency, fixedSchedule,
113 vars.rate->value(), vars.fixedDayCount, vars.payConvention, vars.payLag, vars.payCalendar, vars.usdNominal,
114 vars.index->currency(), floatSchedule, vars.index, vars.spread->value(), vars.payConvention, vars.payLag,
115 vars.payCalendar));
116
117 // Attach pricing engine
118 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<CrossCcySwapEngine>(
119 vars.fixedCurrency, discCurve, vars.index->currency(), vars.usdDiscCurve, Handle<Quote>(vars.spotFx));
120 swap->setPricingEngine(engine);
121
122 return swap;
123}
124
125// Use the helper that we are testing to create a bootstrapped curve
126Handle<YieldTermStructure> bootstrappedCurve(CommonVars& vars) {
127
128 // Create a helper
129 vector<QuantLib::ext::shared_ptr<RateHelper> > helpers(1);
130 vars.helper.reset(new CrossCcyFixFloatSwapHelper(
131 vars.rate, Handle<Quote>(vars.spotFx), vars.settlementDays, vars.payCalendar, vars.payConvention, vars.tenor,
132 vars.fixedCurrency, vars.fixedFrequency, vars.payConvention, vars.fixedDayCount, vars.index, vars.usdDiscCurve,
133 Handle<Quote>(vars.spread)));
134 helpers[0] = vars.helper;
135
136 // Create a yield curve referencing the helper
137 return Handle<YieldTermStructure>(
138 QuantLib::ext::make_shared<PiecewiseYieldCurve<Discount, LogLinear> >(0, NullCalendar(), helpers, Actual365Fixed()));
139}
140
141} // namespace
142
143BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
144
145BOOST_AUTO_TEST_SUITE(CrossCurrencyFixFloatSwapHelperTest)
146
147BOOST_AUTO_TEST_CASE(testBootstrap) {
148
149 BOOST_TEST_MESSAGE("Test simple bootstrap against cross currency fix float swap");
150
151 SavedSettings backup;
152
153 CommonVars vars;
154
155 Settings::instance().evaluationDate() = vars.asof;
156
157 // Create a helper and bootstrapped curve
158 Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);
159
160 // Create the helper swap manually and price it using curve bootstrapped from helper
161 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);
162
163 // Swap should have NPV = 0.0. On notional = $10M i.e. TRY60.5M, 1e-5 is enough.
164 Real absTol = 1e-5;
165 BOOST_CHECK_SMALL(swap->NPV(), absTol);
166
167 // Check fair fixed rates match. Bootstrap uses 1e-12 accuracy.
168 Real relTol = 1e-10;
169 BOOST_CHECK_CLOSE(vars.rate->value(), swap->fairFixedRate(), relTol);
170
171 // Check the 5Y discount factor
172 DiscountFactor expDisc = 0.3299260408883904;
173 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
174}
175
176BOOST_AUTO_TEST_CASE(testSpotFxChange) {
177
178 BOOST_TEST_MESSAGE("Test rebootstrap under spot FX change");
179
180 SavedSettings backup;
181
182 CommonVars vars;
183
184 Settings::instance().evaluationDate() = vars.asof;
185
186 // Create a helper and bootstrapped curve
187 Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);
188
189 // Create the helper swap manually and price it using curve bootstrapped from helper
190 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);
191
192 // Check NPV = 0.0
193 Real absTol = 1e-5;
194 BOOST_CHECK_SMALL(swap->NPV(), absTol);
195
196 // Check the 5Y discount factor
197 Real relTol = 1e-10;
198 DiscountFactor expDisc = 0.3299260408883904;
199 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
200
201 // Check the nominal of the helper swap
202 BOOST_CHECK_CLOSE(vars.spotFx->value(), vars.helper->swap()->fixedNominal(), relTol);
203
204 // Bump the spot rate by 10%
205 vars.spotFx->setValue(vars.spotFx->value() * 1.1);
206
207 // Build a new swap using the updated spot FX rate
208 swap = makeTestSwap(vars, tryDiscCurve);
209
210 // Check that the new swap's NPV is 0.0
211 BOOST_CHECK_SMALL(swap->NPV(), absTol);
212
213 // Check the 5Y discount factor again. It should be the same.
214 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
215
216 // Check the nominal of the helper swap. Should now be the bumped amount
217 BOOST_CHECK_CLOSE(vars.spotFx->value(), vars.helper->swap()->fixedNominal(), relTol);
218}
219
220BOOST_AUTO_TEST_CASE(testSpreadChange) {
221
222 BOOST_TEST_MESSAGE("Test rebootstrap under helper spread change");
223
224 SavedSettings backup;
225
226 CommonVars vars;
227
228 Settings::instance().evaluationDate() = vars.asof;
229
230 // Create a helper and bootstrapped curve
231 Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);
232
233 // Create the helper swap manually and price it using curve bootstrapped from helper
234 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);
235
236 // Check NPV = 0.0
237 Real absTol = 1e-5;
238 BOOST_CHECK_SMALL(swap->NPV(), absTol);
239
240 // Check the 5Y discount factor
241 Real relTol = 1e-10;
242 DiscountFactor expDisc = 0.3299260408883904;
243 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
244
245 // Check the spread of the helper swap
246 BOOST_CHECK_CLOSE(vars.spread->value(), vars.helper->swap()->floatSpread(), relTol);
247
248 // Add a 10bps spread
249 vars.spread->setValue(0.0010);
250
251 // Build a new swap using the updated spread of 10bps
252 swap = makeTestSwap(vars, tryDiscCurve);
253
254 // Check that the new swap's NPV is 0.0
255 BOOST_CHECK_SMALL(swap->NPV(), absTol);
256
257 // Check the 5Y discount factor again. Added spread on float => higher discount factor.
258 expDisc = 0.3322218009717460;
259 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
260
261 // Check the spread of the helper swap. Should now be 10bps.
262 BOOST_CHECK_CLOSE(vars.spread->value(), vars.helper->swap()->floatSpread(), relTol);
263}
264
265BOOST_AUTO_TEST_CASE(testMovingEvaluationDate) {
266
267 BOOST_TEST_MESSAGE("Test rebootstrap after moving evaluation date");
268
269 SavedSettings backup;
270
271 CommonVars vars;
272
273 Settings::instance().evaluationDate() = vars.asof;
274
275 // Create a helper and bootstrapped curve
276 Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);
277
278 // Create the helper swap manually and price it using curve bootstrapped from helper
279 QuantLib::ext::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);
280
281 // Check NPV = 0.0
282 Real absTol = 1e-5;
283 BOOST_CHECK_SMALL(swap->NPV(), absTol);
284
285 // Check the 5Y discount factor
286 Real relTol = 1e-10;
287 DiscountFactor expDisc = 0.3299260408883904;
288 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
289
290 // Check the start date of the helper swap
291 BOOST_CHECK_EQUAL(swap->startDate(), vars.helper->swap()->startDate());
292
293 // Move evaluation date forward
294 vars.asof = vars.asof + 1 * Days;
295 Settings::instance().evaluationDate() = vars.asof;
296
297 // Build a new swap using new evaluation date
298 swap = makeTestSwap(vars, tryDiscCurve);
299
300 // Check that the new swap's NPV is 0.0
301 BOOST_CHECK_SMALL(swap->NPV(), absTol);
302
303 // Check the 5Y discount factor again. Changes slightly due to helper holidays/weekends.
304 expDisc = 0.3299334970640459;
305 BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
306
307 // Check the start date of the helper swap. Should be 1 day greater.
308 BOOST_CHECK_EQUAL(swap->startDate(), vars.helper->swap()->startDate());
309}
310
311BOOST_AUTO_TEST_SUITE_END()
312
313BOOST_AUTO_TEST_SUITE_END()
Cross currency fix vs. float swap helper.
Cross currency fixed vs. float swap helper.
Cross currency swap engine.
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
BOOST_AUTO_TEST_CASE(testBootstrap)
Fixture that can be used at top level.