19#include <boost/test/unit_test.hpp>
51#include <oret/toplevelfixture.hpp>
52#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
53#include <ql/time/calendars/target.hpp>
54#include <ql/time/date.hpp>
55#include <ql/time/daycounters/actualactual.hpp>
63using namespace boost::unit_test_framework;
75QuantLib::ext::shared_ptr<data::Conventions>
stressConv() {
76 QuantLib::ext::shared_ptr<data::Conventions> conventions(
new data::Conventions());
78 QuantLib::ext::shared_ptr<data::Convention> swapIndexConv(
80 conventions->add(swapIndexConv);
84 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"EUR-6M-SWAP-CONVENTIONS",
"TARGET",
"A",
"MF",
85 "30/360",
"EUR-EURIBOR-6M"));
86 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"USD-3M-SWAP-CONVENTIONS",
"TARGET",
"Q",
"MF",
87 "30/360",
"USD-LIBOR-3M"));
88 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"USD-6M-SWAP-CONVENTIONS",
"TARGET",
"Q",
"MF",
89 "30/360",
"USD-LIBOR-6M"));
90 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"GBP-6M-SWAP-CONVENTIONS",
"TARGET",
"A",
"MF",
91 "30/360",
"GBP-LIBOR-6M"));
92 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"JPY-6M-SWAP-CONVENTIONS",
"TARGET",
"A",
"MF",
93 "30/360",
"JPY-LIBOR-6M"));
94 conventions->add(QuantLib::ext::make_shared<data::IRSwapConvention>(
"CHF-6M-SWAP-CONVENTIONS",
"TARGET",
"A",
"MF",
95 "30/360",
"CHF-LIBOR-6M"));
97 conventions->add(QuantLib::ext::make_shared<data::DepositConvention>(
"EUR-DEP-CONVENTIONS",
"EUR-EURIBOR"));
98 conventions->add(QuantLib::ext::make_shared<data::DepositConvention>(
"USD-DEP-CONVENTIONS",
"USD-LIBOR"));
99 conventions->add(QuantLib::ext::make_shared<data::DepositConvention>(
"GBP-DEP-CONVENTIONS",
"GBP-LIBOR"));
100 conventions->add(QuantLib::ext::make_shared<data::DepositConvention>(
"JPY-DEP-CONVENTIONS",
"JPY-LIBOR"));
101 conventions->add(QuantLib::ext::make_shared<data::DepositConvention>(
"CHF-DEP-CONVENTIONS",
"CHF-LIBOR"));
103 InstrumentConventions::instance().setConventions(conventions);
109 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData(
112 simMarketData->baseCcy() =
"EUR";
113 simMarketData->setDiscountCurveNames({
"EUR",
"GBP",
"USD",
"CHF",
"JPY"});
114 simMarketData->setYieldCurveTenors(
"", {1 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
115 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years, 30 * Years});
116 simMarketData->setIndices(
117 {
"EUR-EURIBOR-6M",
"USD-LIBOR-3M",
"USD-LIBOR-6M",
"GBP-LIBOR-6M",
"CHF-LIBOR-6M",
"JPY-LIBOR-6M"});
118 simMarketData->interpolation() =
"LogLinear";
119 simMarketData->extrapolation() =
"FlatFwd";
121 simMarketData->setSwapVolTerms(
"", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years});
122 simMarketData->setSwapVolExpiries(
123 "", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years});
124 simMarketData->setSwapVolKeys({
"EUR",
"GBP",
"USD",
"CHF",
"JPY"});
125 simMarketData->swapVolDecayMode() =
"ForwardVariance";
126 simMarketData->setSimulateSwapVols(
true);
128 simMarketData->setFxVolExpiries(
"",
129 vector<Period>{1 * Months, 3 * Months, 6 * Months, 2 * Years, 3 * Years, 4 * Years, 5 * Years});
130 simMarketData->setFxVolDecayMode(
string(
"ConstantVariance"));
131 simMarketData->setSimulateFXVols(
true);
132 simMarketData->setFxVolIsSurface(
false);
133 simMarketData->setFxVolMoneyness(vector<Real>{0.0});
134 simMarketData->setFxVolCcyPairs({
"EURUSD",
"EURGBP",
"EURCHF",
"EURJPY"});
136 simMarketData->setFxCcyPairs({
"EURUSD",
"EURGBP",
"EURCHF",
"EURJPY"});
138 simMarketData->setSimulateCapFloorVols(
true);
139 simMarketData->capFloorVolDecayMode() =
"ForwardVariance";
140 simMarketData->setCapFloorVolKeys({
"EUR",
"USD"});
141 simMarketData->setCapFloorVolExpiries(
142 "", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years});
143 simMarketData->setCapFloorVolStrikes(
"", {0.00, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06});
145 return simMarketData;
149 QuantLib::ext::shared_ptr<StressTestScenarioData> stressData = QuantLib::ext::make_shared<StressTestScenarioData>();
152 data.label =
"stresstest_1";
154 data.discountCurveShifts[
"EUR"].shiftType = ShiftType::Absolute;
155 data.discountCurveShifts[
"EUR"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
156 5 * Years, 7 * Years, 10 * Years};
157 data.discountCurveShifts[
"EUR"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
159 data.discountCurveShifts[
"USD"].shiftType = ShiftType::Absolute;
160 data.discountCurveShifts[
"USD"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
161 5 * Years, 7 * Years, 10 * Years};
162 data.discountCurveShifts[
"USD"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
164 data.discountCurveShifts[
"GBP"].shiftType = ShiftType::Absolute;
165 data.discountCurveShifts[
"GBP"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
166 5 * Years, 7 * Years, 10 * Years};
167 data.discountCurveShifts[
"GBP"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
169 data.discountCurveShifts[
"JPY"].shiftType = ShiftType::Absolute;
170 data.discountCurveShifts[
"JPY"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
171 5 * Years, 7 * Years, 10 * Years};
172 data.discountCurveShifts[
"JPY"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
174 data.discountCurveShifts[
"CHF"].shiftType = ShiftType::Absolute;
175 data.discountCurveShifts[
"CHF"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
176 5 * Years, 7 * Years, 10 * Years};
177 data.discountCurveShifts[
"CHF"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
179 data.indexCurveShifts[
"EUR-EURIBOR-6M"].shiftType = ShiftType::Absolute;
180 data.indexCurveShifts[
"EUR-EURIBOR-6M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
181 5 * Years, 7 * Years, 10 * Years};
182 data.indexCurveShifts[
"EUR-EURIBOR-6M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
184 data.indexCurveShifts[
"USD-LIBOR-3M"].shiftType = ShiftType::Absolute;
185 data.indexCurveShifts[
"USD-LIBOR-3M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
186 5 * Years, 7 * Years, 10 * Years};
187 data.indexCurveShifts[
"USD-LIBOR-3M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
189 data.indexCurveShifts[
"USD-LIBOR-6M"].shiftType = ShiftType::Absolute;
190 data.indexCurveShifts[
"USD-LIBOR-6M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
191 5 * Years, 7 * Years, 10 * Years};
192 data.indexCurveShifts[
"USD-LIBOR-6M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
194 data.indexCurveShifts[
"GBP-LIBOR-6M"].shiftType = ShiftType::Absolute;
195 data.indexCurveShifts[
"GBP-LIBOR-6M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
196 5 * Years, 7 * Years, 10 * Years};
197 data.indexCurveShifts[
"GBP-LIBOR-6M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
199 data.indexCurveShifts[
"CHF-LIBOR-6M"].shiftType = ShiftType::Absolute;
200 data.indexCurveShifts[
"CHF-LIBOR-6M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
201 5 * Years, 7 * Years, 10 * Years};
202 data.indexCurveShifts[
"CHF-LIBOR-6M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
204 data.indexCurveShifts[
"JPY-LIBOR-6M"].shiftType = ShiftType::Absolute;
205 data.indexCurveShifts[
"JPY-LIBOR-6M"].shiftTenors = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
206 5 * Years, 7 * Years, 10 * Years};
207 data.indexCurveShifts[
"JPY-LIBOR-6M"].shifts = {0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007};
209 data.fxShifts[
"EURUSD"].shiftType = ShiftType::Relative;
210 data.fxShifts[
"EURUSD"].shiftSize = 0.01;
212 data.fxShifts[
"EURGBP"].shiftType = ShiftType::Relative;
213 data.fxShifts[
"EURGBP"].shiftSize = 0.01;
215 data.fxShifts[
"EURJPY"].shiftType = ShiftType::Relative;
216 data.fxShifts[
"EURJPY"].shiftSize = 0.01;
218 data.fxShifts[
"EURCHF"].shiftType = ShiftType::Relative;
219 data.fxShifts[
"EURCHF"].shiftSize = 0.01;
221 data.fxVolShifts[
"EURUSD"].shiftType = ShiftType::Absolute;
222 data.fxVolShifts[
"EURUSD"].shiftExpiries = {6 * Months, 2 * Years, 3 * Years, 5 * Years};
223 data.fxVolShifts[
"EURUSD"].shifts = {0.10, 0.11, 0.13, 0.14};
225 data.fxVolShifts[
"EURGBP"].shiftType = ShiftType::Absolute;
226 data.fxVolShifts[
"EURGBP"].shiftExpiries = {6 * Months, 2 * Years, 3 * Years, 5 * Years};
227 data.fxVolShifts[
"EURGBP"].shifts = {0.10, 0.11, 0.13, 0.14};
229 data.fxVolShifts[
"EURJPY"].shiftType = ShiftType::Absolute;
230 data.fxVolShifts[
"EURJPY"].shiftExpiries = {6 * Months, 2 * Years, 3 * Years, 5 * Years};
231 data.fxVolShifts[
"EURJPY"].shifts = {0.10, 0.11, 0.13, 0.14};
233 data.fxVolShifts[
"EURCHF"].shiftType = ShiftType::Absolute;
234 data.fxVolShifts[
"EURCHF"].shiftExpiries = {6 * Months, 2 * Years, 3 * Years, 5 * Years};
235 data.fxVolShifts[
"EURCHF"].shifts = {0.10, 0.11, 0.13, 0.14};
237 stressData->data() = vector<StressTestScenarioData::StressTestData>(1,
data);
244BOOST_AUTO_TEST_SUITE(StressTestingTest)
247 BOOST_TEST_MESSAGE(
"Testing Sensitivity Par Conversion");
249 SavedSettings backup;
251 Date today = Date(14, April, 2016);
252 Settings::instance().evaluationDate() = today;
254 BOOST_TEST_MESSAGE(
"Today is " << today);
257 string baseCcy =
"EUR";
259 ccys.push_back(baseCcy);
260 ccys.push_back(
"GBP");
261 ccys.push_back(
"CHF");
262 ccys.push_back(
"USD");
263 ccys.push_back(
"JPY");
266 QuantLib::ext::shared_ptr<Market> initMarket = QuantLib::ext::make_shared<TestMarket>(today);
276 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarket> simMarket =
277 QuantLib::ext::make_shared<analytics::ScenarioSimMarket>(initMarket, simMarketData);
280 QuantLib::ext::shared_ptr<Scenario> baseScenario = simMarket->baseScenario();
281 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory = QuantLib::ext::make_shared<CloneScenarioFactory>(baseScenario);
284 QuantLib::ext::shared_ptr<StressScenarioGenerator> scenarioGenerator =
285 QuantLib::ext::make_shared<StressScenarioGenerator>(stressData, baseScenario, simMarketData, simMarket, scenarioFactory);
286 simMarket->scenarioGenerator() = scenarioGenerator;
289 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
290 engineData->model(
"Swap") =
"DiscountedCashflows";
291 engineData->engine(
"Swap") =
"DiscountingSwapEngine";
292 engineData->model(
"CrossCurrencySwap") =
"DiscountedCashflows";
293 engineData->engine(
"CrossCurrencySwap") =
"DiscountingCrossCurrencySwapEngine";
294 engineData->model(
"EuropeanSwaption") =
"BlackBachelier";
295 engineData->engine(
"EuropeanSwaption") =
"BlackBachelierSwaptionEngine";
296 engineData->model(
"FxForward") =
"DiscountedCashflows";
297 engineData->engine(
"FxForward") =
"DiscountingFxForwardEngine";
298 engineData->model(
"FxOption") =
"GarmanKohlhagen";
299 engineData->engine(
"FxOption") =
"AnalyticEuropeanEngine";
300 engineData->model(
"CapFloor") =
"IborCapModel";
301 engineData->engine(
"CapFloor") =
"IborCapEngine";
302 engineData->model(
"CapFlooredIborLeg") =
"BlackOrBachelier";
303 engineData->engine(
"CapFlooredIborLeg") =
"BlackIborCouponPricer";
304 QuantLib::ext::shared_ptr<EngineFactory> factory = QuantLib::ext::make_shared<EngineFactory>(engineData, simMarket);
307 QuantLib::ext::shared_ptr<Portfolio> portfolio(
new Portfolio());
308 portfolio->add(buildSwap(
"1_Swap_EUR",
"EUR",
true, 10000000.0, 0, 10, 0.03, 0.00,
"1Y",
"30/360",
"6M",
"A360",
310 portfolio->add(buildSwap(
"2_Swap_USD",
"USD",
true, 10000000.0, 0, 15, 0.02, 0.00,
"6M",
"30/360",
"3M",
"A360",
312 portfolio->add(buildSwap(
"3_Swap_GBP",
"GBP",
true, 10000000.0, 0, 20, 0.04, 0.00,
"6M",
"30/360",
"3M",
"A360",
314 portfolio->add(buildSwap(
"4_Swap_JPY",
"JPY",
true, 1000000000.0, 0, 5, 0.01, 0.00,
"6M",
"30/360",
"3M",
"A360",
316 portfolio->add(buildEuropeanSwaption(
"5_Swaption_EUR",
"Long",
"EUR",
true, 1000000.0, 10, 10, 0.03, 0.00,
"1Y",
317 "30/360",
"6M",
"A360",
"EUR-EURIBOR-6M"));
318 portfolio->add(buildEuropeanSwaption(
"6_Swaption_EUR",
"Long",
"EUR",
true, 1000000.0, 2, 5, 0.03, 0.00,
"1Y",
319 "30/360",
"6M",
"A360",
"EUR-EURIBOR-6M"));
320 portfolio->add(buildFxOption(
"7_FxOption_EUR_USD",
"Long",
"Call", 3,
"EUR", 10000000.0,
"USD", 11000000.0));
321 portfolio->add(buildFxOption(
"8_FxOption_EUR_GBP",
"Long",
"Call", 7,
"EUR", 10000000.0,
"GBP", 11000000.0));
322 portfolio->add(buildCap(
"9_Cap_EUR",
"EUR",
"Long", 0.05, 1000000.0, 0, 10,
"6M",
"A360",
"EUR-EURIBOR-6M"));
323 portfolio->add(buildFloor(
"10_Floor_USD",
"USD",
"Long", 0.01, 1000000.0, 0, 10,
"3M",
"A360",
"USD-LIBOR-3M"));
324 portfolio->build(factory);
326 BOOST_TEST_MESSAGE(
"Portfolio size after build: " << portfolio->size());
331 std::map<std::string, Real> baseNPV = analysis.
baseNPV();
332 std::map<std::pair<std::string, std::string>, Real> shiftedNPV = analysis.
shiftedNPV();
334 QL_REQUIRE(shiftedNPV.size() > 0,
"no shifted results");
342 std::vector<Results> cachedResults = {{
"10_Floor_USD",
"stresstest_1", -2487.75},
343 {
"1_Swap_EUR",
"stresstest_1", 629406},
344 {
"2_Swap_USD",
"stresstest_1", 599846},
345 {
"3_Swap_GBP",
"stresstest_1", 1.11005e+06},
346 {
"4_Swap_JPY",
"stresstest_1", 186736},
347 {
"5_Swaption_EUR",
"stresstest_1", 13623.1},
348 {
"6_Swaption_EUR",
"stresstest_1", 5041.52},
349 {
"7_FxOption_EUR_USD",
"stresstest_1", 748160},
350 {
"8_FxOption_EUR_GBP",
"stresstest_1", 1.21724e+06},
351 {
"9_Cap_EUR",
"stresstest_1", 1175.5}};
353 std::map<pair<string, string>, Real> stressMap;
354 for (Size i = 0; i < cachedResults.size(); ++i) {
355 pair<string, string> p(cachedResults[i].
id, cachedResults[i].label);
356 stressMap[p] = cachedResults[i].shift;
359 Real tolerance = 0.01;
361 for (
auto data : shiftedNPV) {
362 pair<string, string> p =
data.first;
363 string id =
data.first.first;
364 Real npv =
data.second;
365 QL_REQUIRE(baseNPV.find(
id) != baseNPV.end(),
"base npv not found for trade " <<
id);
366 Real base = baseNPV[id];
367 Real delta = npv - base;
368 if (fabs(delta) > 0.0) {
370 QL_REQUIRE(stressMap.find(p) != stressMap.end(),
371 "pair (" << p.first <<
", " << p.second <<
") not found in sensi map");
372 BOOST_CHECK_MESSAGE(fabs(delta - stressMap[p]) < tolerance ||
373 fabs((delta - stressMap[p]) / delta) < tolerance,
374 "stress test regression failed for pair (" << p.first <<
", " << p.second
375 <<
"): " << delta <<
" vs " << stressMap[p]);
378 BOOST_CHECK_MESSAGE(
count == cachedResults.size(),
"number of non-zero stress impacts ("
379 <<
count <<
") do not match regression data ("
380 << cachedResults.size() <<
")");
381 IndexManager::instance().clearHistories();
384BOOST_AUTO_TEST_SUITE_END()
386BOOST_AUTO_TEST_SUITE_END()
ScenarioSimMarket description.
const std::map< std::pair< std::string, std::string >, Real > & shiftedNPV()
Return shifted NPVs by trade and scenario.
const std::map< std::string, Real > & baseNPV()
Return base NPV by trade, before shift.
OREAnalytics Top level fixture.
Simple flat market setup to be used in the test suite.
factory class for cloning a cached scenario
Class that wraps a sensitivity stream and filters out negligible records.
A cube implementation that stores the cube in memory.
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 > buildFxOption(string id, string longShort, string putCall, Size expiry, string boughtCcy, Real boughtAmount, string soldCcy, Real soldAmount, Real premium, string premiumCcy, string premiumDate)
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 > 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 > buildEuropeanSwaption(string id, string longShort, string ccy, bool isPayer, Real notional, int start, Size term, Real rate, Real spread, string fixedFreq, string fixedDC, string floatFreq, string floatDC, string index, string cashPhysical, Real premium, string premiumCcy, string premiumDate)
Singleton class to hold global Observation Mode.
Fixture that can be used at top level of OREAnalytics test suites.
Perform parametric var calculation for a given portfolio.
risk class and type filter
A Market class that can be updated by Scenarios.
A class to hold Scenario parameters for scenarioSimMarket.
Class for aggregating SensitivityRecords.
Perform sensitivity analysis for a given portfolio.
Class for streaming SensitivityRecords from a SensitivityCube.
Class for streaming SensitivityRecords from file.
Class for streaming SensitivityRecords from in-memory container.
Struct for holding a sensitivity record.
Base class for sensitivity record streamer.
Stress scenario generation.
perform a stress testing analysis for a given portfolio.
BOOST_AUTO_TEST_CASE(regression)
QuantLib::ext::shared_ptr< data::Conventions > stressConv()
QuantLib::ext::shared_ptr< analytics::ScenarioSimMarketParameters > setupStressSimMarketData()
QuantLib::ext::shared_ptr< StressTestScenarioData > setupStressScenarioData()
The counterparty cube calculator interface.