Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
cms.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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 <boost/make_shared.hpp>
20#include <boost/test/unit_test.hpp>
30#include <oret/toplevelfixture.hpp>
31#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
32#include <ql/termstructures/yield/flatforward.hpp>
33#include <ql/time/calendars/target.hpp>
34#include <ql/time/daycounters/actualactual.hpp>
35
36#include <iostream>
37
38using namespace QuantLib;
39using namespace boost::unit_test_framework;
40using namespace std;
41using namespace ore::data;
42
43// CMS Swap test
44
45namespace {
46
47class TestMarket : public MarketImpl {
48public:
49 TestMarket() : MarketImpl(false) {
50 asof_ = Date(3, Feb, 2016);
51
52 // build discount
53 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")] = flatRateYts(0.02);
54
55 // build swaption vols
56 swaptionCurves_[make_pair(Market::defaultConfiguration, "EUR")] = flatRateSvs(0.1);
57
58 // build ibor index
59 Handle<IborIndex> hEUR(ore::data::parseIborIndex(
60 "EUR-EURIBOR-6M", yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")]));
61 iborIndices_[make_pair(Market::defaultConfiguration, "EUR-EURIBOR-6M")] = hEUR;
62
63 QuantLib::ext::shared_ptr<Conventions> conventions = QuantLib::ext::make_shared<Conventions>();
64
65 // add swap index
66 QuantLib::ext::shared_ptr<ore::data::Convention> swapEURConv(new ore::data::IRSwapConvention(
67 "EUR-6M-SWAP-CONVENTIONS", "TARGET", "Annual", "MF", "30/360", "EUR-EURIBOR-6M"));
68 conventions->add(swapEURConv);
69 QuantLib::ext::shared_ptr<ore::data::Convention> swapIndexEURLongConv(
70 new ore::data::SwapIndexConvention("EUR-CMS-30Y", "EUR-6M-SWAP-CONVENTIONS"));
71 conventions->add(swapIndexEURLongConv);
72
73 InstrumentConventions::instance().setConventions(conventions);
74
75 addSwapIndex("EUR-CMS-30Y", "EUR-EURIBOR-6M", Market::defaultConfiguration);
76 }
77
78private:
79 Handle<YieldTermStructure> flatRateYts(Real forward) {
80 QuantLib::ext::shared_ptr<YieldTermStructure> yts(new FlatForward(0, NullCalendar(), forward, ActualActual(ActualActual::ISDA)));
81 return Handle<YieldTermStructure>(yts);
82 }
83 Handle<SwaptionVolatilityStructure> flatRateSvs(Volatility forward) {
84 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> Svs(
85 new ConstantSwaptionVolatility(0, NullCalendar(), ModifiedFollowing, forward, ActualActual(ActualActual::ISDA)));
86 return Handle<SwaptionVolatilityStructure>(Svs);
87 }
88};
89
90struct CommonVars {
91 // global data
92 string ccy;
93 bool isPayer;
94 string start;
95 string end;
96 string fixtenor;
97 string cmstenor;
98 Calendar cal;
99 string calStr;
100 string conv;
101 string rule;
102 Size days;
103 string fixDC;
104 Real fixedRate;
105 string index;
106 int fixingdays;
107 bool isinarrears;
108 double notional;
109 string longShort;
110 vector<double> notionals;
111 vector<double> spread;
112 vector<string> spreadDates;
113
114 // utilities
115 QuantLib::ext::shared_ptr<ore::data::Swap> makeSwap() {
116 ScheduleData fixedSchedule(ScheduleRules(start, end, fixtenor, calStr, conv, conv, rule));
117 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
118
119 // build CMSSwap
120 LegData fixedLegData(QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, fixedRate)), !isPayer, ccy,
121 fixedSchedule, fixDC, notionals);
122 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread), isPayer, ccy,
123 cmsSchedule, fixDC, notionals);
124
125 Envelope env("CP1");
126 QuantLib::ext::shared_ptr<ore::data::Swap> swap(new ore::data::Swap(env, fixedLegData, cmsLegData));
127 return swap;
128 }
129
130 QuantLib::ext::shared_ptr<ore::data::Swap> makeSwap(Real fixedRate_, string fixtenor_) {
131 ScheduleData fixedSchedule(ScheduleRules(start, end, fixtenor_, calStr, conv, conv, rule));
132 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
133
134 // build CMSSwap
135 LegData fixedLegData(QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, fixedRate_)), !isPayer, ccy,
136 fixedSchedule, fixDC, notionals);
137 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread), isPayer, ccy,
138 cmsSchedule, fixDC, notionals);
139
140 Envelope env("CP1");
141 QuantLib::ext::shared_ptr<ore::data::Swap> swap(new ore::data::Swap(env, fixedLegData, cmsLegData));
142 return swap;
143 }
144
145 QuantLib::ext::shared_ptr<ore::data::Swap> makeCmsLegSwap() {
146 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
147
148 vector<LegData> legData;
149 CMSLegData cmsLegRateData;
150 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread), isPayer, ccy,
151 cmsSchedule, fixDC, notionals);
152 legData.push_back(cmsLegData);
153
154 Envelope env("CP1");
155 QuantLib::ext::shared_ptr<ore::data::Swap> swap(new ore::data::Swap(env, legData));
156 return swap;
157 }
158
159 QuantLib::ext::shared_ptr<ore::data::Swap> makeCappedCmsLegSwap(vector<double> caps,
160 vector<string> capDates = vector<string>()) {
161 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
162
163 vector<LegData> legData;
164 LegData cmsLegData(
165 QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread, spreadDates, caps, capDates),
166 isPayer, ccy, cmsSchedule, fixDC, notionals);
167 legData.push_back(cmsLegData);
168
169 Envelope env("CP1");
170 QuantLib::ext::shared_ptr<ore::data::Swap> swap(new ore::data::Swap(env, legData));
171 return swap;
172 }
173
174 QuantLib::ext::shared_ptr<ore::data::CapFloor> makeCap(vector<double> caps) {
175 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
176
177 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread, spreadDates), isPayer,
178 ccy, cmsSchedule, fixDC, notionals);
179
180 Envelope env("CP1");
181 QuantLib::ext::shared_ptr<ore::data::CapFloor> capfloor(
182 new ore::data::CapFloor(env, longShort, cmsLegData, caps, vector<double>()));
183 return capfloor;
184 }
185
186 QuantLib::ext::shared_ptr<ore::data::Swap> makeFlooredCmsLegSwap(vector<double> floors,
187 vector<string> floorDates = vector<string>()) {
188 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
189
190 vector<LegData> legData;
191 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread, spreadDates,
192 vector<double>(), vector<string>(), floors, floorDates),
193 isPayer, ccy, cmsSchedule, fixDC, notionals);
194 legData.push_back(cmsLegData);
195
196 Envelope env("CP1");
197 QuantLib::ext::shared_ptr<ore::data::Swap> swap(new ore::data::Swap(env, legData));
198 return swap;
199 }
200
201 QuantLib::ext::shared_ptr<ore::data::CapFloor> makeFloor(vector<double> floors) {
202 ScheduleData cmsSchedule(ScheduleRules(start, end, cmstenor, calStr, conv, conv, rule));
203
204 LegData cmsLegData(QuantLib::ext::make_shared<CMSLegData>(index, fixingdays, isinarrears, spread, spreadDates), isPayer,
205 ccy, cmsSchedule, fixDC, notionals);
206
207 Envelope env("CP1");
208 QuantLib::ext::shared_ptr<ore::data::CapFloor> capfloor(
209 new ore::data::CapFloor(env, longShort, cmsLegData, vector<double>(), floors));
210 return capfloor;
211 }
212
213 CommonVars() : ccy("EUR"), isPayer(false), start("20160301"), end("20360301"), fixtenor("1Y"), cmstenor("6M") {
214
215 longShort = isPayer ? "Short" : "Long";
216 cal = TARGET();
217 calStr = "TARGET";
218 conv = "MF";
219 rule = "Forward";
220 fixDC = "ACT/360";
221 fixedRate = 0.0;
222 index = "EUR-CMS-30Y";
223 fixingdays = 2;
224 isinarrears = false;
225 notional = 10000000;
226 notionals.push_back(10000000);
227 spread.push_back(0.0);
228 }
229};
230
231void outputCoupons(QuantLib::ext::shared_ptr<ore::data::Swap> cmsSwap) {
232 Leg leg = cmsSwap->legs().at(1);
233 for (auto cf : leg) {
234 QuantLib::ext::shared_ptr<FloatingRateCoupon> frc = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(cf);
235 BOOST_TEST_MESSAGE("Coupon Date: " << frc->date() << "; Rate: " << frc->rate()
236 << "; DayCount: " << frc->dayCounter());
237 }
238};
239} // namespace
240
241BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
242
243BOOST_AUTO_TEST_SUITE(CmsTests)
244
245BOOST_AUTO_TEST_CASE(testCMSAnalyticHagan) {
246 BOOST_TEST_MESSAGE("Testing CMS Analytic Hagan price...");
247
248 // build market
249 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
250 Settings::instance().evaluationDate() = market->asofDate();
251 CommonVars vars;
252 QuantLib::ext::shared_ptr<ore::data::Swap> cmsSwap = vars.makeSwap();
253
254 // Build and price
255 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
256 engineData->model("CMS") = "Hagan";
257 engineData->engine("CMS") = "Analytic";
258 map<string, string> engineparams;
259 engineparams["YieldCurveModel"] = "Standard";
260 engineparams["MeanReversion"] = "0.0";
261 engineData->engineParameters("CMS") = engineparams;
262
263 engineData->model("Swap") = "DiscountedCashflows";
264 engineData->engine("Swap") = "DiscountingSwapEngineOptimised";
265
266 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
267
268 cmsSwap->build(engineFactory);
269
270 Real expectedNPV = 3440673.46;
271 Real npv = cmsSwap->instrument()->NPV();
272
273 BOOST_TEST_MESSAGE("Hagan Analytic price is " << npv);
274 outputCoupons(cmsSwap);
275
276 BOOST_CHECK_CLOSE(npv, expectedNPV, 1.0);
277}
278
279BOOST_AUTO_TEST_CASE(testCMSNumericalHagan) {
280 BOOST_TEST_MESSAGE("Testing CMS Numerical Hagan price...");
281
282 // build market
283 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
284 Settings::instance().evaluationDate() = market->asofDate();
285
286 CommonVars vars;
287 QuantLib::ext::shared_ptr<ore::data::Swap> cmsSwap = vars.makeSwap();
288
289 // Build and price
290 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
291 engineData->model("CMS") = "Hagan";
292 engineData->engine("CMS") = "Numerical";
293 map<string, string> engineparams;
294 engineparams["YieldCurveModel"] = "Standard";
295 engineparams["MeanReversion"] = "0.0";
296 engineparams["UpperLimit"] = "0.0";
297 engineparams["LowerLimit"] = "1.0";
298 engineparams["Precision"] = "0.000001";
299 engineData->engineParameters("CMS") = engineparams;
300
301 engineData->model("Swap") = "DiscountedCashflows";
302 engineData->engine("Swap") = "DiscountingSwapEngineOptimised";
303
304 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
305
306 cmsSwap->build(engineFactory);
307
308 Real expectedNPV = 3440673.46;
309 Real npv = cmsSwap->instrument()->NPV();
310
311 BOOST_TEST_MESSAGE("Hagan Numerical price is " << npv);
312 outputCoupons(cmsSwap);
313
314 BOOST_CHECK_CLOSE(npv, expectedNPV, 1.0);
315}
316
317BOOST_AUTO_TEST_CASE(testCMSLinearTsr) {
318 BOOST_TEST_MESSAGE("Testing CMS Linear TSR price...");
319
320 // build market
321 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
322 Settings::instance().evaluationDate() = market->asofDate();
323
324 CommonVars vars;
325 QuantLib::ext::shared_ptr<ore::data::Swap> cmsSwap = vars.makeSwap();
326
327 // Build and price
328 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
329 engineData->model("CMS") = "LinearTSR";
330 engineData->engine("CMS") = "LinearTSRPricer";
331 map<string, string> engineparams;
332 engineparams["MeanReversion"] = "0.0";
333 engineparams["Policy"] = "RateBound";
334 engineparams["LowerRateBoundLogNormal"] = "0.0001";
335 engineparams["UpperRateBoundLogNormal"] = "2.0000";
336 engineData->engineParameters("CMS") = engineparams;
337
338 engineData->model("Swap") = "DiscountedCashflows";
339 engineData->engine("Swap") = "DiscountingSwapEngineOptimised";
340
341 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
342
343 cmsSwap->build(engineFactory);
344
345 Real expectedNPV = 3440673.46;
346 Real npv = cmsSwap->instrument()->NPV();
347
348 BOOST_TEST_MESSAGE("Linear TSR price is " << npv);
349 outputCoupons(cmsSwap);
350
351 BOOST_CHECK_CLOSE(npv, expectedNPV, 1.0);
352}
353
355 BOOST_TEST_MESSAGE("Testing CMS CapFloor price...");
356
357 // build market
358 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
359 Settings::instance().evaluationDate() = market->asofDate();
360
361 CommonVars vars;
362 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
363 engineData->model("CMS") = "Hagan";
364 engineData->engine("CMS") = "Analytic";
365 map<string, string> engineparams;
366 engineparams["YieldCurveModel"] = "Standard";
367 engineparams["MeanReversion"] = "0.0";
368 engineData->engineParameters("CMS") = engineparams;
369
370 engineData->model("Swap") = "DiscountedCashflows";
371 engineData->engine("Swap") = "DiscountingSwapEngineOptimised";
372
373 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
374
375 BOOST_TEST_MESSAGE(
376 "Comparing CMS Cap price to replication by a Single Legged CMS Swap and a Single Leg Capped CMS Swap...");
377 vector<double> capRate(1, 0.021);
378 QuantLib::ext::shared_ptr<ore::data::Swap> cmsLegSwap = vars.makeCmsLegSwap();
379 QuantLib::ext::shared_ptr<ore::data::Swap> cappedCmsLegSwap = vars.makeCappedCmsLegSwap(capRate);
380 QuantLib::ext::shared_ptr<ore::data::CapFloor> cap = vars.makeCap(capRate);
381
382 cmsLegSwap->build(engineFactory);
383 cappedCmsLegSwap->build(engineFactory);
384 cap->build(engineFactory);
385
386 Real cmsLegNpv = cmsLegSwap->instrument()->NPV();
387 Real cappedCmsLegNpv = cappedCmsLegSwap->instrument()->NPV();
388 Real capNpv = cap->instrument()->NPV();
389
390 Real capBySwaps = cmsLegNpv - cappedCmsLegNpv;
391
392 BOOST_TEST_MESSAGE("CMS Leg swap NPV is " << cmsLegNpv);
393 BOOST_TEST_MESSAGE("CMS Capped Leg swap NPV is " << cappedCmsLegNpv);
394 BOOST_TEST_MESSAGE("CMS Cap NPV is " << capNpv);
395 BOOST_TEST_MESSAGE("CMS Cap NPV from Swap replication is " << capBySwaps);
396 BOOST_CHECK_CLOSE(capNpv, capBySwaps, 1.0);
397
398 BOOST_TEST_MESSAGE("Checking CMS Cap with high Cap is zero...");
399 vector<double> capHigh(1, 1.0);
400
401 cap = vars.makeCap(capHigh);
402 cap->build(engineFactory);
403 capNpv = cap->instrument()->NPV();
404 BOOST_TEST_MESSAGE("CMS Cap (Cap of 100%) NPV is " << capNpv);
405 BOOST_CHECK_SMALL(capNpv, 0.01);
406
407 BOOST_TEST_MESSAGE("Checking CMS Cap with low Cap is equal to single leg swap...");
408 vector<double> capLow(1, -1.0);
409
410 cap = vars.makeCap(capLow);
411 cap->build(engineFactory);
412 capNpv = cap->instrument()->NPV();
413 BOOST_TEST_MESSAGE("CMS Cap (Cap of -100%) NPV is " << capNpv);
414 BOOST_CHECK_CLOSE(capNpv, cmsLegNpv, 1.0);
415
416 BOOST_TEST_MESSAGE("Checking CMS Floor with low Cap is equal to zero...");
417 vector<double> floorLow(1, -1.0);
418
419 QuantLib::ext::shared_ptr<ore::data::CapFloor> floor = vars.makeFloor(floorLow);
420 floor->build(engineFactory);
421 Real floorNpv = floor->instrument()->NPV();
422 BOOST_TEST_MESSAGE("CMS Floor (Floor of -100%) NPV is " << floorNpv);
423 BOOST_CHECK_SMALL(floorNpv, 0.01);
424
425 BOOST_TEST_MESSAGE("Checking CMS Cap + CMS Floor = Swap...");
426 vector<double> floorRate(1, 0.021);
427
428 cap = vars.makeCap(capRate);
429 floor = vars.makeFloor(floorRate);
430 QuantLib::ext::shared_ptr<ore::data::Swap> swap = vars.makeSwap(0.021, "6M");
431 cap->build(engineFactory);
432 floor->build(engineFactory);
433 swap->build(engineFactory);
434 capNpv = cap->instrument()->NPV();
435 floorNpv = floor->instrument()->NPV();
436 Real swapNpv = swap->instrument()->NPV();
437 Real capFloorNpv = capNpv - floorNpv;
438 BOOST_TEST_MESSAGE("CMS Cap NPV is " << capNpv);
439 BOOST_TEST_MESSAGE("CMS Floor NPV is " << floorNpv);
440 BOOST_TEST_MESSAGE("CMS Cap + Floor NPV is " << capFloorNpv);
441 BOOST_TEST_MESSAGE("CMS Swap NPV is " << swapNpv);
442 BOOST_CHECK_CLOSE(capFloorNpv, swapNpv, 1.0);
443}
444
445BOOST_AUTO_TEST_SUITE_END()
446
447BOOST_AUTO_TEST_SUITE_END()
Ibor cap, floor or collar trade data model and serialization.
Serializable CMS Leg Data.
Definition: legdata.hpp:414
Serializable cap, floor, collar.
Definition: capfloor.hpp:37
Serializable object holding generic trade data, reporting dimensions.
Definition: envelope.hpp:51
Container for storing Interest Rate Swap conventions.
Serializable object holding leg data.
Definition: legdata.hpp:844
static const string defaultConfiguration
Default configuration label.
Definition: market.hpp:296
Market Implementation.
Definition: marketimpl.hpp:53
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
Definition: marketimpl.hpp:208
map< pair< string, string >, Handle< IborIndex > > iborIndices_
Definition: marketimpl.hpp:209
map< pair< string, string >, Handle< QuantLib::SwaptionVolatilityStructure > > swaptionCurves_
Definition: marketimpl.hpp:211
void addSwapIndex(const string &swapindex, const string &discountIndex, const string &configuration=Market::defaultConfiguration) const
add a swap index to the market
Definition: marketimpl.cpp:473
Serializable schedule data.
Definition: schedule.hpp:202
Serializable object holding schedule Rules data.
Definition: schedule.hpp:37
Serializable Swap, Single and Cross Currency.
Definition: swap.hpp:36
Container for storing Swap Index conventions.
builder that returns an engine to price capped floored ibor legs
A class to hold pricing engine parameters.
trade envelope data model and serialization
QuantLib::ext::shared_ptr< IborIndex > parseIborIndex(const string &s, const Handle< YieldTermStructure > &h)
Convert std::string to QuantLib::IborIndex.
Map text representations to QuantLib/QuantExt types.
leg data model and serialization
An implementation of the Market class that stores the required objects in maps.
trade schedule data model and serialization
Swap trade data model and serialization.
BOOST_AUTO_TEST_CASE(testCMSAnalyticHagan)
Definition: cms.cpp:245