Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
gaussiancam.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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// clang-format off
20#include <boost/test/unit_test.hpp>
21#include <boost/test/data/test_case.hpp>
22// clang-format on
23
24#include "oredtestmarket.hpp"
25
33
34#include <oret/toplevelfixture.hpp>
35
39
40#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
41#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
42#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
43
44using namespace ore::data;
45using namespace QuantLib;
46using namespace QuantExt;
47
48BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
49
50BOOST_AUTO_TEST_SUITE(GaussianCamTest)
51
52BOOST_AUTO_TEST_CASE(testRepricingCalibrationInstruments) {
53
54 BOOST_TEST_MESSAGE("test repricing of calibration instruments in Gaussian CAM...");
55
56 constexpr Size paths = 25000;
57
58 Date asof(7, July, 2019);
59 Settings::instance().evaluationDate() = asof;
60 auto testMarket = QuantLib::ext::make_shared<OredTestMarket>(asof);
61
62 // build IR-FX-EQ CAM
63
64 std::vector<Date> calibrationExpiries;
65 std::vector<std::string> calibrationExpiriesStr;
66 std::vector<Real> calibrationTimes;
67 for (Size i = 1; i <= 9; ++i) {
68 Date d = TARGET().advance(asof, i * Years);
69 calibrationExpiries.push_back(d);
70 calibrationExpiriesStr.push_back(ore::data::to_string(asof + i * Years));
71 calibrationTimes.push_back(testMarket->discountCurve("EUR")->timeFromReference(d));
72 }
73 std::vector<Date> calibrationTerms(calibrationExpiriesStr.size(), Date(7, Jul, 2029));
74 std::vector<std::string> calibrationTermsStr(calibrationExpiriesStr.size(), "2029-07-07");
75
76 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
77 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
78 std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
79
80 auto configEUR = QuantLib::ext::make_shared<IrLgmData>();
81 configEUR->qualifier() = "EUR";
82 configEUR->reversionType() = LgmData::ReversionType::HullWhite;
83 configEUR->volatilityType() = LgmData::VolatilityType::Hagan;
84 configEUR->calibrateH() = false;
85 configEUR->hParamType() = ParamType::Constant;
86 configEUR->hTimes() = std::vector<Real>();
87 configEUR->calibrationType() = CalibrationType::Bootstrap;
88 configEUR->scaling() = 1.0;
89 configEUR->shiftHorizon() = 0.0;
90 configEUR->hValues() = {0.0050};
91 configEUR->calibrateA() = true;
92 configEUR->aParamType() = ParamType::Piecewise;
93 configEUR->aTimes() = calibrationTimes;
94 configEUR->aValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030);
95 configEUR->optionExpiries() = calibrationExpiriesStr;
96 configEUR->optionTerms() = calibrationTermsStr;
97 configEUR->optionStrikes() = std::vector<std::string>(calibrationExpiriesStr.size(), "ATM");
98
99 auto configUSD = QuantLib::ext::make_shared<IrLgmData>();
100 configUSD->qualifier() = "USD";
101 configUSD->reversionType() = LgmData::ReversionType::HullWhite;
102 configUSD->volatilityType() = LgmData::VolatilityType::Hagan;
103 configUSD->calibrateH() = false;
104 configUSD->hParamType() = ParamType::Constant;
105 configUSD->hTimes() = std::vector<Real>();
106 configUSD->calibrationType() = CalibrationType::Bootstrap;
107 configUSD->scaling() = 1.0;
108 configUSD->shiftHorizon() = 0.0;
109 configUSD->hValues() = {0.0030};
110 configUSD->calibrateA() = true;
111 configUSD->aParamType() = ParamType::Piecewise;
112 configUSD->aTimes() = calibrationTimes;
113 configUSD->aValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030);
114 configUSD->optionExpiries() = calibrationExpiriesStr;
115 configUSD->optionTerms() = calibrationTermsStr;
116 configUSD->optionStrikes() = std::vector<std::string>(calibrationExpiriesStr.size(), "ATM");
117
118 irConfigs.push_back(configEUR);
119 irConfigs.push_back(configUSD);
120
121 auto configFX = QuantLib::ext::make_shared<FxBsData>();
122 configFX->foreignCcy() = "USD";
123 configFX->domesticCcy() = "EUR";
124 configFX->calibrationType() = CalibrationType::Bootstrap;
125 configFX->calibrateSigma() = true;
126 configFX->sigmaParamType() = ParamType::Piecewise;
127 configFX->sigmaTimes() = calibrationTimes;
128 configFX->sigmaValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030);
129 configFX->optionExpiries() = calibrationExpiriesStr;
130 configFX->optionStrikes() = std::vector<std::string>(calibrationExpiriesStr.size(), "ATMF");
131 fxConfigs.push_back(configFX);
132
133 auto configEQ = QuantLib::ext::make_shared<EqBsData>();
134 configEQ->eqName() = "SP5";
135 configEQ->currency() = "USD";
136 configEQ->calibrationType() = CalibrationType::Bootstrap;
137 configEQ->calibrateSigma() = true;
138 configEQ->sigmaParamType() = ParamType::Piecewise;
139 configEQ->sigmaTimes() = calibrationTimes;
140 configEQ->sigmaValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030);
141 configEQ->optionExpiries() = calibrationExpiriesStr;
142 configEQ->optionStrikes() = std::vector<std::string>(calibrationExpiriesStr.size(), "ATMF");
143 eqConfigs.push_back(configEQ);
144
146 cmb.addCorrelation("IR:EUR", "IR:USD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.6)));
147 cmb.addCorrelation("IR:EUR", "FX:EURUSD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.2)));
148 cmb.addCorrelation("IR:EUR", "EQ:SP5", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.2)));
149 cmb.addCorrelation("IR:USD", "FX:EURUSD", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)));
150 cmb.addCorrelation("IR:USD", "EQ:SP5", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.5)));
151 cmb.addCorrelation("FX:EURUSD", "EQ:SP5", Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.4)));
152
153 auto camBuilder = QuantLib::ext::make_shared<CrossAssetModelBuilder>(
154 testMarket, QuantLib::ext::make_shared<CrossAssetModelData>(irConfigs, fxConfigs, eqConfigs, cmb.correlations()));
155 auto model = camBuilder->model();
156
157 // set up gaussian cam adapter with simulation dates = calibration expiries
158
159 std::vector<std::string> modelCcys = {"EUR", "USD"};
160 std::vector<Handle<YieldTermStructure>> modelCurves = {testMarket->discountCurve("EUR"),
161 testMarket->discountCurve("USD")};
162 std::vector<Handle<Quote>> modelFxSpots = {testMarket->fxRate("USDEUR")};
163 std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<InterestRateIndex>>> irIndices = {
164 std::make_pair("EUR-EURIBOR-6M", *testMarket->iborIndex("EUR-EURIBOR-6M"))};
165 std::vector<std::string> indices = {"FX-GENERIC-USD-EUR", "EQ-SP5"};
166 std::vector<std::string> indexCurrencies = {"USD", "USD"};
167 auto gaussianCam = QuantLib::ext::make_shared<GaussianCam>(
168 model, paths, modelCcys, modelCurves, modelFxSpots, irIndices,
169 std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<ZeroInflationIndex>>>(), indices, indexCurrencies,
170 std::set<Date>(calibrationExpiries.begin(), calibrationExpiries.end()), Model::McParams());
171
172 // generate MC prices for the calibration instruments and compare them with analytical prices
173
174 // FX instruments
175
176 auto context = QuantLib::ext::make_shared<Context>();
177 context->scalars["Option"] = RandomVariable(paths, 0.0);
178 context->scalars["Underlying"] = IndexVec{paths, "FX-GENERIC-USD-EUR"};
179 context->scalars["PayCcy"] = CurrencyVec{paths, "EUR"};
180 context->scalars["PutCall"] = RandomVariable(paths, 1.0);
181 std::string optionScript =
182 "Option = PAY( max( PutCall * (Underlying(Expiry)-Strike), 0), Expiry, Expiry, PayCcy );";
183 auto optionAst = ScriptParser(optionScript).ast();
184 BOOST_REQUIRE(optionAst);
185 ScriptEngine optionEngine(optionAst, context, gaussianCam);
186
187 for (Size i = 0; i < calibrationExpiries.size(); ++i) {
188 Real atmf = testMarket->fxRate("USDEUR")->value() /
189 testMarket->discountCurve("EUR")->discount(calibrationExpiries[i]) *
190 testMarket->discountCurve("USD")->discount(calibrationExpiries[i]);
191 // compute the script price
192 context->scalars["Expiry"] = EventVec{paths, calibrationExpiries[i]};
193 context->scalars["Strike"] = RandomVariable(paths, atmf);
194 optionEngine.run();
195 Real scriptPrice = expectation(QuantLib::ext::get<RandomVariable>(context->scalars["Option"])).at(0);
196 // compute the analytical price
197 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
198 testMarket->fxRate("USDEUR"), testMarket->discountCurve("USD"), testMarket->discountCurve("EUR"),
199 testMarket->fxVol("USDEUR"));
200 auto engine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(process);
201 VanillaOption option(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, atmf),
202 QuantLib::ext::make_shared<EuropeanExercise>(calibrationExpiries[i]));
203 option.setPricingEngine(engine);
204 Real analyticalPrice = option.NPV();
205 // compare script and analytical price
206 BOOST_TEST_MESSAGE("FX Option expiry " << calibrationExpiries[i] << " analyticalPrice= " << analyticalPrice
207 << " scriptPrice=" << scriptPrice << " relative error "
208 << (scriptPrice - analyticalPrice) / analyticalPrice);
209 BOOST_CHECK_CLOSE(analyticalPrice, scriptPrice, 0.5);
210 }
211
212 // EQ instruments
213
214 context->scalars["Underlying"] = IndexVec{paths, "EQ-SP5"};
215 context->scalars["PayCcy"] = CurrencyVec{paths, "USD"};
216
217 for (Size i = 0; i < calibrationExpiries.size(); ++i) {
218 Real atmf = testMarket->equitySpot("SP5")->value() /
219 testMarket->equityForecastCurve("SP5")->discount(calibrationExpiries[i]) *
220 testMarket->equityDividendCurve("SP5")->discount(calibrationExpiries[i]);
221 // compute the script price
222 context->scalars["Expiry"] = EventVec{paths, calibrationExpiries[i]};
223 context->scalars["Strike"] = RandomVariable(paths, atmf);
224 optionEngine.run();
225 Real scriptPrice = expectation(QuantLib::ext::get<RandomVariable>(context->scalars["Option"])).at(0);
226 // compute the analytical price
227 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
228 testMarket->equitySpot("SP5"), testMarket->equityDividendCurve("SP5"),
229 testMarket->equityForecastCurve("SP5"), testMarket->equityVol("SP5"));
230 auto engine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(process, testMarket->discountCurve("USD"));
231 VanillaOption option(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, atmf),
232 QuantLib::ext::make_shared<EuropeanExercise>(calibrationExpiries[i]));
233 option.setPricingEngine(engine);
234 Real analyticalPrice = option.NPV() * testMarket->fxRate("USDEUR")->value();
235 // compare script and analytical price
236 BOOST_TEST_MESSAGE("Equity Option expiry " << calibrationExpiries[i] << " analyticalPrice= " << analyticalPrice
237 << " scriptPrice=" << scriptPrice << " relative error "
238 << (scriptPrice - analyticalPrice) / analyticalPrice);
239 BOOST_CHECK_CLOSE(analyticalPrice, scriptPrice, 1.0); // eq has higher market vol compared to fx
240 }
241
242 // IR instruments
243
244 context = QuantLib::ext::make_shared<Context>();
245 context->scalars["Option"] = RandomVariable(paths, 0.0);
246 context->scalars["FloatIndex"] = IndexVec{paths, "EUR-EURIBOR-6M"};
247 context->scalars["PayCurrency"] = CurrencyVec{paths, "EUR"};
248 context->scalars["FixedRatePayer"] = RandomVariable(paths, -1.0);
249 context->scalars["Notional"] = RandomVariable(paths, 1.0);
250 context->scalars["FloatSpread"] = RandomVariable(paths, 0.0);
251 context->scalars["FixedDayCounter"] = DaycounterVec{paths, "30/360"};
252 context->scalars["FloatDayCounter"] = DaycounterVec{paths, "A360"};
253 std::string swaptionScript =
254 "NUMBER UnderlyingNpv;\n"
255 "NUMBER i, j;\n"
256 "FOR j IN (2, SIZE(FixedLegSchedule), 1) DO\n"
257 " UnderlyingNpv = UnderlyingNpv + PAY( Notional * FixedRate * dcf( FixedDayCounter, \n"
258 " FixedLegSchedule[j-1], FixedLegSchedule[j] ),\n"
259 " OptionExpiry, FixedLegSchedule[j], PayCurrency );\n"
260 "END;\n"
261 "FOR j IN (2, SIZE(FloatLegSchedule), 1) DO\n"
262 " UnderlyingNpv = UnderlyingNpv - PAY( Notional * (FloatIndex(OptionExpiry, FixingSchedule[j-1]) + \n"
263 " FloatSpread) * dcf( FloatDayCounter, FloatLegSchedule[j-1], FloatLegSchedule[j] ),\n"
264 " OptionExpiry, FloatLegSchedule[j], PayCurrency );\n"
265 "END;\n"
266 "Option = max( FixedRatePayer * UnderlyingNpv, 0);\n";
267 auto swaptionAst = ScriptParser(swaptionScript).ast();
268 BOOST_REQUIRE(swaptionAst);
269
270 auto swvol = testMarket->swaptionVol("EUR");
271 auto swapIndex = testMarket->swapIndex(testMarket->swapIndexBase("EUR"));
272 auto iborIndex = swapIndex->iborIndex();
273 auto fixedLegTenor = swapIndex->fixedLegTenor();
274 auto fixedDayCounter = swapIndex->dayCounter();
275 auto floatDayCounter = swapIndex->iborIndex()->dayCounter();
276
277 for (Size i = 0; i < calibrationExpiries.size(); ++i) {
278 Real optionTime = swvol->timeFromReference(calibrationExpiries[i]);
279 Real swapLength = swvol->swapLength(calibrationExpiries[i], calibrationTerms[i]);
280 // dummy strike (we don't have a smile in the market)
281 Handle<Quote> vol(QuantLib::ext::make_shared<SimpleQuote>(swvol->volatility(optionTime, swapLength, 0.01)));
282 // atm swaption helper
283 auto helper = QuantLib::ext::make_shared<SwaptionHelper>(
284 calibrationExpiries[i], Date(7, July, 2029), vol, iborIndex, fixedLegTenor, fixedDayCounter,
285 floatDayCounter, testMarket->discountCurve("EUR"), BlackCalibrationHelper::RelativePriceError, Null<Real>(),
286 1.0, swvol->volatilityType(), swvol->shift(optionTime, swapLength));
287 Real atmStrike = helper->underlyingSwap()->fairRate();
288 // script price
289 auto workingContext = QuantLib::ext::make_shared<Context>(*context);
290 std::vector<ValueType> fixedSchedule, floatSchedule, fixingSchedule;
291 for (auto const& d : helper->underlyingSwap()->fixedSchedule().dates())
292 fixedSchedule.push_back(EventVec{paths, d});
293 for (auto const& d : helper->underlyingSwap()->floatingSchedule().dates()) {
294 floatSchedule.push_back(EventVec{paths, d});
295 fixingSchedule.push_back(EventVec{paths, iborIndex->fixingDate(d)});
296 }
297 workingContext->scalars["OptionExpiry"] = EventVec{paths, calibrationExpiries[i]};
298 workingContext->arrays["FixedLegSchedule"] = fixedSchedule;
299 workingContext->arrays["FloatLegSchedule"] = floatSchedule;
300 workingContext->arrays["FixingSchedule"] = fixingSchedule;
301 workingContext->scalars["FixedRate"] = RandomVariable(paths, atmStrike);
302 ScriptEngine swaptionEngine(swaptionAst, workingContext, gaussianCam);
303 swaptionEngine.run(swaptionScript);
304 Real scriptPrice = expectation(QuantLib::ext::get<RandomVariable>(workingContext->scalars["Option"])).at(0);
305 // analytical price
306 Real analyticalPrice = helper->marketValue();
307 // compare script and analytical price
308 BOOST_TEST_MESSAGE("Swaption Option expiry "
309 << calibrationExpiries[i] << " analyticalPrice= " << analyticalPrice << " scriptPrice="
310 << scriptPrice << " relative error " << (scriptPrice - analyticalPrice) / analyticalPrice);
311 BOOST_CHECK_CLOSE(analyticalPrice, scriptPrice, 1.0);
312 }
313}
314
315BOOST_AUTO_TEST_SUITE_END()
316
317BOOST_AUTO_TEST_SUITE_END()
ast printer
black scholes model for n underlyings (fx, equity or commodity)
void addCorrelation(const std::string &factor1, const std::string &factor2, QuantLib::Real correlation)
const std::map< CorrelationKey, QuantLib::Handle< QuantLib::Quote > > & correlations()
Get the raw correlation data.
void run(const std::string &script="", bool interactive=false, QuantLib::ext::shared_ptr< PayLog > paylog=nullptr, bool includePastCashflows=false)
ASTNodePtr ast() const
Build a cross asset model.
dummy model implementation
gaussian cross asset model for ir, fx, eq, com
IR component data for the cross asset model.
RandomVariable expectation(const RandomVariable &r)
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
scriptengine
script parser
static script analyser
Real at(const Size i) const
std::string value
Definition: value.hpp:47
BOOST_AUTO_TEST_CASE(testRepricingCalibrationInstruments)
Definition: gaussiancam.cpp:52
string conversion utilities