52 {
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
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);
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
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
173
174
175
176 auto context = QuantLib::ext::make_shared<Context>();
178 context->scalars[
"Underlying"] =
IndexVec{paths,
"FX-GENERIC-USD-EUR"};
179 context->scalars[
"PayCcy"] =
CurrencyVec{paths,
"EUR"};
181 std::string optionScript =
182 "Option = PAY( max( PutCall * (Underlying(Expiry)-Strike), 0), Expiry, Expiry, PayCcy );";
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
192 context->scalars[
"Expiry"] =
EventVec{paths, calibrationExpiries[i]};
194 optionEngine.run();
195 Real scriptPrice =
expectation(QuantLib::ext::get<RandomVariable>(context->scalars[
"Option"])).
at(0);
196
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
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
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
222 context->scalars[
"Expiry"] =
EventVec{paths, calibrationExpiries[i]};
224 optionEngine.run();
225 Real scriptPrice =
expectation(QuantLib::ext::get<RandomVariable>(context->scalars[
"Option"])).
at(0);
226
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
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);
240 }
241
242
243
244 context = QuantLib::ext::make_shared<Context>();
246 context->scalars[
"FloatIndex"] =
IndexVec{paths,
"EUR-EURIBOR-6M"};
247 context->scalars[
"PayCurrency"] =
CurrencyVec{paths,
"EUR"};
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";
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
281 Handle<Quote> vol(QuantLib::ext::make_shared<SimpleQuote>(swvol->volatility(optionTime, swapLength, 0.01)));
282
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
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
306 Real analyticalPrice =
helper->marketValue();
307
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}
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.
RandomVariable expectation(const RandomVariable &r)
std::string to_string(const LocationInfo &l)
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
Real at(const Size i) const