Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
cds.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// clang-format off
21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
23// clang-format on
37#include <oret/datapaths.hpp>
38#include <oret/toplevelfixture.hpp>
39#include <ql/termstructures/credit/flathazardrate.hpp>
40#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
41#include <ql/termstructures/yield/flatforward.hpp>
42#include <ql/time/calendars/target.hpp>
43#include <ql/time/daycounters/actualactual.hpp>
44#include <ql/time/daycounters/simpledaycounter.hpp>
45
46using namespace QuantLib;
47using namespace boost::unit_test_framework;
48using namespace std;
49using namespace ore::data;
50using ore::test::TopLevelFixture;
51
53
54namespace {
55
56class TestMarket : public MarketImpl {
57public:
58 TestMarket(Real hazardRate, Real recoveryRate, Real liborRate) : MarketImpl(false) {
59 asof_ = Date(3, Feb, 2016);
60 // build discount
61 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")] =
62 flatRateYts(liborRate);
63 defaultCurves_[make_pair(Market::defaultConfiguration, "CreditCurve_A")] = flatRateDcs(hazardRate);
64 recoveryRates_[make_pair(Market::defaultConfiguration, "CreditCurve_A")] =
65 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(recoveryRate));
66 // build ibor index
67 Handle<IborIndex> hEUR(ore::data::parseIborIndex(
68 "EUR-EURIBOR-6M", yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")]));
69 iborIndices_[make_pair(Market::defaultConfiguration, "EUR-EURIBOR-6M")] = hEUR;
70 }
71
72private:
73 Handle<YieldTermStructure> flatRateYts(Real forward) {
74 QuantLib::ext::shared_ptr<YieldTermStructure> yts(new FlatForward(0, NullCalendar(), forward, ActualActual(ActualActual::ISDA)));
75 yts->enableExtrapolation();
76 return Handle<YieldTermStructure>(yts);
77 }
78 Handle<QuantExt::CreditCurve> flatRateDcs(Volatility forward) {
79 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> dcs(
80 new FlatHazardRate(asof_, forward, ActualActual(ActualActual::ISDA)));
81 return Handle<QuantExt::CreditCurve>(
82 QuantLib::ext::make_shared<QuantExt::CreditCurve>(Handle<DefaultProbabilityTermStructure>(dcs)));
83 }
84};
85
86struct CommonVars {
87 // global data
88 string ccy;
89 string creditCurveId;
90 string issuerId;
91 bool isPayer;
92 string start;
93 string issue;
94 string fixtenor;
95 Calendar cal;
96 string calStr;
97 string conv;
98 string rule;
99 Size days;
100 string fixDC;
101 string settledays;
102 bool isinarrears;
103 Real notional;
104 vector<Real> notionals;
105 vector<Real> spread;
106
107 // utilities
108 QuantLib::ext::shared_ptr<ore::data::CreditDefaultSwap> makeCDS(string end, Real rate) {
109 ScheduleData fixedSchedule(ScheduleRules(start, end, fixtenor, calStr, conv, conv, rule));
110
111 // build CDS
112 QuantLib::ext::shared_ptr<FixedLegData> fixedLegRateData = QuantLib::ext::make_shared<FixedLegData>(vector<double>(1, rate));
113 LegData fixedLegData(fixedLegRateData, isPayer, ccy, fixedSchedule, fixDC, notionals);
114
115 CreditDefaultSwapData cd(issuerId, creditCurveId, fixedLegData, false,
116 QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atDefault);
117 Envelope env("CP1");
118
119 QuantLib::ext::shared_ptr<ore::data::CreditDefaultSwap> cds(new ore::data::CreditDefaultSwap(env, cd));
120 return cds;
121 }
122
123 CommonVars()
124 : ccy("EUR"), creditCurveId("CreditCurve_A"), issuerId("CPTY_A"), isPayer(false), start("20160203"),
125 issue("20160203"), fixtenor("1Y") {
126 cal = TARGET();
127 calStr = "TARGET";
128 conv = "MF";
129 rule = "Forward";
130 fixDC = "ACT/365";
131 settledays = "2";
132 isinarrears = false;
133 notional = 1;
134 notionals.push_back(1);
135 spread.push_back(0.0);
136 }
137};
138
139struct MarketInputs {
140 Real hazardRate;
141 Real recoveryRate;
142 Real liborRate;
143};
144
145// Needed for BOOST_DATA_TEST_CASE below as it writes out the MarketInputs
146ostream& operator<<(ostream& os, const MarketInputs& m) {
147 return os << "[" << m.hazardRate << "," << m.recoveryRate << "," << m.liborRate << "]";
148}
149
150struct TradeInputs {
151 string endDate;
152 Real fixedRate;
153};
154
155// Needed for BOOST_DATA_TEST_CASE below as it writes out the TradeInputs
156ostream& operator<<(ostream& os, const TradeInputs& t) { return os << "[" << t.endDate << "," << t.fixedRate << "]"; }
157
158Real cdsNpv(const MarketInputs& m, const TradeInputs& t) {
159
160 // build market
161 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(m.hazardRate, m.recoveryRate, m.liborRate);
162 Settings::instance().evaluationDate() = market->asofDate();
163
164 CommonVars vars;
165 QuantLib::ext::shared_ptr<ore::data::CreditDefaultSwap> cds = vars.makeCDS(t.endDate, t.fixedRate);
166
167 // Build and price
168 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
169 engineData->model("CreditDefaultSwap") = "DiscountedCashflows";
170 engineData->engine("CreditDefaultSwap") = "MidPointCdsEngine";
171
172 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
173
174 cds->build(engineFactory);
175
176 return cds->instrument()->NPV();
177}
178
179// Cases below:
180// 1) HazardRate = 0, couponRate = 0. ExpectedNpv = 0
181// 2) RecoveryRate = 1, couponRate = 0. ExpectedNpv = 0
182// 3) Example from Hull, Ch 21 (pp. 510 - 513). Take RR = 1 to show only couponNPV.
183// 4) Example from Hull, Ch 21 (pp. 510 - 513). Take coupon rate = 0 to show only defaultNPV.
184
185// Market inputs used in the test below
186MarketInputs marketInputs[] = {{0, 1, 0}, {1, 1, 0}, {0.02, 1, 0.05}, {0.02, 0.4, 0.05}};
187
188// Trade inputs used in the test below
189TradeInputs tradeInputs[] = {{"20170203", 0}, {"20170203", 0}, {"20210203", 0.0124248849209095}, {"20210203", 0.0}};
190
191// Expected NPVs given the market and trade inputs above
192Real expNpvs[] = {0, 0, 0.050659, -0.05062};
193
194// List of trades that will feed the data-driven test below to check CDS trade building.
195vector<string> trades = {"cds_minimal_with_rules", "cds_minimal_with_dates"};
196
197struct TodaysMarketFiles {
198 string todaysMarket = "todaysmarket.xml";
199 string conventions = "conventions.xml";
200 string curveConfig = "curveconfig.xml";
201 string market = "market.txt";
202 string fixings = "fixings.txt";
203};
204
205// Create todaysmarket from input files.
206QuantLib::ext::shared_ptr<TodaysMarket> createTodaysMarket(const Date& asof, const string& inputDir,
207 const TodaysMarketFiles& tmf) {
208
209 auto conventions = QuantLib::ext::make_shared<Conventions>();
210 conventions->fromFile(TEST_INPUT_FILE(string(inputDir + "/" + tmf.conventions)));
211 InstrumentConventions::instance().setConventions(conventions);
212
213 auto curveConfigs = QuantLib::ext::make_shared<CurveConfigurations>();
214 curveConfigs->fromFile(TEST_INPUT_FILE(string(inputDir + "/" + tmf.curveConfig)));
215
216 auto todaysMarketParameters = QuantLib::ext::make_shared<TodaysMarketParameters>();
217 todaysMarketParameters->fromFile(TEST_INPUT_FILE(string(inputDir + "/" + tmf.todaysMarket)));
218
219 auto loader = QuantLib::ext::make_shared<CSVLoader>(TEST_INPUT_FILE(string(inputDir + "/" + tmf.market)),
220 TEST_INPUT_FILE(string(inputDir + "/" + tmf.fixings)), false);
221
222 return QuantLib::ext::make_shared<TodaysMarket>(asof, todaysMarketParameters, loader, curveConfigs);
223}
224
225} // namespace
226
227BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
228
229BOOST_AUTO_TEST_SUITE(CreditDefaultSwapTests)
230
231BOOST_DATA_TEST_CASE(testCreditDefaultSwap, bdata::make(marketInputs) ^ bdata::make(tradeInputs) ^ bdata::make(expNpvs),
232 mkt, trd, exp) {
233
234 BOOST_CHECK_CLOSE(cdsNpv(mkt, trd), exp, 0.01);
235}
236
237BOOST_DATA_TEST_CASE_F(TopLevelFixture, testCreditDefaultSwapBuilding, bdata::make(trades), trade) {
238
239 BOOST_TEST_MESSAGE("Test the building of various CDS trades from XML");
240
241 Settings::instance().evaluationDate() = Date(3, Feb, 2016);
242
243 // Read in the trade
244 Portfolio p;
245 string portfolioFile = "trades/" + trade + ".xml";
246 p.fromFile(TEST_INPUT_FILE(portfolioFile));
247 BOOST_REQUIRE_MESSAGE(p.size() == 1, "Expected portfolio to contain a single trade");
248
249 // Use the test market
250 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(0.02, 0.4, 0.05);
251
252 // Engine data
253 QuantLib::ext::shared_ptr<EngineData> ed = QuantLib::ext::make_shared<EngineData>();
254 ed->model("CreditDefaultSwap") = "DiscountedCashflows";
255 ed->engine("CreditDefaultSwap") = "MidPointCdsEngine";
256
257 // Test that the trade builds and prices without error
258 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(ed, market);
259 BOOST_CHECK_NO_THROW(p.build(engineFactory));
260 Real npv;
261 BOOST_CHECK_NO_THROW(npv = p.trades().begin()->second->instrument()->NPV());
262 BOOST_TEST_MESSAGE("CDS NPV is: " << npv);
263}
264
265// Various file combinations for the upfront consistency test below.
266struct UpfrontFiles {
267 string market;
268 string curveConfig;
269};
270
271ostream& operator<<(ostream& os, const UpfrontFiles& upfrontFiles) {
272 return os << "[" << upfrontFiles.market << "," << upfrontFiles.curveConfig << "]";
273}
274
275UpfrontFiles upfrontFiles[] = {{"market.txt", "curveconfig.xml"}, {"market_rs.txt", "curveconfig_rs.xml"}};
276
277// Create CDS curve from upfront quotes. Price portfolio of CDS that match the curve instruments. Check that the
278// value of each instrument is zero as expected. Notional is $10M and bootstrap accuracy is 1e-12 => tol of 1e-4
279// should be adequate.
280BOOST_DATA_TEST_CASE(testUpfrontDefaultCurveConsistency, bdata::make(upfrontFiles), files) {
281
282 BOOST_TEST_MESSAGE("Testing upfront default curve consistency ...");
283
284 Date asof(6, Nov, 2020);
285 Settings::instance().evaluationDate() = asof;
286 string dir("upfront");
287 Real tol = 0.0001;
288
289 TodaysMarketFiles tmf;
290 tmf.market = files.market;
291 tmf.curveConfig = files.curveConfig;
292
293 QuantLib::ext::shared_ptr<TodaysMarket> tm;
294 BOOST_REQUIRE_NO_THROW(tm = createTodaysMarket(asof, dir, tmf));
295
296 QuantLib::ext::shared_ptr<EngineData> data = QuantLib::ext::make_shared<EngineData>();
297 data->fromFile(TEST_INPUT_FILE(string(dir + "/pricingengine.xml")));
298 QuantLib::ext::shared_ptr<EngineFactory> ef = QuantLib::ext::make_shared<EngineFactory>(data, tm);
299
300 Portfolio portfolio;
301 portfolio.fromFile(TEST_INPUT_FILE(string(dir + "/portfolio.xml")));
302 portfolio.build(ef);
303
304 for (const auto& [tradeId, trade] : portfolio.trades()) {
305 auto npv = trade->instrument()->NPV();
306 BOOST_TEST_CONTEXT("NPV of trade " << tradeId << " is " << fixed << setprecision(12) << npv) {
307 BOOST_CHECK_SMALL(trade->instrument()->NPV(), tol);
308 }
309 }
310}
311
312BOOST_AUTO_TEST_CASE(testUpfrontCurveBuildFailsIfNoRunningSpread) {
313
314 BOOST_TEST_MESSAGE("Testing upfront failure when no running spread ...");
315
316 TodaysMarketFiles tmf;
317 tmf.curveConfig = "curveconfig_no_rs.xml";
318
319 Date asof(6, Nov, 2020);
320 Settings::instance().evaluationDate() = Date(6, Nov, 2020);
321 BOOST_CHECK_THROW(createTodaysMarket(asof, "upfront", tmf), QuantLib::Error);
322}
323
324BOOST_AUTO_TEST_CASE(testSimultaneousUsageCdsQuoteTypes) {
325
326 BOOST_TEST_MESSAGE("Testing different CDS quote types can be used together ...");
327
328 TodaysMarketFiles tmf;
329 tmf.todaysMarket = "todaysmarket_all_cds_quote_types.xml";
330
331 Date asof(6, Nov, 2020);
332 Settings::instance().evaluationDate() = Date(6, Nov, 2020);
333
334 // Check that todaysmarket instance is created without error.
335 QuantLib::ext::shared_ptr<TodaysMarket> tm;
336 BOOST_CHECK_NO_THROW(tm = createTodaysMarket(asof, "upfront", tmf));
337
338 // Check that each of the three expected curves exist and give survival probability.
339 vector<string> curveNames = {
340 "RED:8B69AP|SNRFOR|USD|CR-UPFRONT",
341 "RED:8B69AP|SNRFOR|USD|CR-PAR_SPREAD",
342 "RED:8B69AP|SNRFOR|USD|CR-CONV_SPREAD",
343 };
344
345 for (const string& curveName : curveNames) {
346 BOOST_TEST_CONTEXT("Checking default curve " << curveName) {
347 Handle<DefaultProbabilityTermStructure> dpts;
348 BOOST_CHECK_NO_THROW(dpts = tm->defaultCurve(curveName)->curve());
349 BOOST_CHECK_NO_THROW(dpts->survivalProbability(1.0));
350 }
351 }
352}
353
354BOOST_AUTO_TEST_SUITE_END()
355
356BOOST_AUTO_TEST_SUITE_END()
Builder that returns an engine to price a credit default swap.
BOOST_AUTO_TEST_CASE(testUpfrontCurveBuildFailsIfNoRunningSpread)
Definition: cds.cpp:312
ostream & operator<<(ostream &os, const UpfrontFiles &upfrontFiles)
Definition: cds.cpp:271
UpfrontFiles upfrontFiles[]
Definition: cds.cpp:275
BOOST_DATA_TEST_CASE_F(TopLevelFixture, testCreditDefaultSwapBuilding, bdata::make(trades), trade)
Definition: cds.cpp:237
BOOST_DATA_TEST_CASE(testCreditDefaultSwap, bdata::make(marketInputs) ^ bdata::make(tradeInputs) ^ bdata::make(expNpvs), mkt, trd, exp)
Definition: cds.cpp:231
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
Serializable portfolio.
Definition: portfolio.hpp:43
const std::map< std::string, QuantLib::ext::shared_ptr< Trade > > & trades() const
Return the map tradeId -> trade.
Definition: portfolio.cpp:162
QuantLib::Size size() const
Portfolio size.
Definition: portfolio.hpp:68
void build(const QuantLib::ext::shared_ptr< EngineFactory > &, const std::string &context="unspecified", const bool emitStructuredError=true)
Call build on all trades in the portfolio, the context is included in error messages.
Definition: portfolio.cpp:122
Serializable schedule data.
Definition: schedule.hpp:202
Serializable object holding schedule Rules data.
Definition: schedule.hpp:37
void fromFile(const std::string &filename)
Definition: xmlutils.cpp:150
Ibor cap, floor or collar trade data model and serialization.
Market Datum Loader Implementation.
Curve configuration repository.
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
Market Datum Loader Interface.
@ data
Definition: log.hpp:77
An implementation of the Market class that stores the required objects in maps.
RandomVariable exp(RandomVariable x)
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
Portfolio class.
trade schedule data model and serialization
vector< string > curveConfigs
An concrete implementation of the Market class that loads todays market and builds the required curve...