Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
cpiswap.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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>
28#include <oret/toplevelfixture.hpp>
29#include <ql/cashflows/cpicoupon.hpp>
30#include <ql/cashflows/cpicouponpricer.hpp>
31#include <ql/cashflows/iborcoupon.hpp>
32#include <ql/indexes/inflation/ukrpi.hpp>
33#include <ql/termstructures/inflation/inflationhelpers.hpp>
34#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
35#include <ql/termstructures/yield/discountcurve.hpp>
36#include <ql/time/calendars/unitedkingdom.hpp>
37#include <ql/time/daycounters/actual365fixed.hpp>
38#include <ql/time/daycounters/actualactual.hpp>
39
40using namespace QuantLib;
41using namespace boost::unit_test_framework;
42using namespace ore::data;
43using std::vector;
44
45// Inflation CPI Swap paying fixed * CPI(t)/baseCPI * N
46// with last XNL N * (CPI(T)/baseCPI - 1)
47
48namespace {
49
50class TestMarket : public MarketImpl {
51public:
52 TestMarket() : MarketImpl(false) {
53 // valuation date
54 asof_ = Date(18, July, 2016);
55
56 // build vectors with dates and discount factors for GBP
57 vector<Date> datesGBP = {asof_,
58 asof_ + 6 * Months,
59 asof_ + 7 * Months,
60 asof_ + 8 * Months,
61 asof_ + 9 * Months,
62 asof_ + 10 * Months,
63 asof_ + 11 * Months,
64 asof_ + 12 * Months,
65 asof_ + 13 * Months,
66 asof_ + 14 * Months,
67 asof_ + 15 * Months,
68 asof_ + 16 * Months,
69 asof_ + 17 * Months,
70 asof_ + 18 * Months,
71 asof_ + 19 * Months,
72 asof_ + 20 * Months,
73 asof_ + 21 * Months,
74 asof_ + 22 * Months,
75 asof_ + 23 * Months,
76 asof_ + 2 * Years,
77 asof_ + 3 * Years,
78 asof_ + 4 * Years,
79 asof_ + 5 * Years,
80 asof_ + 6 * Years,
81 asof_ + 7 * Years,
82 asof_ + 8 * Years,
83 asof_ + 9 * Years,
84 asof_ + 10 * Years,
85 asof_ + 15 * Years,
86 asof_ + 20 * Years};
87
88 vector<DiscountFactor> dfsGBP = {1, 0.9955, 0.9953, 0.9947, 0.9941, 0.9933, 0.9924, 0.9914,
89 0.9908, 0.9901, 0.9895, 0.9888, 0.9881, 0.9874, 0.9868, 0.9862,
90 0.9855, 0.9849, 0.9842, 0.9836, 0.9743, 0.9634, 0.9510, 0.9361,
91 0.9192, 0.9011, 0.8822, 0.8637, 0.7792, 0.7079};
92
93 // build vectors with dates and inflation zc swap rates for UKRPI
94 vector<Date> datesZCII = {asof_,
95 asof_ + 1 * Years,
96 asof_ + 2 * Years,
97 asof_ + 3 * Years,
98 asof_ + 4 * Years,
99 asof_ + 5 * Years,
100 asof_ + 6 * Years,
101 asof_ + 7 * Years,
102 asof_ + 8 * Years,
103 asof_ + 9 * Years,
104 asof_ + 10 * Years,
105 asof_ + 12 * Years,
106 asof_ + 15 * Years,
107 asof_ + 20 * Years};
108
109 vector<Rate> ratesZCII = {2.825, 2.9425, 2.975, 2.983, 3.0, 3.01, 3.008,
110 3.009, 3.013, 3.0445, 3.044, 3.09, 3.109, 3.108};
111
112 // build UKRPI fixing history
113 Schedule fixingDatesUKRPI =
114 MakeSchedule().from(Date(1, May, 2015)).to(Date(1, July, 2016)).withTenor(1 * Months);
115 Real fixingRatesUKRPI[] = {258.5, 258.9, 258.6, 259.8, 259.6, 259.5, 259.8, 260.6,
116 258.8, 260.0, 261.1, 261.4, 262.1, -999.0, -999.0};
117
118 // build GBP discount curve
119 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "GBP")] =
120 intDiscCurve(datesGBP, dfsGBP, ActualActual(ActualActual::ISDA), UnitedKingdom());
121
122 // build GBP Libor index
123 hGBP = Handle<IborIndex>(
124 parseIborIndex("GBP-LIBOR-6M", intDiscCurve(datesGBP, dfsGBP, ActualActual(ActualActual::ISDA), UnitedKingdom())));
125 iborIndices_[make_pair(Market::defaultConfiguration, "GBP-LIBOR-6M")] = hGBP;
126
127 // add Libor 6M fixing (lag for GBP is 0d)
128 hGBP->addFixing(Date(18, July, 2016), 0.0061731);
129
130 // build UKRPI index
131 QuantLib::ext::shared_ptr<UKRPI> ii;
132 QuantLib::ext::shared_ptr<ZeroInflationTermStructure> cpiTS;
133 RelinkableHandle<ZeroInflationTermStructure> hcpi;
134 ii = QuantLib::ext::shared_ptr<UKRPI>(new UKRPI(hcpi));
135 for (Size i = 0; i < fixingDatesUKRPI.size(); i++) {
136 // std::cout << i << ", " << fixingDatesUKRPI[i] << ", " << fixingRatesUKRPI[i] << std::endl;
137 ii->addFixing(fixingDatesUKRPI[i], fixingRatesUKRPI[i], true);
138 };
139 // now build the helpers ...
140 vector<QuantLib::ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure>>> instruments;
141 for (Size i = 0; i < datesZCII.size(); i++) {
142 Handle<Quote> quote(QuantLib::ext::shared_ptr<Quote>(new SimpleQuote(ratesZCII[i] / 100.0)));
143 QuantLib::ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure>> anInstrument(
144 new ZeroCouponInflationSwapHelper(
145 quote, Period(2, Months), datesZCII[i], UnitedKingdom(), ModifiedFollowing, ActualActual(ActualActual::ISDA), ii,
146 CPI::AsIndex, yieldCurves_.at(make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "GBP"))));
147 ;
148 instruments.push_back(anInstrument);
149 };
150 // we can use historical or first ZCIIS for this
151 // we know historical is WAY off market-implied, so use market implied flat.
152 Rate baseZeroRate = ratesZCII[0] / 100.0;
153 QuantLib::ext::shared_ptr<PiecewiseZeroInflationCurve<Linear>> pCPIts(new PiecewiseZeroInflationCurve<Linear>(
154 asof_, UnitedKingdom(), ActualActual(ActualActual::ISDA), Period(2, Months), ii->frequency(),
155 baseZeroRate, instruments));
156 pCPIts->recalculate();
157 cpiTS = QuantLib::ext::dynamic_pointer_cast<ZeroInflationTermStructure>(pCPIts);
158 hUKRPI = Handle<ZeroInflationIndex>(
159 parseZeroInflationIndex("UKRPI", Handle<ZeroInflationTermStructure>(cpiTS)));
160 zeroInflationIndices_[make_pair(Market::defaultConfiguration, "UKRPI")] = hUKRPI;
161 }
162
163 Handle<IborIndex> hGBP;
164 Handle<ZeroInflationIndex> hUKRPI;
165
166private:
167 Handle<YieldTermStructure> intDiscCurve(vector<Date> dates, vector<DiscountFactor> dfs, DayCounter dc,
168 Calendar cal) {
169 QuantLib::ext::shared_ptr<YieldTermStructure> idc(
170 new QuantLib::InterpolatedDiscountCurve<LogLinear>(dates, dfs, dc, cal));
171 return Handle<YieldTermStructure>(idc);
172 }
173};
174} // namespace
175
176BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
177
178BOOST_AUTO_TEST_SUITE(CPISwapTests)
179
180BOOST_AUTO_TEST_CASE(testCPISwapPrice) {
181
182 BOOST_TEST_MESSAGE("Testing CPI Swap Price...");
183
184 // build market
185 Date today(18, July, 2016);
186 Settings::instance().evaluationDate() = today;
187 QuantLib::ext::shared_ptr<TestMarket> market = QuantLib::ext::make_shared<TestMarket>();
188 Date marketDate = market->asofDate();
189 BOOST_CHECK_EQUAL(today, marketDate);
190 Settings::instance().evaluationDate() = marketDate;
191
192 // Test if GBP discount curve is empty
193 Handle<YieldTermStructure> dts = market->discountCurve("GBP");
194 QL_REQUIRE(!dts.empty(), "GBP discount curve not found");
195 BOOST_CHECK_CLOSE(market->discountCurve("GBP")->discount(today + 1 * Years), 0.9914, 0.0001);
196
197 // Test if GBP Libor curve is empty
198 Handle<IborIndex> iis = market->iborIndex("GBP-LIBOR-6M");
199 QL_REQUIRE(!iis.empty(), "GBP LIBOR 6M ibor Index not found");
200 BOOST_TEST_MESSAGE(
201 "CPISwap: Projected Libor fixing: " << market->iborIndex("GBP-LIBOR-6M")->forecastFixing(today + 1 * Years));
202
203 // Test if GBP discount curve is empty
204 Handle<ZeroInflationIndex> infidx = market->zeroInflationIndex("UKRPI");
205 QL_REQUIRE(!infidx.empty(), "UKRPI inflation index not found");
206 BOOST_TEST_MESSAGE("CPISwap: Projected UKRPI rate: " << infidx->fixing(today + 1 * Years));
207
208 // envelope
209 Envelope env("CP");
210
211 // Start/End date
212 Date startDate = today;
213 Date endDate = today + 5 * Years;
214
215 // date 2 string
216 std::ostringstream oss;
217 oss << io::iso_date(startDate);
218 string start(oss.str());
219 oss.str("");
220 oss.clear();
221 oss << io::iso_date(endDate);
222 string end(oss.str());
223
224 // Schedules
225 string conv = "MF";
226 string rule = "Forward";
227 ScheduleData scheduleLibor(ScheduleRules(start, end, "6M", "UK", conv, conv, rule));
228 ScheduleData scheduleCPI(ScheduleRules(start, end, "1Y", "UK", conv, conv, rule));
229
230 // Leg variables
231 bool isInArrears = false;
232 string dc = "ACT/ACT";
233 vector<Real> notional(1, 10000000);
234 string paymentConvention = "F";
235
236 // GBP Libor Leg
237 bool isPayerLibor = true;
238 string indexLibor = "GBP-LIBOR-6M";
239 vector<Real> spread(1, 0);
240 LegData legLibor(QuantLib::ext::make_shared<FloatingLegData>(indexLibor, 0, isInArrears, spread), isPayerLibor, "GBP",
241 scheduleLibor, "A365F", notional, vector<string>(), paymentConvention);
242
243 // GBP CPI Leg
244 bool isPayerCPI = false;
245 string indexCPI = "UKRPI";
246 Real baseCPI = 210.0;
247 string CPIlag = "2M";
248 std::vector<double> fixedRate(1, 0.02);
249 bool interpolated = false;
250 LegData legCPI(
251 QuantLib::ext::make_shared<CPILegData>(indexCPI, start, baseCPI, CPIlag, (interpolated ? "Linear" : "Flat"), fixedRate),
252 isPayerCPI, "GBP", scheduleCPI, dc, notional, vector<string>(), paymentConvention, false, true);
253
254 // Build swap trades
255 QuantLib::ext::shared_ptr<Trade> CPIswap(new ore::data::Swap(env, legLibor, legCPI));
256
257 // engine data and factory
258 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
259 engineData->model("Swap") = "DiscountedCashflows";
260 engineData->engine("Swap") = "DiscountingSwapEngine";
261 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
262
263 // build swaps and portfolio
264 QuantLib::ext::shared_ptr<Portfolio> portfolio(new Portfolio());
265 CPIswap->id() = "CPI_Swap";
266
267 portfolio->add(CPIswap);
268 portfolio->build(engineFactory);
269
270 // check CPI swap NPV against pure QL pricing
271 Schedule floatSchedule(startDate, endDate, 6 * Months, UnitedKingdom(), ModifiedFollowing, ModifiedFollowing,
272 DateGeneration::Forward, false);
273 Schedule cpiSchedule(startDate, endDate, 1 * Years, UnitedKingdom(), ModifiedFollowing, ModifiedFollowing,
274 DateGeneration::Forward, false);
275 Leg floatLeg = IborLeg(floatSchedule, *market->hGBP).withNotionals(10000000.0);
276 Leg cpiLeg = CPILeg(cpiSchedule, *market->hUKRPI, baseCPI, 2 * Months)
277 .withFixedRates(0.02)
278 .withNotionals(10000000)
280 .withPaymentDayCounter(ActualActual(ActualActual::ISDA))
281 .withPaymentAdjustment(Following);
282 auto pricer = QuantLib::ext::make_shared<CPICouponPricer>(market->hGBP->forwardingTermStructure());
283 for (auto const& c : cpiLeg) {
284 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<CPICoupon>(c))
285 cpn->setPricer(pricer);
286 }
287
288 QuantLib::Swap qlSwap(floatLeg, cpiLeg);
289 auto dscEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(market->hGBP->forwardingTermStructure());
290 qlSwap.setPricingEngine(dscEngine);
291 BOOST_TEST_MESSAGE("Leg 1 NPV: ORE = "
292 << QuantLib::ext::static_pointer_cast<QuantLib::Swap>(CPIswap->instrument()->qlInstrument())->legNPV(0)
293 << " QL = " << qlSwap.legNPV(0));
294 BOOST_TEST_MESSAGE("Leg 2 NPV: ORE = "
295 << QuantLib::ext::static_pointer_cast<QuantLib::Swap>(CPIswap->instrument()->qlInstrument())->legNPV(1)
296 << " QL = " << qlSwap.legNPV(1));
297 BOOST_CHECK_CLOSE(CPIswap->instrument()->NPV(), qlSwap.NPV(), 1E-8); // this is 1E-10 rel diff
298}
299
300BOOST_AUTO_TEST_SUITE_END()
301
302BOOST_AUTO_TEST_SUITE_END()
Engine builder for Swaps.
CPILeg & withNotionals(Real notional)
CPILeg & withPaymentAdjustment(BusinessDayConvention)
CPILeg & withFixedRates(Real fixedRate)
CPILeg & withPaymentDayCounter(const DayCounter &)
CPILeg & withObservationInterpolation(CPI::InterpolationType)
Serializable object holding generic trade data, reporting dimensions.
Definition: envelope.hpp:51
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< ZeroInflationIndex > > zeroInflationIndices_
Definition: marketimpl.hpp:222
Serializable portfolio.
Definition: portfolio.hpp:43
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
BOOST_AUTO_TEST_CASE(testCPISwapPrice)
Definition: cpiswap.cpp:180
A class to hold pricing engine parameters.
QuantLib::ext::shared_ptr< ZeroInflationIndex > parseZeroInflationIndex(const string &s, const Handle< ZeroInflationTermStructure > &h)
Convert std::string to QuantLib::ZeroInflationIndex.
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.
Classes and functions for log message handling.
An implementation of the Market class that stores the required objects in maps.
Portfolio class.
Swap trade data model and serialization.