Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
simulationmeasures.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/test/unit_test.hpp>
35#include <oret/toplevelfixture.hpp>
37
42#include <qle/models/lgm.hpp>
48
49#include <ql/cashflows/simplecashflow.hpp>
50#include <ql/currencies/america.hpp>
51#include <ql/currencies/europe.hpp>
52#include <ql/indexes/ibor/all.hpp>
53#include <ql/indexes/swap/euriborswap.hpp>
54#include <ql/indexes/swap/usdliborswap.hpp>
55#include <ql/instruments/cpicapfloor.hpp>
56#include <ql/instruments/makeswaption.hpp>
57#include <ql/instruments/makevanillaswap.hpp>
58#include <ql/math/statistics/incrementalstatistics.hpp>
59#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
60#include <ql/quotes/simplequote.hpp>
61#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
62#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
63#include <ql/termstructures/yield/flatforward.hpp>
64#include <ql/time/calendars/target.hpp>
65#include <ql/time/daycounters/actual360.hpp>
66#include <ql/time/daycounters/thirty360.hpp>
67#include "testmarket.hpp"
68
69#include <boost/timer/timer.hpp>
70
71using namespace QuantLib;
72using namespace QuantExt;
73using namespace boost::unit_test_framework;
74using namespace ore::analytics;
75using namespace ore::data;
76using namespace ore;
77using boost::timer::cpu_timer;
78using boost::timer::default_places;
80
81namespace {
82
83QuantLib::ext::shared_ptr<data::Conventions> convs() {
84 QuantLib::ext::shared_ptr<data::Conventions> conventions(new data::Conventions());
85
86 QuantLib::ext::shared_ptr<data::Convention> swapIndexConv(
87 new data::SwapIndexConvention("EUR-CMS-2Y", "EUR-6M-SWAP-CONVENTIONS"));
88 conventions->add(swapIndexConv);
89
90 QuantLib::ext::shared_ptr<data::Convention> swapConv(
91 new data::IRSwapConvention("EUR-6M-SWAP-CONVENTIONS", "TARGET", "Annual", "MF", "30/360", "EUR-EURIBOR-6M"));
92 conventions->add(swapConv);
93
94 InstrumentConventions::instance().setConventions(conventions);
95
96 return conventions;
97}
98
99struct TestData {
100 TestData(std::string measure, Real shiftHorizon = 0.0) : referenceDate(30, July, 2015) {
101 Settings::instance().evaluationDate() = referenceDate;
102
103 // Build test market
104 market = QuantLib::ext::make_shared<TestMarket>(referenceDate);
105
106 // Build IR configurations
107 CalibrationType calibrationType = CalibrationType::Bootstrap;
108 LgmData::ReversionType revType = LgmData::ReversionType::HullWhite;
109 LgmData::VolatilityType volType = LgmData::VolatilityType::Hagan;
110 vector<string> swaptionExpiries = {"1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "15Y", "20Y", "30Y"};
111 vector<string> swaptionTerms = {"5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y", "5Y"};
112 vector<string> swaptionStrikes(swaptionExpiries.size(), "ATM");
113 vector<Time> hTimes = {};
114 vector<Time> aTimes = {};
115
116 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
117
118 vector<Real> hValues = {0.02};
119 vector<Real> aValues = {0.08};
120 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>("EUR", calibrationType, revType, volType, false,
121 ParamType::Constant, hTimes, hValues, true,
122 ParamType::Piecewise, aTimes, aValues, shiftHorizon, 1.0,
123 swaptionExpiries, swaptionTerms, swaptionStrikes));
124
125 hValues = {0.03};
126 aValues = {0.009};
127 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
128 "USD", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
129 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
130
131 hValues = {0.04};
132 aValues = {0.01};
133 irConfigs.push_back(QuantLib::ext::make_shared<IrLgmData>(
134 "GBP", calibrationType, revType, volType, false, ParamType::Constant, hTimes, hValues, true,
135 ParamType::Piecewise, aTimes, aValues, 0.0, 1.0, swaptionExpiries, swaptionTerms, swaptionStrikes));
136
137 // Compile FX configurations
138 vector<string> optionExpiries = {"1Y", "2Y", "3Y", "5Y", "7Y", "10Y"};
139 vector<string> optionStrikes(optionExpiries.size(), "ATMF");
140 vector<Time> sigmaTimes = {};
141
142 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
143
144 vector<Real> sigmaValues = {0.15};
145 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>("USD", "EUR", calibrationType, true, ParamType::Piecewise,
146 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
147
148 sigmaValues = {0.15};
149 fxConfigs.push_back(QuantLib::ext::make_shared<FxBsData>("GBP", "EUR", calibrationType, true, ParamType::Piecewise,
150 sigmaTimes, sigmaValues, optionExpiries, optionStrikes));
151
152 std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
153 // Inflation configurations
154 vector<QuantLib::ext::shared_ptr<InflationModelData>> infConfigs;
155 // Credit configs
156 std::vector<QuantLib::ext::shared_ptr<CrLgmData>> crLgmConfigs;
157 std::vector<QuantLib::ext::shared_ptr<CrCirData>> crCirConfigs;
158
159 std::vector<QuantLib::ext::shared_ptr<CommoditySchwartzData>> comConfigs;
160
162 cmb.addCorrelation("IR:EUR", "IR:USD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.6)));
163 cmb.addCorrelation("IR:EUR", "IR:GBP", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
164 cmb.addCorrelation("IR:USD", "IR:GBP", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
165 cmb.addCorrelation("FX:USDEUR", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
166 cmb.addCorrelation("IR:EUR", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.2)));
167 cmb.addCorrelation("IR:EUR", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
168 cmb.addCorrelation("IR:USD", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(-0.2)));
169 cmb.addCorrelation("IR:USD", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(-0.1)));
170 cmb.addCorrelation("IR:GBP", "FX:USDEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.0)));
171 cmb.addCorrelation("IR:GBP", "FX:GBPEUR", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.1)));
172
173 Real tolerance = 1e-4;
174 QuantLib::ext::shared_ptr<CrossAssetModelData> config1(QuantLib::ext::make_shared<CrossAssetModelData>(
175 irConfigs, fxConfigs, eqConfigs, infConfigs, crLgmConfigs, crCirConfigs, comConfigs, 0, cmb.correlations(),
176 tolerance, measure, CrossAssetModel::Discretization::Exact));
177 QuantLib::ext::shared_ptr<CrossAssetModelData> config2(QuantLib::ext::make_shared<CrossAssetModelData>(
178 irConfigs, fxConfigs, eqConfigs, infConfigs, crLgmConfigs, crCirConfigs, comConfigs, 0, cmb.correlations(),
179 tolerance, measure, CrossAssetModel::Discretization::Euler));
180
181 CrossAssetModelBuilder modelBuilder1(market, config1);
182 ccLgmExact = *modelBuilder1.model();
183
184 CrossAssetModelBuilder modelBuilder2(market, config2);
185 ccLgmEuler = *modelBuilder2.model();
186
187 lgm = QuantLib::ext::make_shared<QuantExt::LGM>(ccLgmExact->irlgm1f(0));
188 }
189
190 SavedSettings backup;
191 Date referenceDate;
192 QuantLib::ext::shared_ptr<CrossAssetModelData> config;
193 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> ccLgmExact, ccLgmEuler;
194 QuantLib::ext::shared_ptr<QuantExt::LGM> lgm;
195 QuantLib::ext::shared_ptr<ore::data::Market> market;
196};
197
198} // anonymous namespace
199
200BOOST_FIXTURE_TEST_SUITE(OREAnalyticsTestSuite, ore::test::OreaTopLevelFixture)
201
202BOOST_AUTO_TEST_SUITE(SimulationMeasuresTest)
203
204void test_measure(std::string measureName, Real shiftHorizon, std::string discName) {
205
206 BOOST_TEST_MESSAGE("Testing market simulation, measure " << measureName << ", horizon " << shiftHorizon
207 << ", discretization " << discName);
208
209 TestData d(measureName, shiftHorizon);
210
211 // Simulation date grid
212 Date today = d.referenceDate;
213 std::vector<Period> tenorGrid;
214 if (discName == "exact")
215 tenorGrid = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years};
216 else {
217 for (Size i = 1; i <= 60; ++i)
218 tenorGrid.push_back(i * 2 * Months);
219 }
220 QuantLib::ext::shared_ptr<DateGrid> grid = QuantLib::ext::make_shared<DateGrid>(tenorGrid);
221
222 // Model
223 QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel> model = discName == "exact" ? d.ccLgmExact : d.ccLgmEuler;
224
225 // Simulation market parameters, we just need the yield curve structure here
226 QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> simMarketConfig(new ScenarioSimMarketParameters);
227 simMarketConfig->setYieldCurveTenors("", {3 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
228 5 * Years, 7 * Years, 10 * Years, 12 * Years});
229 simMarketConfig->setSimulateFXVols(false);
230 simMarketConfig->setSimulateEquityVols(false);
231
232 simMarketConfig->baseCcy() = "EUR";
233 simMarketConfig->setDiscountCurveNames({"EUR", "USD", "GBP"});
234 simMarketConfig->setIndices({"EUR-EURIBOR-6M", "USD-LIBOR-3M", "GBP-LIBOR-6M"});
235 simMarketConfig->interpolation() = "LogLinear";
236 simMarketConfig->setSwapVolExpiries("", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years});
237 simMarketConfig->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years});
238 simMarketConfig->setFxCcyPairs({"USDEUR", "GBPEUR"});
239
240 QuantLib::ext::shared_ptr<ScenarioGeneratorData> sgd(new ScenarioGeneratorData);
241 sgd->sequenceType() = Sobol;
242 sgd->seed() = 42;
243 sgd->setGrid(grid);
244
246 QuantLib::ext::shared_ptr<ScenarioFactory> sf = QuantLib::ext::make_shared<SimpleScenarioFactory>(true);
247 QuantLib::ext::shared_ptr<ScenarioGenerator> sg = sgb.build(model, sf, simMarketConfig, today, d.market);
248
249 convs();
250 auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(d.market, simMarketConfig);
251 simMarket->scenarioGenerator() = sg;
252
253 // Basic Martingale tests
254 Size samples = 5000;
255 Real eur = 0.0, usd = 0.0, gbp = 0.0, eur2 = 0.0, usd2 = 0.0, gbp2 = 0.0;
256 Real eur3 = 0.0, usd3 = 0.0, gbp3 = 0.0;
257 int horizon = 10;
258
259 Date d1 = grid->dates().back();
260 Date d2 = d1 + horizon * Years;
261 Real relTolerance = 0.01;
262 Real eurExpected = d.market->discountCurve("EUR")->discount(d2);
263 Real eurExpected2 = d.market->discountCurve("EUR")->discount(d1);
264 Real gbpExpected = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(d2);
265 Real gbpExpected2 = d.market->fxRate("GBPEUR")->value() * d.market->discountCurve("GBP")->discount(d1);
266 Real usdExpected = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(d2);
267 Real usdExpected2 = d.market->fxRate("USDEUR")->value() * d.market->discountCurve("USD")->discount(d1);
268
269 cpu_timer timer, timer2;
270 BOOST_TEST_MESSAGE("running " << samples << " samples simulation over " << grid->dates().size() << " time steps");
271 for (Size i = 0; i < samples; i++) {
272 for (Date d : grid->dates()) {
273 timer.resume();
274 simMarket->update(d);
275 timer.stop();
276 if (d == grid->dates().back()) {
277 Real numeraire = simMarket->numeraire();
278 Real usdeurFX = simMarket->fxRate("USDEUR")->value();
279 Real gbpeurFX = simMarket->fxRate("GBPEUR")->value();
280 Real eurDiscount = simMarket->discountCurve("EUR")->discount(1.0 * horizon);
281 Real gbpDiscount = simMarket->discountCurve("GBP")->discount(1.0 * horizon);
282 ;
283 Real usdDiscount = simMarket->discountCurve("USD")->discount(1.0 * horizon);
284 ;
285 Real eurIndex =
286 simMarket->iborIndex("EUR-EURIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
287 Real gbpIndex =
288 simMarket->iborIndex("GBP-LIBOR-6M")->forwardingTermStructure()->discount(1.0 * horizon);
289 ;
290 Real usdIndex =
291 simMarket->iborIndex("USD-LIBOR-3M")->forwardingTermStructure()->discount(1.0 * horizon);
292 ;
293 eur += eurDiscount / numeraire;
294 gbp += gbpDiscount * gbpeurFX / numeraire;
295 usd += usdDiscount * usdeurFX / numeraire;
296 eur2 += 1.0 / numeraire;
297 gbp2 += gbpeurFX / numeraire;
298 usd2 += usdeurFX / numeraire;
299 eur3 += eurIndex / numeraire;
300 gbp3 += gbpIndex * gbpeurFX / numeraire;
301 usd3 += usdIndex * usdeurFX / numeraire;
302 }
303 }
304 }
305
306 timer2.stop();
307
308 eur /= samples;
309 gbp /= samples;
310 usd /= samples;
311 eur2 /= samples;
312 gbp2 /= samples;
313 usd2 /= samples;
314 eur3 /= samples;
315 gbp3 /= samples;
316 usd3 /= samples;
317
318 Real eurDiff = fabs(eur - eurExpected) / eurExpected;
319 BOOST_CHECK_MESSAGE(eurDiff < relTolerance, "EUR 20Y Discount mismatch: " << eur << " vs " << eurExpected);
320
321 Real gbpDiff = fabs(gbp - gbpExpected) / gbpExpected;
322 BOOST_CHECK_MESSAGE(gbpDiff < relTolerance,
323 "GBP 20Y Discount mismatch: " << gbp << " vs " << gbpExpected << " (" << gbpDiff << ")");
324
325 Real usdDiff = fabs(usd - usdExpected) / usdExpected;
326 BOOST_CHECK_MESSAGE(usdDiff < relTolerance, "USD 20Y Discount mismatch: " << usd << " vs " << usdExpected);
327
328 Real eur3Diff = fabs(eur3 - eurExpected) / eurExpected;
329 BOOST_CHECK_MESSAGE(eur3Diff < relTolerance, "EUR 20Y Index Discount mismatch: " << eur3 << " vs " << eurExpected);
330
331 Real gbp3Diff = fabs(gbp3 - gbpExpected) / gbpExpected;
332 BOOST_CHECK_MESSAGE(gbp3Diff < relTolerance, "GBP 20Y Index Discount mismatch: " << gbp3 << " vs " << gbpExpected);
333
334 Real usd3Diff = fabs(usd3 - usdExpected) / usdExpected;
335 BOOST_CHECK_MESSAGE(usd3Diff < relTolerance, "USD 20Y Index Discount mismatch: " << usd3 << " vs " << usdExpected);
336
337 Real eur2Diff = fabs(eur2 - eurExpected2) / eurExpected2;
338 BOOST_CHECK_MESSAGE(eur2Diff < relTolerance, "EUR 10Y Discount mismatch: " << eur2 << " vs " << eurExpected2);
339
340 Real gbp2Diff = fabs(gbp2 - gbpExpected2) / gbpExpected2;
341 BOOST_CHECK_MESSAGE(gbp2Diff < relTolerance, "GBP 10Y Discount mismatch: " << gbp2 << " vs " << gbpExpected2);
342
343 Real usd2Diff = fabs(usd2 - usdExpected2) / usdExpected2;
344 BOOST_CHECK_MESSAGE(usd2Diff < relTolerance, "USD 10Y Discount mismatch: " << usd2 << " vs " << usdExpected2);
345
346 BOOST_TEST_MESSAGE("CrossAssetModel via ScenarioSimMarket");
347 BOOST_TEST_MESSAGE("EUR " << QuantLib::io::iso_date(d2) << " Discount: " << eur << " vs " << eurExpected
348 << " (" << eurDiff << ")");
349 BOOST_TEST_MESSAGE("GBP " << QuantLib::io::iso_date(d2) << " Discount in EUR: " << gbp << " vs " << gbpExpected
350 << " (" << gbpDiff << ")");
351 BOOST_TEST_MESSAGE("USD " << QuantLib::io::iso_date(d2) << " Discount in EUR: " << usd << " vs " << usdExpected
352 << " (" << usdDiff << ")");
353 BOOST_TEST_MESSAGE("EUR " << QuantLib::io::iso_date(d1) << " Discount: " << eur2 << " vs " << eurExpected2
354 << " (" << eur2Diff << ")");
355 BOOST_TEST_MESSAGE("GBP " << QuantLib::io::iso_date(d1) << " Discount in EUR: " << gbp2 << " vs " << gbpExpected2
356 << " (" << gbp2Diff << ")");
357 BOOST_TEST_MESSAGE("USD " << QuantLib::io::iso_date(d1) << " Discount in EUR: " << usd2 << " vs " << usdExpected2
358 << " (" << usd2Diff << ")");
359 BOOST_TEST_MESSAGE("Simulation time " << timer.format(default_places, "%w") << ", total "
360 << timer2.format(default_places, "%w"));
361}
362
363BOOST_AUTO_TEST_CASE(testLgmExact) { test_measure("LGM", 0.0, "exact"); }
364
365// BOOST_AUTO_TEST_CASE(testLgmEuler) { test_measure("LGM", 0.0, "euler"); }
366
367BOOST_AUTO_TEST_CASE(testFwdExact) { test_measure("LGM", 30.0, "exact"); }
368
369// BOOST_AUTO_TEST_CASE(testFwdEuler) { test_measure("LGM", 30.0, "euler"); }
370
371BOOST_AUTO_TEST_CASE(testBaExact) { test_measure("BA", 0.0, "exact"); }
372
373BOOST_AUTO_TEST_CASE(testBaEuler) { test_measure("BA", 0.0, "euler"); }
374
375BOOST_AUTO_TEST_SUITE_END()
376
377BOOST_AUTO_TEST_SUITE_END()
QuantLib::ext::shared_ptr< ScenarioGenerator > build(QuantLib::ext::shared_ptr< QuantExt::CrossAssetModel > model, QuantLib::ext::shared_ptr< ScenarioFactory > sf, QuantLib::ext::shared_ptr< ScenarioSimMarketParameters > marketConfig, Date asof, QuantLib::ext::shared_ptr< ore::data::Market > initMarket, const std::string &configuration=ore::data::Market::defaultConfiguration, const QuantLib::ext::shared_ptr< PathGeneratorFactory > &pf=QuantLib::ext::make_shared< MultiPathGeneratorFactory >())
Build function.
void addCorrelation(const std::string &factor1, const std::string &factor2, QuantLib::Real correlation)
const std::map< CorrelationKey, QuantLib::Handle< QuantLib::Quote > > & correlations()
OREAnalytics Top level fixture.
Simple flat market setup to be used in the test suite.
Definition: testmarket.hpp:64
Scenario generation using cross asset model paths.
Scenario generation using LGM paths.
Date referenceDate
Sobol
CalibrationType
QuantLib::ext::shared_ptr< data::Conventions > convs()
Fixture that can be used at top level of OREAnalytics test suites.
Build a scenariogenerator.
A Market class that can be updated by Scenarios.
Simple scenario class.
factory classes for simple scenarios
void test_measure(std::string measureName, Real shiftHorizon, std::string discName)
BOOST_AUTO_TEST_CASE(testLgmExact)