Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
parsensitivityanalysismanual.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
20#include "testmarket.hpp"
21#include "testportfolio.hpp"
22
28
43
44#include <oret/toplevelfixture.hpp>
45
46#include <ql/time/date.hpp>
47
49
50using namespace std;
51using namespace QuantLib;
52using namespace QuantExt;
53using namespace boost::unit_test_framework;
54using namespace ore;
55using namespace ore::data;
56using namespace ore::analytics;
57
58namespace {
59
60QuantLib::ext::shared_ptr<EngineFactory> registerBuilders(QuantLib::ext::shared_ptr<EngineData> engineData,
61 QuantLib::ext::shared_ptr<Market> market) {
62 QuantLib::ext::shared_ptr<EngineFactory> factory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
63 return factory;
64}
65
66void parSensiBumpAnalysis(QuantLib::ext::shared_ptr<Portfolio>& portfolio, QuantLib::ext::shared_ptr<EngineData>& engineData,
67 QuantLib::ext::shared_ptr<Market>& initMarket, map<string, Real>& baseManualPv, string& baseCcy,
68 vector<Handle<Quote>>& parValVecBase, vector<string>& labelVec, Real shiftSize,
69 ShiftType shiftType, std::map<std::pair<std::string, std::string>, Real>& parDelta,
70 std::map<std::pair<std::string, std::string>, Real>& zeroDelta, map<string, Real>& basePv) {
71
72 Size tradeCount = portfolio->size();
73 Real basePvTol = 0.00001; // tolerance for base PV after re-bootstrapping
74 Real sensiThold = 0.0001; // sensi values below this are ignored
75 Real sensiRelTol = 1.0; // relative tolerance on par sensitivities
76 Real sensiAbsTol = 0.2; // absolute tolerance on par sensitivities for small values (EUR)
77 Real relAbsTolBoundary = 10.0; // abs val threshold, below this abs tolerance checked, above this rel tolerance
78
79 vector<Real> baseValues;
80 for (Size i = 0; i < parValVecBase.size(); ++i) {
81 Handle<Quote> q = parValVecBase[i];
82 Real baseVal = q->value();
83 baseValues.push_back(baseVal);
84 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*q)->setValue(baseVal);
85 }
86 QuantLib::ext::shared_ptr<EngineFactory> manualFactory2 = registerBuilders(engineData, initMarket);
87 portfolio->reset();
88 portfolio->build(manualFactory2);
89 BOOST_CHECK_MESSAGE(portfolio->size() == tradeCount,
90 "Some trades not built correctly," << portfolio->size() << " vs. " << tradeCount);
91 for (auto& [tradeId, trade] : portfolio->trades()) {
92 Real fx = 1.0;
93 if (trade->npvCurrency() != "EUR") {
94 string ccyPair = trade->npvCurrency() + baseCcy;
95 fx = initMarket->fxRate(ccyPair)->value();
96 }
97 BOOST_CHECK_MESSAGE(std::fabs(baseManualPv[tradeId] - (fx * trade->instrument()->NPV())) <= basePvTol,
98 "Equality error for trade " << tradeId << "; got " << (fx * trade->instrument()->NPV())
99 << ", but expected " << baseManualPv[tradeId]);
100 }
101 // bump each point in sequence and generate new par curve
102 for (Size i = 0; i < parValVecBase.size(); ++i) {
103
104 string sensiLabel = labelVec[i];
105 // if ((curveType == "CDSVolatility") || (curveType == "EquityVolatility")) sensiLabel = sensiLabel + "/ATM";
106 for (Size j = 0; j < parValVecBase.size(); ++j) {
107 if (i == j) {
108 Real newVal = (shiftType == ShiftType::Absolute) ? (baseValues[j] + shiftSize)
109 : (baseValues[j] * (1.0 + shiftSize));
110 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*parValVecBase[j])->setValue(newVal);
111 } else {
112 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*parValVecBase[j])->setValue(baseValues[j]);
113 }
114 }
115 QuantLib::ext::shared_ptr<EngineFactory> manualFactoryDisc = registerBuilders(engineData, initMarket);
116 portfolio->reset();
117 portfolio->build(manualFactoryDisc);
118 BOOST_CHECK_MESSAGE(portfolio->size() == tradeCount,
119 "Some trades not built correctly," << portfolio->size() << " vs. " << tradeCount);
120 for (auto [tradeId, trade] : portfolio->trades()) {
121 Real fx = 1.0;
122 if (trade->npvCurrency() != "EUR") {
123 string ccyPair = trade->npvCurrency() + baseCcy;
124 fx = initMarket->fxRate(ccyPair)->value();
125 }
126 Real shiftedNPV = fx * trade->instrument()->NPV();
127 Real anticipatedParDelta = shiftedNPV - baseManualPv[tradeId];
128 Real computedParDelta = 0.0;
129 if (parDelta.find(std::make_pair(tradeId, sensiLabel)) != parDelta.end())
130 computedParDelta = parDelta[std::make_pair(tradeId, sensiLabel)];
131
132 if ((std::fabs(anticipatedParDelta) > sensiThold) || (std::fabs(computedParDelta) > sensiThold)) {
133 BOOST_TEST_MESSAGE("#reportrow," << tradeId << "," << sensiLabel << "," << baseManualPv[tradeId]
134 << "," << basePv[tradeId] << "," << anticipatedParDelta << ","
135 << computedParDelta << ","
136 << zeroDelta[std::make_pair(tradeId, sensiLabel)]);
137
138 if ((std::fabs(anticipatedParDelta) > relAbsTolBoundary) &&
139 (std::fabs(computedParDelta) > relAbsTolBoundary))
140 BOOST_CHECK_CLOSE(anticipatedParDelta, computedParDelta, sensiRelTol);
141 else
142 BOOST_CHECK(std::fabs(anticipatedParDelta - computedParDelta) < sensiAbsTol);
143 }
144 }
145 }
146 // set back to the base curve
147 for (Size i = 0; i < parValVecBase.size(); ++i) {
148 QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*parValVecBase[i])->setValue(baseValues[i]);
149 }
150 QuantLib::ext::shared_ptr<EngineFactory> manualFactory3 = registerBuilders(engineData, initMarket);
151
152 portfolio->reset();
153 portfolio->build(manualFactory3);
154 BOOST_CHECK_MESSAGE(portfolio->size() == tradeCount,
155 "Some trades not built correctly," << portfolio->size() << " vs. " << tradeCount);
156 for (auto [tradeId, trade] : portfolio->trades()) {
157 Real fx = 1.0;
158 if (trade->npvCurrency() != "EUR") {
159 string ccyPair = trade->npvCurrency() + baseCcy;
160 fx = initMarket->fxRate(ccyPair)->value();
161 }
162 BOOST_CHECK_MESSAGE(std::fabs(baseManualPv[tradeId] - (fx * trade->instrument()->NPV())) <= basePvTol,
163 "Equality error for trade " << tradeId << "; got " << (fx * trade->instrument()->NPV())
164 << ", but expected " << baseManualPv[tradeId]);
165 }
166}
167} // namespace
168
169namespace testsuite {
170
172
173 BOOST_TEST_MESSAGE("Testing swap par sensitivities against manual bump of par curve instruments");
174 SavedSettings backup;
175
176 ObservationMode::Mode backupMode = ObservationMode::instance().mode();
177 ObservationMode::Mode om = ObservationMode::Mode::None;
178 ObservationMode::instance().setMode(om);
179
180 Date today = Date(14, April, 2016); // Settings::instance().evaluationDate();
181 Settings::instance().evaluationDate() = today;
182 BOOST_TEST_MESSAGE("Today is " << today);
183
184 // build model
185 string baseCcy = "EUR";
186 vector<string> ccys;
187 ccys.push_back(baseCcy);
188 ccys.push_back("GBP");
189 ccys.push_back("CHF");
190 ccys.push_back("USD");
191 ccys.push_back("JPY");
192
193 // Init market
194 QuantLib::ext::shared_ptr<Market> initMarket = QuantLib::ext::make_shared<TestMarketParCurves>(today);
195
196 // build scenario sim market parameters
197 QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarketParameters> simMarketData =
199
200 // sensitivity config
201 QuantLib::ext::shared_ptr<SensitivityScenarioData> sensiData =
203 // build porfolio
204 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
205 engineData->model("Swap") = "DiscountedCashflows";
206 engineData->engine("Swap") = "DiscountingSwapEngine";
207 engineData->model("CrossCurrencySwap") = "DiscountedCashflows";
208 engineData->engine("CrossCurrencySwap") = "DiscountingCrossCurrencySwapEngine";
209 engineData->model("EuropeanSwaption") = "BlackBachelier";
210 engineData->engine("EuropeanSwaption") = "BlackBachelierSwaptionEngine";
211 engineData->model("FxForward") = "DiscountedCashflows";
212 engineData->engine("FxForward") = "DiscountingFxForwardEngine";
213 engineData->model("FxOption") = "GarmanKohlhagen";
214 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
215 engineData->model("CapFloor") = "IborCapModel";
216 engineData->engine("CapFloor") = "IborCapEngine";
217 engineData->model("CapFlooredIborLeg") = "BlackOrBachelier";
218 engineData->engine("CapFlooredIborLeg") = "BlackIborCouponPricer";
219 engineData->model("CreditDefaultSwap") = "DiscountedCashflows";
220 engineData->engine("CreditDefaultSwap") = "MidPointCdsEngine";
221
222 engineData->model("IndexCreditDefaultSwapOption") = "Black";
223 engineData->engine("IndexCreditDefaultSwapOption") = "BlackIndexCdsOptionEngine";
224 map<string, string> engineParamMap1;
225 engineParamMap1["Curve"] = "Underlying";
226 engineData->engineParameters("IndexCreditDefaultSwapOption") = engineParamMap1;
227
228 engineData->model("IndexCreditDefaultSwap") = "DiscountedCashflows";
229 engineData->engine("IndexCreditDefaultSwap") = "MidPointIndexCdsEngine";
230 map<string, string> engineParamMap2;
231 engineParamMap2["Curve"] = "Underlying";
232 engineData->engineParameters("IndexCreditDefaultSwap") = engineParamMap2;
233
234 engineData->model("CMS") = "LinearTSR";
235 engineData->engine("CMS") = "LinearTSRPricer";
236 map<string, string> engineparams;
237 engineparams["MeanReversion"] = "0.0";
238 engineparams["Policy"] = "RateBound";
239 engineparams["LowerRateBoundNormal"] = "-2.0000";
240 engineparams["UpperRateBoundNormal"] = "2.0000";
241 engineData->engineParameters("CMS") = engineparams;
242 engineData->model("SyntheticCDO") = "GaussCopula";
243 engineData->engine("SyntheticCDO") = "Bucketing";
244 map<string, string> modelParamMap3;
245 map<string, string> engineParamMap3;
246 modelParamMap3["correlation"] = "0.0";
247 modelParamMap3["min"] = "-5.0";
248 modelParamMap3["max"] = "5.0";
249 modelParamMap3["steps"] = "64";
250 engineParamMap3["buckets"] = "200";
251 engineParamMap3["homogeneousPoolWhenJustified"] = "N";
252 engineData->modelParameters("SyntheticCDO") = modelParamMap3;
253 engineData->engineParameters("SyntheticCDO") = engineParamMap3;
254
255 engineData->model("EquityOption") = "BlackScholesMerton";
256 engineData->engine("EquityOption") = "AnalyticEuropeanEngine";
257
258 std::vector<QuantLib::ext::shared_ptr<EngineBuilder>> builders;
259 // builders.push_back(QuantLib::ext::make_shared<oreplus::data::MidPointIndexCdsEngineBuilder>());
260 // builders.push_back(QuantLib::ext::make_shared<oreplus::data::BlackIndexCdsOptionEngineBuilder>());
261 builders.push_back(QuantLib::ext::make_shared<ore::data::GaussCopulaBucketingCdoEngineBuilder>());
262
263 QuantLib::ext::shared_ptr<EngineFactory> factory = registerBuilders(engineData, initMarket);
264 QuantLib::ext::shared_ptr<Portfolio> portfolio(new Portfolio());
265 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
266 QuantLib::ext::shared_ptr<IRSwapConvention> eurConv =
267 QuantLib::ext::dynamic_pointer_cast<IRSwapConvention>(conventions->get("EUR-6M-SWAP-CONVENTIONS"));
268 string eurIdx = "EUR-EURIBOR-6M";
269 Period eurFloatTenor = initMarket->iborIndex(eurIdx)->tenor();
270 QuantLib::ext::shared_ptr<IRSwapConvention> usdConv =
271 QuantLib::ext::dynamic_pointer_cast<IRSwapConvention>(conventions->get("USD-6M-SWAP-CONVENTIONS"));
272 string usdIdx = "USD-LIBOR-6M";
273 Period usdFloatTenor = initMarket->iborIndex(usdIdx)->tenor();
274 QuantLib::ext::shared_ptr<IRSwapConvention> jpyConv =
275 QuantLib::ext::dynamic_pointer_cast<IRSwapConvention>(conventions->get("JPY-6M-SWAP-CONVENTIONS"));
276 string jpyIdx = "JPY-LIBOR-6M";
277 Period jpyFloatTenor = initMarket->iborIndex(jpyIdx)->tenor();
278 QuantLib::ext::shared_ptr<CrossCcyBasisSwapConvention> chfBasisConv =
279 QuantLib::ext::dynamic_pointer_cast<CrossCcyBasisSwapConvention>(conventions->get("CHF-XCCY-BASIS-CONVENTIONS"));
280 QuantLib::ext::shared_ptr<CdsConvention> cdsConv =
281 QuantLib::ext::dynamic_pointer_cast<CdsConvention>(conventions->get("CDS-STANDARD-CONVENTIONS"));
282
283 string chfIdx = chfBasisConv->spreadIndexName();
284 string otherIdx = chfBasisConv->flatIndexName();
285 Period chfFloatTenor = initMarket->iborIndex(chfIdx)->tenor();
286 Period otherFloatTenor = initMarket->iborIndex(otherIdx)->tenor();
287
288 portfolio->add(buildSwap(
289 "1_Swap_EUR", "EUR", true, 10000000.0, 0, 10, 0.02, 0.00,
290 ore::data::to_string(Period(eurConv->fixedFrequency())), ore::data::to_string(eurConv->fixedDayCounter()),
291 ore::data::to_string(eurFloatTenor), "A360", eurIdx, eurConv->fixedCalendar(),
292 initMarket->iborIndex(eurIdx)->fixingDays(), true));
293 portfolio->add(buildSwap(
294 "2_Swap_USD", "USD", true, 10000000.0, 0, 15, 0.03, 0.00,
295 ore::data::to_string(Period(usdConv->fixedFrequency())), ore::data::to_string(usdConv->fixedDayCounter()),
296 ore::data::to_string(usdFloatTenor), "A360", usdIdx, usdConv->fixedCalendar(),
297 initMarket->iborIndex(usdIdx)->fixingDays(), true));
298 portfolio->add(buildCap(
299 "9_Cap_EUR", "EUR", "Long", 0.02, 1000000.0, 0, 10, ore::data::to_string(eurFloatTenor), "A360", eurIdx,
300 eurConv->fixedCalendar(), initMarket->iborIndex(eurIdx)->fixingDays(), true));
301 portfolio->add(buildFloor(
302 "10_Floor_USD", "USD", "Long", 0.03, 1000000.0, 0, 10, ore::data::to_string(usdFloatTenor), "A360", usdIdx,
303 usdConv->fixedCalendar(), initMarket->iborIndex(usdIdx)->fixingDays(), true));
304 portfolio->add(buildSwap(
305 "3_Swap_EUR", "EUR", false, 10000000.0, 1, 12, 0.025, 0.00,
306 ore::data::to_string(Period(eurConv->fixedFrequency())), ore::data::to_string(eurConv->fixedDayCounter()),
307 ore::data::to_string(eurFloatTenor), "A360", eurIdx, eurConv->fixedCalendar(),
308 initMarket->iborIndex(eurIdx)->fixingDays(), true));
309 portfolio->add(buildCrossCcyBasisSwap(
310 "4_XCCY_SWAP", "CHF", 10000000, "EUR", 10000000, 0, 15, 0.0000, 0.0000, ore::data::to_string(chfFloatTenor),
311 "A360", chfIdx, chfBasisConv->settlementCalendar(), ore::data::to_string(otherFloatTenor), "A360", otherIdx,
312 chfBasisConv->settlementCalendar(), chfBasisConv->settlementDays(), true));
313 portfolio->add(buildCrossCcyBasisSwap(
314 "5_XCCY_SWAP_WithPrincipal", "CHF", 10000000, "EUR", 10000000, 0, 15, 0.0000, 0.0000,
315 ore::data::to_string(chfFloatTenor), "A360", chfIdx, chfBasisConv->settlementCalendar(),
316 ore::data::to_string(otherFloatTenor), "A360", otherIdx, chfBasisConv->settlementCalendar(),
317 chfBasisConv->settlementDays(), true, true, true, true, false));
318 portfolio->add(buildSwap(
319 "6_Swap_JPY", "JPY", true, 1000000000.0, 0, 10, 0.005, 0.00,
320 ore::data::to_string(Period(jpyConv->fixedFrequency())), ore::data::to_string(jpyConv->fixedDayCounter()),
321 ore::data::to_string(jpyFloatTenor), "A360", jpyIdx, jpyConv->fixedCalendar(),
322 initMarket->iborIndex(jpyIdx)->fixingDays(), true));
323 portfolio->add(buildCrossCcyBasisSwap(
324 "7_XCCY_SWAP_OffMarket", "EUR", 10000000, "CHF", 10500000, 0, 15, 0.0000, 0.0010,
325 ore::data::to_string(chfFloatTenor), "A360", chfIdx, chfBasisConv->settlementCalendar(),
326 ore::data::to_string(otherFloatTenor), "A360", otherIdx, chfBasisConv->settlementCalendar(),
327 chfBasisConv->settlementDays(), true));
328 portfolio->add(buildCrossCcyBasisSwap(
329 "8_XCCY_SWAP_RESET", "CHF", 10000000, "EUR", 10000000, 0, 15, 0.0000, 0.0000,
330 ore::data::to_string(chfFloatTenor), "A360", chfIdx, chfBasisConv->settlementCalendar(),
331 ore::data::to_string(otherFloatTenor), "A360", otherIdx, chfBasisConv->settlementCalendar(),
332 chfBasisConv->settlementDays(), true, true, true, false, false, true));
333 portfolio->add(buildCreditDefaultSwap("9_CDS_USD", "USD", "dc", "dc", true, 10000000, 0, 15, 0.4,
334 0.009, ore::data::to_string(Period(cdsConv->frequency())),
335 ore::data::to_string(cdsConv->dayCounter())));
336 portfolio->add(buildCreditDefaultSwap(
337 "9_CDS_EUR", "EUR", "dc2", "dc2", true, 10000000, 0, 15, 0.4, 0.009,
338 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
339 portfolio->add(buildCreditDefaultSwap(
340 "10_CDS_USD", "USD", "dc", "dc", true, 10000000, 0, 10, 0.4, 0.001,
341 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
342 portfolio->add(buildCreditDefaultSwap(
343 "10_CDS_EUR", "EUR", "dc2", "dc2", true, 10000000, 0, 10, 0.4, 0.001,
344 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
345 portfolio->add(buildCreditDefaultSwap(
346 "11_CDS_EUR", "EUR", "dc2", "dc2", true, 10000000, 0, 5, 0.4, 0.001,
347 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
348 portfolio->add(buildCreditDefaultSwap("11_CDS_USD", "USD", "dc", "dc", true, 10000000, 0, 5, 0.4,
349 0.001, ore::data::to_string(Period(cdsConv->frequency())),
350 ore::data::to_string(cdsConv->dayCounter())));
351 portfolio->add(buildCreditDefaultSwap("12_CDS_USD", "USD", "dc", "dc", true, 10000000, 0, 2, 0.4,
352 0.004, ore::data::to_string(Period(cdsConv->frequency())),
353 ore::data::to_string(cdsConv->dayCounter())));
354 portfolio->add(buildCreditDefaultSwap(
355 "12_CDS_EUR", "EUR", "dc2", "dc2", true, 10000000, 0, 2, 0.4, 0.001,
356 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
357 portfolio->add(buildCreditDefaultSwap(
358 "13_CDS_USD", "USD", "dc", "dc", true, 10000000, 0, 15, 0.4, 0.001,
359 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
360 portfolio->add(buildCreditDefaultSwap(
361 "13_CDS_EUR", "EUR", "dc2", "dc2", true, 10000000, 0, 15, 0.4, 0.001,
362 ore::data::to_string(Period(cdsConv->frequency())), ore::data::to_string(cdsConv->dayCounter())));
363
364 vector<string> names = {"dc", "dc2", "dc3"};
365 vector<string> indexCcys = {"USD", "EUR", "GBP"};
366 vector<Real> notionals = {3000000, 3000000, 3000000};
367 // portfolio->add(buildIndexCdsOption("14_IndexCDSOption_USD", "dc", names, "Long", "USD",
368 // indexCcys, true, notionals, notional, 1, 4,
369 // 0.4, 0.001,
370 // ore::data::to_string(Period(cdsConv->frequency())),
371 // ore::data::to_string(cdsConv->dayCounter()), "Physical"));
372
373 vector<string> names2(1, "dc2");
374 vector<string> indexCcys2(1, "EUR");
375 vector<Real> notionals2(1, 10000000.0);
376 // portfolio->add(buildIndexCdsOption("15_IndexCDSOption_EUR", "dc2", names2, "Long", "EUR",
377 // indexCcys2, true, notionals2, 10000000.0, 1, 4,
378 // 0.4, 0.001,
379 // ore::data::to_string(Period(cdsConv->frequency())),
380 // ore::data::to_string(cdsConv->dayCounter()), "Physical"));
381 portfolio->add(buildSyntheticCDO("16_SyntheticCDO_EUR", "dc2", names2, "Long", "EUR", indexCcys2,
382 true, notionals2, 1000000.0, 0, 5, 0.03, 0.01, "1Y", "30/360"));
383
384 portfolio->add(buildCmsCapFloor("17_CMS_EUR", "EUR", "EUR-CMS-30Y", true, 2000000, 0, 5, 0.0, 1,
385 0.0, ore::data::to_string(Period(eurConv->fixedFrequency())),
386 ore::data::to_string(eurConv->fixedDayCounter())));
387 portfolio->add(
388 buildEquityOption("18_EquityOption_SP5", "Long", "Call", 2, "SP5", "USD", 2147.56, 775));
389
390 portfolio->add(buildCPIInflationSwap("19_CPIInflationSwap_UKRPI", "GBP", true, 100000.0, 0, 10,
391 0.0, "6M", "ACT/ACT", "GBP-LIBOR-6M", "1Y", "ACT/ACT",
392 "UKRPI", 201.0, "2M", false, 0.005));
393 portfolio->add(buildYYInflationSwap("20_YoYInflationSwap_UKRPI", "GBP", true, 100000.0, 0, 10,
394 0.0, "1Y", "ACT/ACT", "GBP-LIBOR-6M", "1Y", "ACT/ACT",
395 "UKRPI", "2M", 2));
396
397 Size tradeCount = portfolio->size();
398 portfolio->build(factory);
399 BOOST_CHECK_MESSAGE(portfolio->size() == tradeCount,
400 "Some trades not built correctly," << portfolio->size() << " vs. " << tradeCount);
401 // build the sensitivity analysis object
402 QuantLib::ext::shared_ptr<SensitivityAnalysis> zeroAnalysis =
403 QuantLib::ext::make_shared<SensitivityAnalysis>(portfolio, initMarket, Market::defaultConfiguration, engineData,
404 simMarketData, sensiData,
405 false, nullptr, nullptr, false, nullptr);
406 ParSensitivityAnalysis parAnalysis(today, simMarketData, *sensiData, Market::defaultConfiguration);
407 parAnalysis.alignPillars();
408 zeroAnalysis->overrideTenors(true);
409 zeroAnalysis->generateSensitivities();
410 parAnalysis.computeParInstrumentSensitivities(zeroAnalysis->simMarket());
411 QuantLib::ext::shared_ptr<ParSensitivityConverter> parConverter =
412 QuantLib::ext::make_shared<ParSensitivityConverter>(parAnalysis.parSensitivities(), parAnalysis.shiftSizes());
413 QuantLib::ext::shared_ptr<SensitivityCube> sensiCube = zeroAnalysis->sensiCube();
414 ZeroToParCube parCube(sensiCube, parConverter);
415
416 std::map<std::pair<std::string, std::string>, Real> parDelta;
417 std::map<std::pair<std::string, std::string>, Real> zeroDelta;
418 map<string, Real> baseManualPv;
419 map<string, Real> basePv;
420 for (const auto& tradeId : portfolio->ids()) {
421 basePv[tradeId] = sensiCube->npv(tradeId);
422 for (const auto& f : sensiCube->factors()) {
423 string des = sensiCube->factorDescription(f);
424 zeroDelta[make_pair(tradeId, des)] = sensiCube->delta(tradeId, f);
425 }
426 // Fill the par deltas map
427 auto temp = parCube.parDeltas(tradeId);
428 for (const auto& kv : temp) {
429 string des = sensiCube->factorDescription(kv.first);
430 parDelta[make_pair(tradeId, des)] = kv.second;
431 }
432 }
433 QuantLib::ext::shared_ptr<EngineFactory> manualFactory = registerBuilders(engineData, initMarket);
434
435 portfolio->reset();
436 portfolio->build(manualFactory);
437
438 for (auto [tradeId, trade] : portfolio->trades()) {
439 Real fx = 1.0;
440 if (trade->npvCurrency() != "EUR") {
441 string ccyPair = trade->npvCurrency() + baseCcy;
442 fx = initMarket->fxRate(ccyPair)->value();
443 }
444 baseManualPv[tradeId] = fx * trade->instrument()->NPV();
445 Real tradeNotional = fx * trade->notional();
446 Real simMarketTol =
447 1.e-5 * tradeNotional; // tolerance for difference to sim market is 0.1bp upfront (should this be tightened?)
448
449 BOOST_TEST_MESSAGE("Base PV check for trade " << tradeId << "; got " << baseManualPv[tradeId]
450 << ", expected " << basePv[tradeId]);
451 BOOST_CHECK_MESSAGE(std::fabs(baseManualPv[tradeId] - basePv[tradeId]) < simMarketTol,
452 "Base PV check error for trade " << tradeId << "; got " << baseManualPv[tradeId]
453 << ", but expected " << basePv[tradeId]);
454 }
455
456 QuantLib::ext::shared_ptr<TestMarketParCurves> initParMarket = QuantLib::ext::dynamic_pointer_cast<TestMarketParCurves>(initMarket);
457 BOOST_ASSERT(initParMarket);
458 BOOST_TEST_MESSAGE("testing discount curve par sensis");
459 // the discount curve par sensis
460 for (auto d_it : initParMarket->discountRateHelpersInstMap()) {
461 string ccy = d_it.first;
462 Real shiftSize = zeroAnalysis->sensitivityData()->discountCurveShiftData()[ccy]->shiftSize;
463 ShiftType shiftType = zeroAnalysis->sensitivityData()->discountCurveShiftData()[ccy]->shiftType;
464 vector<Period> parTenorVec = initParMarket->discountRateHelperTenorsMap().find(ccy)->second;
465 vector<Handle<Quote>> parValVecBase = initParMarket->discountRateHelperValuesMap().find(ccy)->second;
466 vector<string> sensiLabels(parValVecBase.size());
467 for (Size i = 0; i < parValVecBase.size(); i++) {
468 sensiLabels[i] = "DiscountCurve/" + ccy + "/" + to_string(i) + "/" + to_string(parTenorVec[i]);
469 }
470 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
471 shiftSize, shiftType, parDelta, zeroDelta, basePv);
472 }
473 BOOST_TEST_MESSAGE("testing index curve par sensis");
474 // the index curve par sensis
475 for (auto d_it : initParMarket->indexCurveRateHelperInstMap()) {
476 string idxName = d_it.first;
477 BOOST_TEST_MESSAGE(idxName);
478 auto itr = zeroAnalysis->sensitivityData()->indexCurveShiftData().find(idxName);
479 if (itr == zeroAnalysis->sensitivityData()->indexCurveShiftData().end())
480 zeroAnalysis->sensitivityData()->indexCurveShiftData()[idxName] =
481 QuantLib::ext::make_shared<SensitivityScenarioData::CurveShiftData>();
482 Real shiftSize = zeroAnalysis->sensitivityData()->indexCurveShiftData()[idxName]->shiftSize;
483 ShiftType shiftType = zeroAnalysis->sensitivityData()->indexCurveShiftData()[idxName]->shiftType;
484 vector<Period> parTenorVec = initParMarket->indexCurveRateHelperTenorsMap().find(idxName)->second;
485 vector<Handle<Quote>> parValVecBase = initParMarket->indexCurveRateHelperValuesMap().find(idxName)->second;
486 vector<string> sensiLabels(parValVecBase.size());
487 for (Size i = 0; i < parValVecBase.size(); i++) {
488 sensiLabels[i] = "IndexCurve/" + idxName + "/" + to_string(i) + "/" + to_string(parTenorVec[i]);
489 }
490 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
491 shiftSize, shiftType, parDelta, zeroDelta, basePv);
492 }
493 BOOST_TEST_MESSAGE("testing default curve par sensis");
494 // the default curve par sensis
495 for (auto d_it : initParMarket->defaultRateHelpersInstMap()) {
496 string name = d_it.first;
497 auto itr = zeroAnalysis->sensitivityData()->creditCurveShiftData().find(name);
498 if (itr == zeroAnalysis->sensitivityData()->creditCurveShiftData().end())
499 zeroAnalysis->sensitivityData()->creditCurveShiftData()[name] =
500 QuantLib::ext::make_shared<SensitivityScenarioData::CurveShiftData>();
501 Real shiftSize = zeroAnalysis->sensitivityData()->creditCurveShiftData()[name]->shiftSize;
502 ShiftType shiftType = zeroAnalysis->sensitivityData()->creditCurveShiftData()[name]->shiftType;
503 vector<Period> parTenorVec = initParMarket->defaultRateHelperTenorsMap().find(name)->second;
504 vector<Handle<Quote>> parValVecBase = initParMarket->defaultRateHelperValuesMap().find(name)->second;
505 vector<string> sensiLabels(parValVecBase.size());
506 for (Size i = 0; i < parValVecBase.size(); i++) {
507 sensiLabels[i] = "SurvivalProbability/" + name + "/" + to_string(i) + "/" + to_string(parTenorVec[i]);
508 }
509 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
510 shiftSize, shiftType, parDelta, zeroDelta, basePv);
511 }
512 BOOST_TEST_MESSAGE("testing cds par sensis");
513 // CDS Vol sensis (compare with zero sensi)
514 for (auto d_it : initParMarket->cdsVolRateHelperValuesMap()) {
515 string name = d_it.first;
516 Real shiftSize = zeroAnalysis->sensitivityData()->cdsVolShiftData()[name].shiftSize;
517 ShiftType shiftType = zeroAnalysis->sensitivityData()->cdsVolShiftData()[name].shiftType;
518 vector<Period> parTenorVec = initParMarket->cdsVolRateHelperTenorsMap().find(name)->second;
519 vector<Handle<Quote>> parValVecBase = initParMarket->cdsVolRateHelperValuesMap().find(name)->second;
520 vector<string> sensiLabels(parValVecBase.size());
521 for (Size i = 0; i < parValVecBase.size(); i++) {
522 sensiLabels[i] = "CDSVolatility/" + name + "/" + to_string(i) + "/" + to_string(parTenorVec[i]) + "/ATM";
523 }
524 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
525 shiftSize, shiftType, zeroDelta, zeroDelta, basePv);
526 }
527 BOOST_TEST_MESSAGE("testing eqVol curve par sensis");
528 // Equity Vol sensis (compare with zero sensi)
529 for (auto d_it : initParMarket->equityVolRateHelperValuesMap()) {
530 string name = d_it.first;
531 Real shiftSize = zeroAnalysis->sensitivityData()->equityVolShiftData()[name].shiftSize;
532 ShiftType shiftType = zeroAnalysis->sensitivityData()->equityVolShiftData()[name].shiftType;
533 vector<Period> parTenorVec = initParMarket->equityVolRateHelperTenorsMap().find(name)->second;
534 vector<Handle<Quote>> parValVecBase = initParMarket->equityVolRateHelperValuesMap().find(name)->second;
535 vector<string> sensiLabels(parValVecBase.size());
536 for (Size i = 0; i < parValVecBase.size(); i++) {
537 sensiLabels[i] = "EquityVolatility/" + name + "/" + to_string(i) + "/" + to_string(parTenorVec[i]) + "/ATM";
538 }
539 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
540 shiftSize, shiftType, zeroDelta, zeroDelta, basePv);
541 }
542 BOOST_TEST_MESSAGE("testing swaption vol par sensis");
543 // Swaption Vol sensis (compare with zero sensi)
544 for (auto d_it : initParMarket->swaptionVolRateHelperValuesMap()) {
545 string name = d_it.first;
546 Real shiftSize = zeroAnalysis->sensitivityData()->swaptionVolShiftData()[name].shiftSize;
547 ShiftType shiftType = zeroAnalysis->sensitivityData()->swaptionVolShiftData()[name].shiftType;
548 vector<Period> parTenorVec = initParMarket->swaptionVolRateHelperTenorsMap().find(name)->second;
549 vector<Period> swapTenorVec = initParMarket->swaptionVolRateHelperSwapTenorsMap().find(name)->second;
550 vector<Real> strikeSpreadVec = zeroAnalysis->sensitivityData()->swaptionVolShiftData()[name].shiftStrikes;
551 vector<Handle<Quote>> parValVecBase = initParMarket->swaptionVolRateHelperValuesMap().find(name)->second;
552 vector<string> sensiLabels(parValVecBase.size());
553 Size j = swapTenorVec.size();
554 Size k = zeroAnalysis->sensitivityData()->swaptionVolShiftData()[name].shiftStrikes.size();
555
556 for (Size i = 0; i < parValVecBase.size(); i++) {
557 Size strike = i % k;
558 Size parTenor = i / (j * k);
559 Size swapTenor = (i - parTenor * j * k - strike) / k;
560 std::ostringstream o;
561 if (close_enough(strikeSpreadVec[strike], 0))
562 o << "SwaptionVolatility/" << name << "/" << i << "/" << parTenorVec[parTenor] << "/"
563 << swapTenorVec[swapTenor] << "/ATM";
564 else
565 o << "SwaptionVolatility/" << name << "/" << i << "/" << parTenorVec[parTenor] << "/"
566 << swapTenorVec[swapTenor] << "/" << std::setprecision(4) << strikeSpreadVec[strike];
567 sensiLabels[i] = o.str();
568 }
569
570 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
571 shiftSize, shiftType, zeroDelta, zeroDelta, basePv);
572 }
573 BOOST_TEST_MESSAGE("testing base correlation par sensis");
574 // Base correlations sensis (compare with zero sensi)
575 for (auto d_it : initParMarket->baseCorrRateHelperValuesMap()) {
576 string name = d_it.first;
577 Real shiftSize = zeroAnalysis->sensitivityData()->baseCorrelationShiftData()[name].shiftSize;
578 ShiftType shiftType = zeroAnalysis->sensitivityData()->baseCorrelationShiftData()[name].shiftType;
579 vector<Period> parTenorVec = initParMarket->baseCorrRateHelperTenorsMap().find(name)->second;
580 vector<string> lossLevelVec = initParMarket->baseCorrLossLevelsMap().find(name)->second;
581 vector<Handle<Quote>> parValVecBase = initParMarket->baseCorrRateHelperValuesMap().find(name)->second;
582 vector<string> sensiLabels(parValVecBase.size());
583 Size j = lossLevelVec.size();
584 for (Size i = 0; i < parValVecBase.size(); i++) {
585 sensiLabels[i] = "BaseCorrelation/" + name + "/" + to_string(i) + "/" + lossLevelVec[i % j] + "/" +
586 to_string(parTenorVec[i / j]);
587 }
588 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
589 shiftSize, shiftType, zeroDelta, zeroDelta, basePv);
590 }
591 BOOST_TEST_MESSAGE("testing zero inflation par sensis");
592 // Zero Inflation sensis
593 for (auto d_it : initParMarket->zeroInflationRateHelperInstMap()) {
594 string idxName = d_it.first;
595 Real shiftSize = zeroAnalysis->sensitivityData()->zeroInflationCurveShiftData()[idxName]->shiftSize;
596 ShiftType shiftType = zeroAnalysis->sensitivityData()->zeroInflationCurveShiftData()[idxName]->shiftType;
597 vector<Period> parTenorVec = initParMarket->zeroInflationRateHelperTenorsMap().find(idxName)->second;
598 vector<Handle<Quote>> parValVecBase = initParMarket->zeroInflationRateHelperValuesMap().find(idxName)->second;
599 vector<string> sensiLabels(parValVecBase.size());
600 for (Size i = 0; i < parValVecBase.size(); i++) {
601 sensiLabels[i] = "ZeroInflationCurve/" + idxName + "/" + to_string(i) + "/" + to_string(parTenorVec[i]);
602 }
603 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
604 shiftSize, shiftType, parDelta, zeroDelta, basePv);
605 }
606 BOOST_TEST_MESSAGE("testing yoy inflation par sensis");
607 // YoY Inflation sensis
608 for (auto d_it : initParMarket->yoyInflationRateHelperInstMap()) {
609 string idxName = d_it.first;
610 Real shiftSize = zeroAnalysis->sensitivityData()->yoyInflationCurveShiftData()[idxName]->shiftSize;
611 ShiftType shiftType = zeroAnalysis->sensitivityData()->yoyInflationCurveShiftData()[idxName]->shiftType;
612 vector<Period> parTenorVec = initParMarket->yoyInflationRateHelperTenorsMap().find(idxName)->second;
613 vector<Handle<Quote>> parValVecBase = initParMarket->yoyInflationRateHelperValuesMap().find(idxName)->second;
614 vector<string> sensiLabels(parValVecBase.size());
615 for (Size i = 0; i < parValVecBase.size(); i++) {
616 sensiLabels[i] = "YoYInflationCurve/" + idxName + "/" + to_string(i) + "/" + to_string(parTenorVec[i]);
617 }
618 parSensiBumpAnalysis(portfolio, engineData, initMarket, baseManualPv, baseCcy, parValVecBase, sensiLabels,
619 shiftSize, shiftType, parDelta, zeroDelta, basePv);
620 }
621
622 // end
623 ObservationMode::instance().setMode(backupMode);
624 IndexManager::instance().clearHistories();
625}
626
627BOOST_FIXTURE_TEST_SUITE(OREAnalyticsTestSuite, ore::test::OreaTopLevelFixture)
628
629BOOST_AUTO_TEST_SUITE(ParSensitivityAnalysisManual)
630
631BOOST_AUTO_TEST_CASE(ParSwapBenchmarkTest){
632 BOOST_TEST_MESSAGE("Testing ParSwapBenchmark");
634}
635
636BOOST_AUTO_TEST_SUITE_END()
637
638BOOST_AUTO_TEST_SUITE_END()
639
640} // namespace testsuite
void alignPillars()
align pillars in scenario simulation market parameters with those of the par instruments
const ParContainer & parSensitivities() const
Return computed par sensitivities. Empty if they have not been computed yet.
std::map< ore::analytics::RiskFactorKey, std::pair< QuantLib::Real, QuantLib::Real > > shiftSizes() const
Return the zero rate and par rate absolute shift size for each risk factor key.
void computeParInstrumentSensitivities(const QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarket > &simMarket)
Compute par instrument sensitivities.
std::map< ore::analytics::RiskFactorKey, QuantLib::Real > parDeltas(const std::string &tradeId) const
Return the non-zero par deltas for the given tradeId.
static const string defaultConfiguration
OREAnalytics Top level fixture.
static void testParSwapBenchmark()
Benchmark par conversion against brute-force bump on the par instruments ("None" observation mode)
static QuantLib::ext::shared_ptr< ore::analytics::SensitivityScenarioData > setupSensitivityScenarioData(bool hasSwapVolCube=false, bool hasYYCapVols=false, bool parConversion=false)
SensitivityScenarioData instance.
static QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarketParameters > setupSimMarketData(bool hasSwapVolCube=false, bool hasYYCapVols=false)
ScenarioSimMarketParameters instance.
factory class for cloning a cached scenario
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::string to_string(const LocationInfo &l)
QuantLib::ext::shared_ptr< Trade > buildCreditDefaultSwap(string id, string ccy, string issuerId, string creditCurveId, bool isPayer, Real notional, int start, Size term, Real rate, Real spread, string fixedFreq, string fixedDC)
QuantLib::ext::shared_ptr< Trade > buildSyntheticCDO(string id, string name, vector< string > names, string longShort, string ccy, vector< string > ccys, bool isPayer, vector< Real > notionals, Real notional, int start, Size term, Real rate, Real spread, string fixedFreq, string fixedDC)
QuantLib::ext::shared_ptr< Trade > buildCap(string id, string ccy, string longShort, Real capRate, Real notional, int start, Size term, string floatFreq, string floatDC, string index, Calendar calendar, Natural spotDays, bool spotStartLag)
QuantLib::ext::shared_ptr< Trade > buildCmsCapFloor(string id, string ccy, string indexId, bool isPayer, Real notional, int start, Size term, Real capRate, Real floorRate, Real spread, string freq, string dc)
QuantLib::ext::shared_ptr< Trade > buildSwap(string id, string ccy, bool isPayer, Real notional, int start, Size term, Real rate, Real spread, string fixedFreq, string fixedDC, string floatFreq, string floatDC, string index, Calendar calendar, Natural spotDays, bool spotStartLag)
QuantLib::ext::shared_ptr< Trade > buildCPIInflationSwap(string id, string ccy, bool isPayer, Real notional, int start, Size term, Real spread, string floatFreq, string floatDC, string index, string cpiFreq, string cpiDC, string cpiIndex, Real baseRate, string observationLag, bool interpolated, Real cpiRate)
QuantLib::ext::shared_ptr< Trade > buildEquityOption(string id, string longShort, string putCall, Size expiry, string equityName, string currency, Real strike, Real quantity, Real premium, string premiumCcy, string premiumDate)
QuantLib::ext::shared_ptr< Trade > buildFloor(string id, string ccy, string longShort, Real floorRate, Real notional, int start, Size term, string floatFreq, string floatDC, string index, Calendar calendar, Natural spotDays, bool spotStartLag)
QuantLib::ext::shared_ptr< Trade > buildCrossCcyBasisSwap(string id, string recCcy, Real recNotional, string payCcy, Real payNotional, int start, Size term, Real recLegSpread, Real payLegSpread, string recFreq, string recDC, string recIndex, Calendar recCalendar, string payFreq, string payDC, string payIndex, Calendar payCalendar, Natural spotDays, bool spotStartLag, bool notionalInitialExchange, bool notionalFinalExchange, bool notionalAmortizingExchange, bool isRecLegFXResettable, bool isPayLegFXResettable)
QuantLib::ext::shared_ptr< Trade > buildYYInflationSwap(string id, string ccy, bool isPayer, Real notional, int start, Size term, Real spread, string floatFreq, string floatDC, string index, string yyFreq, string yyDC, string yyIndex, string observationLag, Size fixDays)
BOOST_AUTO_TEST_CASE(ZeroShifts1d)
Singleton class to hold global Observation Mode.
Perfrom sensitivity analysis for a given portfolio.
Fixture that can be used at top level of OREAnalytics test suites.
More par Sensitivity analysis tests.
A Market class that can be updated by Scenarios.
string name
Class for converting zero sensitivities to par sensitivities.