Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
scriptedtrade.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
33
46
53
54#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
55#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
56
57#include <boost/lexical_cast.hpp>
58
59namespace ore {
60namespace data {
61
62using namespace QuantExt;
63
64QuantLib::Handle<QuantExt::CorrelationTermStructure>
65ScriptedTradeEngineBuilder::correlationCurve(const std::string& index1, const std::string& index2) {
66 if (index1 == index2) {
67 // need to handle this case here, we might have calls with index1 == index arising from COMM
68 // indices with different spot / future reference, for which we expect the correlation on
69 // the name level (i.e. for the spot index)
70 return Handle<QuantExt::CorrelationTermStructure>(
71 QuantLib::ext::make_shared<FlatCorrelation>(0, NullCalendar(), 1.0, ActualActual(ActualActual::ISDA)));
72 } else {
73 return market_->correlationCurve(index1, index2, configuration(MarketContext::pricing));
74 }
75}
76
77QuantLib::ext::shared_ptr<ScriptedInstrument::engine>
78ScriptedTradeEngineBuilder::engine(const std::string& id, const ScriptedTrade& scriptedTrade,
79 const QuantLib::ext::shared_ptr<ReferenceDataManager>& referenceData,
80 const IborFallbackConfig& iborFallbackConfig) {
81
82 const std::vector<ScriptedTradeEventData>& events = scriptedTrade.events();
83 const std::vector<ScriptedTradeValueTypeData>& numbers = scriptedTrade.numbers();
84 const std::vector<ScriptedTradeValueTypeData>& indices = scriptedTrade.indices();
85 const std::vector<ScriptedTradeValueTypeData>& currencies = scriptedTrade.currencies();
86 const std::vector<ScriptedTradeValueTypeData>& daycounters = scriptedTrade.daycounters();
87
88 LOG("Building engine for scripted trade " << id);
89
90 // 0 clear members
91
92 clear();
93
94 // 1 set the SIMM product class, simple EQ > COM > FX approach for Hybrids, that's all we can do automatically?
95 // also set the assetClassReplacement string which is used to replace {AssetClass} in product tags
96
97 deriveProductClass(indices);
98
99 // 1b get product tag from scripted trade or library and build resolved product tag
100
101 std::string productTag = getScript(scriptedTrade, ScriptLibraryStorage::instance().get(), "", false).first;
102 resolvedProductTag_ = boost::replace_all_copy(productTag, "{AssetClass}", assetClassReplacement_);
103 DLOG("got product tag '" << productTag << "', resolved product tag is '" << resolvedProductTag_);
104
105 // 2 populate model and engine parameters
106
108
109 // 3 define purpose, get suitable script and build ast (i.e. parse it or retrieve it from cache)
110
111 std::string purpose = "";
112 if (buildingAmc_)
113 purpose = "AMC";
114 else if (engineParam_ == "FD")
115 purpose = "FD";
116
118 getScript(scriptedTrade, ScriptLibraryStorage::instance().get(), purpose, true).second;
119
120 auto f = astCache_.find(script.code());
121
122 if (f != astCache_.end()) {
123 ast_ = f->second;
124 DLOG("retrieved ast from cache");
125 } else {
126 ast_ = parseScript(script.code());
127 astCache_[script.code()] = ast_;
128 DLOGGERSTREAM("built ast:\n" << to_string(ast_));
129 }
130
131 // 4 set up context
132
133 auto context = makeContext(modelSize_, gridCoarsening_, script.schedulesEligibleForCoarsening(), referenceData,
134 events, numbers, indices, currencies, daycounters);
135 addAmcGridToContext(context);
136 addNewSchedulesToContext(context, script.newSchedules());
137
138 DLOG("Built initial context:");
139 DLOGGERSTREAM(*context);
140
141 // 4b set up calibration strike information
142
143 if (!buildingAmc_)
145
146 // 5 run static analyser
147
148 DLOG("Run static analyser on script");
149 staticAnalyser_ = QuantLib::ext::make_shared<StaticAnalyser>(ast_, context);
150 staticAnalyser_->run(script.code());
151
152 // 6 extract eq, fx, ir indices from script
153
154 extractIndices(referenceData);
155
156 // 7 populate fixings map
157
158 populateFixingsMap(iborFallbackConfig);
159
160 // 8 init result variable for NPV
161
162 checkDuplicateName(context, script.npv());
163 context->scalars[script.npv()] = RandomVariable(modelSize_, 0.0);
164
165 // 9 extract pay currencies
166
168
169 // 10 determine base ccy, this might be overwritten in 11 when an AMC engine is built, see the implementation of 11
170
172
173 // 11 compile the model currency list (depends on the model actually)
174
176
177 // 12 get the t0 curves for each model ccy
178
179 std::string externalDiscountCurve = scriptedTrade.envelope().additionalField("discount_curve", false);
180 std::string externalSecuritySpread = scriptedTrade.envelope().additionalField("security_spread", false);
181 for (auto const& c : modelCcys_) {
182 // for base ccy we account for an external discount curve and security spread if given
183 Handle<YieldTermStructure> yts =
184 externalDiscountCurve.empty() || c != baseCcy_
185 ? market_->discountCurve(c, configuration(MarketContext::pricing))
187 if (!externalSecuritySpread.empty() && c == baseCcy_)
188 yts = Handle<YieldTermStructure>(QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(
189 yts, market_->securitySpread(externalSecuritySpread, configuration(MarketContext::pricing))));
190 modelCurves_.push_back(yts);
191 DLOG("curve for " << c << " added.");
192 }
193
194 // 13 get the fx spots for each model ccy vs. the base ccy
195
196 for (Size i = 1; i < modelCcys_.size(); ++i) {
198 DLOG("fx spot " << modelCcys_[i] + baseCcy_ << " added.");
199 }
200
201 // 14 compile the model index (eq, fx, comm) and ir index lists
202
204
205 // 15 determine last relevant date as max over index eval, regression, pay obs/pay dates
206
208
209 // 16 set up correlations between model indices (ir, eq, fx, comm)
210
212
213 // 17 compile the processes (needed for BlackScholes, LocalVol only)
214
215 if (modelParam_ == "BlackScholes" || modelParam_ == "LocalVolDupire" || modelParam_ == "LocalVolAndreasenHuge")
217
218 // 18 setup IR reversion values (needed for Gaussian CAM only)
219
220 if (modelParam_ == "GaussianCam") {
222 }
223
224 // 19 compile the sim and add dates required by the model ctors below
225
227
228 // 20 build the model adapter
229
230 QL_REQUIRE(!buildingAmc_ || modelParam_ == "GaussianCam",
231 "model/engine = GaussianCam/MC required to build an amc model, got " << modelParam_ << "/"
232 << engineParam_);
233
234 if(staticAnalyser_->regressionDates().empty())
235 mcParams_.trainingSamples = Null<Size>();
236
237 if (modelParam_ == "BlackScholes" && engineParam_ == "MC") {
238 buildBlackScholes(id, iborFallbackConfig);
239 } else if (modelParam_ == "BlackScholes" && engineParam_ == "FD") {
240 buildFdBlackScholes(id, iborFallbackConfig);
241 } else if ((modelParam_ == "LocalVolDupire" || modelParam_ == "LocalVolAndreasenHuge") && engineParam_ == "MC") {
242 buildLocalVol(id, iborFallbackConfig);
243 } else if (modelParam_ == "GaussianCam" && engineParam_ == "MC") {
244 if (amcCam_) {
245 buildGaussianCamAMC(id, iborFallbackConfig, script.conditionalExpectationModelStates());
246 } else if (amcCgModel_) {
247 buildAMCCGModel(id, iborFallbackConfig, script.conditionalExpectationModelStates());
248 } else {
249 buildGaussianCam(id, iborFallbackConfig, script.conditionalExpectationModelStates());
250 }
251 } else if (modelParam_ == "GaussianCam" && engineParam_ == "FD") {
252 buildFdGaussianCam(id, iborFallbackConfig);
253 } else {
254 QL_FAIL("model '" << modelParam_ << "' / engine '" << engineParam_
255 << "' not recognised, expected BlackScholes/[MC|FD], LocalVolDupire/MC, "
256 "LocalVolAndreasenHuge/MC, GaussianCam/MC");
257 }
258
259 QL_REQUIRE(model_ != nullptr || modelCG_ != nullptr, "internal error: both model_ and modelCG_ are null");
260
261 // 21 log some summary information
262
263 DLOG("built model : " << modelParam_ << " / " << engineParam_);
264 DLOG("useCg = " << std::boolalpha << useCg_);
265 DLOG("useAd = " << std::boolalpha << useAd_);
266 DLOG("useExternalDevice = " << std::boolalpha << useExternalComputeDevice_);
267 DLOG("useDblPrecExtCalc = " << std::boolalpha << useDoublePrecisionForExternalCalculation_);
268 DLOG("extDeviceCompatMode = " << std::boolalpha << externalDeviceCompatibilityMode_);
269 DLOG("externalDevice = " << (useExternalComputeDevice_ ? externalComputeDevice_ : "na"));
270 DLOG("calibration = " << calibration_);
271 DLOG("base ccy = " << baseCcy_);
272 DLOG("model base (npv) ccy = " << (model_ != nullptr ? model_->baseCcy() : modelCG_->baseCcy()));
273 DLOG("ccys = " << modelCcys_.size());
274 DLOG("eq,fx,com indices = " << modelIndices_.size());
275 DLOG("ir indices = " << irIndices_.size());
276 DLOG("inf indices = " << infIndices_.size());
277 DLOG("sim dates = " << simulationDates_.size());
278 DLOG("add dates = " << addDates_.size());
279 DLOG("timeStepsPerYear = " << timeStepsPerYear_);
280 DLOG("fullDynamicFx = " << std::boolalpha << fullDynamicFx_);
281 if (engineParam_ == "MC") {
282 DLOG("seed = " << mcParams_.seed);
283 DLOG("paths = " << modelSize_);
284 DLOG("regressionOrder = " << mcParams_.regressionOrder);
285 DLOG("sequence type = " << mcParams_.sequenceType);
286 DLOG("polynom type = " << mcParams_.polynomType);
287 if (mcParams_.trainingSamples != Null<Size>()) {
288 DLOG("training seed = " << mcParams_.trainingSeed);
289 DLOG("training paths = " << mcParams_.trainingSamples);
290 DLOG("training seq. type = " << mcParams_.trainingSequenceType);
291 }
292 DLOG("sobol bb ordering = " << mcParams_.sobolOrdering);
293 DLOG("sobol direction int. = " << mcParams_.sobolDirectionIntegers);
294 } else if (engineParam_ == "FD") {
295 DLOG("stateGridPoints = " << modelSize_);
296 DLOG("mesherEpsilon = " << mesherEpsilon_);
297 DLOG("mesherScaling = " << mesherScaling_);
298 DLOG("mesherConcentration = " << mesherConcentration_);
299 DLOG("mesherMaxConcentrPts = " << mesherMaxConcentratingPoints_);
300 DLOG("mesherIsStatic = " << std::boolalpha << mesherIsStatic_);
301 }
302 if (modelParam_ == "GaussianCam") {
303 DLOG("fullDynamicIr = " << std::boolalpha << fullDynamicIr_);
304 DLOG("ref calibration grid = " << referenceCalibrationGrid_);
305 DLOG("bootstrap tolerance = " << bootstrapTolerance_);
306 DLOG("infModelType = " << infModelType_);
307 DLOG("condExpMdlStates = " << boost::algorithm::join(script.conditionalExpectationModelStates(), ","));
308 } else if (modelParam_ == "LocalVolAndreasenHuge") {
309 DLOG("moneyness points = " << calibrationMoneyness_.size());
310 }
311
312 // 22 build the pricing engine and return it
313
314 bool generateAdditionalResults = false;
315 auto p = globalParameters_.find("GenerateAdditionalResults");
316 if (p != globalParameters_.end()) {
317 generateAdditionalResults = parseBool(p->second);
318 }
319
320 QuantLib::ext::shared_ptr<ScriptedInstrument::engine> engine;
321 if (model_) {
322 engine = QuantLib::ext::make_shared<ScriptedInstrumentPricingEngine>(
323 script.npv(), script.results(), model_, ast_, context, script.code(), interactive_, amcCam_ != nullptr,
324 std::set<std::string>(script.stickyCloseOutStates().begin(), script.stickyCloseOutStates().end()),
325 generateAdditionalResults, includePastCashflows_);
326 } else if (modelCG_) {
327 auto rt = globalParameters_.find("RunType");
328 std::string runType = rt != globalParameters_.end() ? rt->second : "<<no run type set>>";
329 bool useCachedSensis = useAd_ && (runType == "SensitivityDelta");
330 bool useExternalDev = useExternalComputeDevice_ && !generateAdditionalResults && !useCachedSensis;
331 if (useAd_ && !useCachedSensis) {
332 WLOG("Will not apply AD although useAD is configured, because runType ("
333 << runType << ") does not match SensitivitiyDelta");
334 }
335 if (useExternalComputeDevice_ && !useExternalDev) {
336 WLOG("Will not use exxternal compute deivce although useExternalComputeDevice is configured, because we "
337 "are either applying AD ("
338 << std::boolalpha << useCachedSensis << ") or we are generating add results ("
339 << generateAdditionalResults << "), both of which do not support external devices at the moment.");
340 }
341 engine = QuantLib::ext::make_shared<ScriptedInstrumentPricingEngineCG>(
342 script.npv(), script.results(), modelCG_, ast_, context, mcParams_, script.code(), interactive_,
343 generateAdditionalResults, includePastCashflows_, useCachedSensis, useExternalDev,
345 if (useExternalDev) {
346 ComputeEnvironment::instance().selectContext(externalComputeDevice_);
347 }
348 }
349
350 LOG("engine built for model " << modelParam_ << " / " << engineParam_ << ", modelSize = " << modelSize_
351 << ", interactive = " << interactive_ << ", amcEnabled = " << buildingAmc_
352 << ", generateAdditionalResults = " << generateAdditionalResults);
353 return engine;
354}
355
357 fixings_.clear();
358 eqIndices_.clear();
359 commIndices_.clear();
360 irIndices_.clear();
361 infIndices_.clear();
362 fxIndices_.clear();
363 payCcys_.clear();
364 modelCcys_.clear();
365 modelCurves_.clear();
366 modelFxSpots_.clear();
367 modelIndices_.clear();
369 modelIrIndices_.clear();
370 modelInfIndices_.clear();
371 correlations_.clear();
372 processes_.clear();
373 irReversions_.clear();
374 simulationDates_.clear();
375 addDates_.clear();
376 calibrationStrikes_.clear();
377}
378
380 const QuantLib::ext::shared_ptr<ore::data::ReferenceDataManager>& referenceData) {
381 DLOG("Extract indices from script:");
382 for (auto const& i : staticAnalyser_->indexEvalDates()) {
383 IndexInfo ind(i.first);
384 if (ind.isEq()) {
385 eqIndices_.insert(ind);
386 } else if (ind.isIr()) {
387 irIndices_.insert(ind);
388 } else if (ind.isInf()) {
389 infIndices_.insert(ind);
390 } else if (ind.isFx()) {
391 // ignore trivial fx indices
392 if (ind.fx()->sourceCurrency() != ind.fx()->targetCurrency())
393 fxIndices_.insert(ind);
394 } else if (ind.isComm()) {
395 commIndices_.insert(ind);
396 } else if (ind.isGeneric()) {
397 // ignore generic indices, only historical fixings can be retrieved from them
398 } else {
399 QL_FAIL("unexpected index type for '" << ind.name() << "'");
400 }
401 DLOG("got " << ind);
402 }
403 for (auto const& i : staticAnalyser_->fwdCompAvgFixingDates()) {
404 IndexInfo ind(i.first);
405 QL_REQUIRE(ind.isIr(), "expected IR (ON) index for " << ind.name());
406 irIndices_.insert(ind);
407 DLOG("got " << ind);
408 }
409}
410
411void ScriptedTradeEngineBuilder::deriveProductClass(const std::vector<ScriptedTradeValueTypeData>& indices) {
412 std::set<std::string> names;
413 std::set<IndexInfo> commIndices, eqIndices, fxIndices, irIndices, infIndices;
414
415 for (auto const& i : indices) {
416 if (i.isArray()) {
417 names.insert(i.values().begin(), i.values().end());
418 } else {
419 names.insert(i.value());
420 }
421 }
422
423 for (auto const& n : names) {
424 IndexInfo ind(n);
425 if (ind.isFx())
426 fxIndices.insert(ind);
427 else if (ind.isEq())
428 eqIndices.insert(ind);
429 else if (ind.isComm())
430 commIndices.insert(ind);
431 else if (ind.isIr())
432 irIndices.insert(ind);
433 else if (ind.isInf())
434 infIndices.insert(ind);
435 }
436
438 if (!commIndices.empty()) {
439 simmProductClass_ = "Commodity";
440 scheduleProductClass_ = "Commodity";
441 assetClassReplacement_ = "COMM";
442 } else if (!eqIndices.empty()) {
443 simmProductClass_ = "Equity";
444 scheduleProductClass_ = "Equity";
446 } else if (!fxIndices.empty()) {
447 simmProductClass_ = "RatesFX";
450 for (auto const& i : fxIndices) {
451 std::string f = i.fx()->sourceCurrency().code();
452 std::string d = i.fx()->targetCurrency().code();
453 if (isPseudoCurrency(f) || isPseudoCurrency(d)) {
454 simmProductClass_ = "Commodity";
455 scheduleProductClass_ = "Commodity";
456 // in terms of the asset class replacement we stick with FX for precious metals
457 // assetClassReplacement_ = "COMM";
458 }
459 }
460 } else if (!irIndices.empty() || !infIndices.empty()) {
461 simmProductClass_ = "RatesFX";
462 scheduleProductClass_ = "Rates";
463 } else {
464 simmProductClass_ = "RatesFx"; // fallback if we do not have any indices (an edge case really...)
465 scheduleProductClass_ = "Rates"; // fallback if we do not have any indices (an edge case really...)
466 }
467
468 if ((int)!eqIndices.empty() + (int)!fxIndices.empty() + (int)!commIndices.empty() > 1) {
469 WLOG("SIMM product class for hybrid trade is set to " << simmProductClass_);
470 WLOG("IM Schedule product class for hybrid trade is set to " << scheduleProductClass_);
471 assetClassReplacement_ = "HYBRID";
472 } else {
473 LOG("SIMM product class is set to " << simmProductClass_);
474 LOG("IM Schedule product class is set to " << scheduleProductClass_);
475 }
476}
477
479 DLOG("Retrieve model and engine parameters using product tag '" << resolvedProductTag_ << "'");
480
481 // mandatory parameters
482
488
492
493 // optional parameters
494
495 zeroVolatility_ = parseBool(engineParameter("ZeroVolatility", {resolvedProductTag_}, false, "false"));
496 calibration_ = modelParameter("Calibration", {resolvedProductTag_}, false, "Deal");
497 useCg_ = parseBool(engineParameter("UseCG", {resolvedProductTag_}, false, "false"));
498 useAd_ = parseBool(engineParameter("UseAD", {resolvedProductTag_}, false, "false"));
500 parseBool(engineParameter("UseExternalComputeDevice", {resolvedProductTag_}, false, "false"));
502 parseBool(engineParameter("UseDoublePrecisionForExternalCalculation", {resolvedProductTag_}, false, "false"));
503 externalComputeDevice_ = engineParameter("ExternalComputeDevice", {}, false, "");
504 externalDeviceCompatibilityMode_ = parseBool(engineParameter("ExternalDeviceCompatibilityMode", {}, false, "false"));
505 includePastCashflows_ = parseBool(engineParameter("IncludePastCashflows", {resolvedProductTag_}, false, "false"));
506
507 // usage of ad or an external device implies usage of cg
509 useCg_ = true;
510
511 // default values for parameters that are only read for specific models
512
513 fullDynamicIr_ = false;
516 infModelType_ = "DK";
517 mesherEpsilon_ = 1.0E-4;
518 mesherScaling_ = 1.5;
521 mesherIsStatic_ = false;
522
523 // parameters only needed for certain model / engine pairs
524
525 DLOG("Retrieve model / engine specific parameters for " << modelParam_ << " / " << engineParam_);
526
527 if (modelParam_ == "GaussianCam") {
529 referenceCalibrationGrid_ = modelParameter("ReferenceCalibrationGrid", {resolvedProductTag_}, false, "");
531 infModelType_ = modelParameter("InfModelType", {resolvedProductTag_}, false, "DK");
532 } else if (modelParam_ == "LocalVolAndreasenHuge") {
534 parseListOfValues<Real>(engineParameter("CalibrationMoneyness", {resolvedProductTag_}), &parseReal);
535 }
536
537 if (engineParam_ == "MC") {
542 parseSequenceType(engineParameter("SequenceType", {resolvedProductTag_}, false, "SobolBrownianBridge"));
544 parsePolynomType(engineParameter("PolynomType", {resolvedProductTag_}, false, "Monomial"));
546 parseSequenceType(engineParameter("TrainingSequenceType", {resolvedProductTag_}, false, "MersenneTwister"));
548 engineParameter("SobolOrdering", {resolvedProductTag_}, false, "Steps"));
550 engineParameter("SobolDirectionIntegers", {resolvedProductTag_}, false, "JoeKuoD7"));
551 if (auto tmp = engineParameter("TrainingSamples", {resolvedProductTag_}, false, ""); !tmp.empty()) {
553 mcParams_.trainingSeed = parseInteger(engineParameter("TrainingSeed", {resolvedProductTag_}, false, "43"));
554 } else {
555 mcParams_.trainingSamples = Null<Size>();
556 }
558 parseRealOrNull(engineParameter("RegressionVarianceCutoff", {resolvedProductTag_}, false, std::string()));
560 } else if (engineParam_ == "FD") {
562 mesherEpsilon_ = parseReal(engineParameter("MesherEpsilon", {resolvedProductTag_}, false, "1.0E-4"));
563 mesherScaling_ = parseReal(engineParameter("MesherScaling", {resolvedProductTag_}, false, "1.5"));
564 mesherConcentration_ = parseReal(engineParameter("MesherConcentration", {resolvedProductTag_}, false, "0.1"));
566 parseInteger(engineParameter("MesherMaxConcentratingPoints", {resolvedProductTag_}, false, "9999"));
567 mesherIsStatic_ = parseBool(engineParameter("MesherIsStatic", {resolvedProductTag_}, false, "false"));
568 }
569
570 // global parameters that are relevant
571
572 calibrate_ = globalParameters_.count("Calibrate") == 0 || parseBool(globalParameters_.at("Calibrate"));
573
574 if (!calibrate_) {
575 DLOG("model calibration is disalbed in global pricing engine parameters");
576 }
577
578 continueOnCalibrationError_ = globalParameters_.count("ContinueOnCalibrationError") > 0 &&
579 parseBool(globalParameters_.at("ContinueOnCalibrationError"));
580
581 // sensitivity template
582
583 sensitivityTemplate_ = engineParameter("SensitivityTemplate", {resolvedProductTag_}, false, std::string());
584}
585
587 DLOG("Populate fixing map");
588
589 // this might be a superset of the actually required fixings, since index evaluations with fwd date are also
590 // returned, in which case only future estimations are allowed
591
592 std::map<std::string, std::set<std::pair<Date, bool>>> indexFixings;
593
594 for (auto const& [name, fixings] : staticAnalyser_->probFixingDates()) {
595 for (auto const& d : fixings) {
596 indexFixings[name].insert(std::make_pair(d, true));
597 }
598 }
599
600 for (auto const& [name, fixings] : staticAnalyser_->indexEvalDates()) {
601 for (auto const& d : fixings) {
602 indexFixings[name].insert(std::make_pair(d, false));
603 }
604 }
605
606 for (auto const& [name, fixings] : indexFixings) {
607 IndexInfo i(name);
608 if (i.isComm()) {
609 // COMM indices require a special treatment, since they might need resolution
610 std::map<std::string, Size> stats;
611 for (auto const& [d, _] : fixings) {
612 auto idx = i.comm(d);
613 std::string name = idx->name();
614 fixings_[name].insert(idx->fixingCalendar().adjust(d, Preceding));
615 stats[name]++;
616 }
617 for (auto const& s : stats) {
618 DLOG("added " << s.second << " fixings for '" << s.first << "' (from eval op, prob fcts)");
619 }
620 } else {
621 // all other indices can be handled generically, notice for inf we include the scripting specific
622 // suffixes #L, #F in the index name, this is handled in the scripted trade builder when populating the
623 // required fixings
624
625 if (i.irIborFallback(iborFallbackConfig)) {
626 // well, except ibor fallback indices that we handle here...
627 Size nIbor = 0, nRfr = 0;
628 for (auto [d, _] : fixings) {
629 d = i.index()->fixingCalendar().adjust(d, Preceding);
630 if (d >= i.irIborFallback(iborFallbackConfig)->switchDate()) {
631 auto fd = i.irIborFallback(iborFallbackConfig)->onCoupon(d)->fixingDates();
632 fixings_[iborFallbackConfig.fallbackData(name).rfrIndex].insert(fd.begin(), fd.end());
633 nRfr += fd.size();
634 } else {
635 fixings_[i.name()].insert(d);
636 nIbor++;
637 }
638 }
639 DLOG("added " << nIbor << " Ibor and " << nRfr << " Rfr fixings for ibor fallback '" << i.name()
640 << "' (from eval op, prob fcts)");
641 } else if (i.irOvernightFallback(iborFallbackConfig)) {
642 Size nOis = 0, nRfr = 0;
643 for (auto [d, _] : fixings) {
644 d = i.index()->fixingCalendar().adjust(d, Preceding);
645 if (d >= i.irOvernightFallback(iborFallbackConfig)->switchDate()) {
646 fixings_[iborFallbackConfig.fallbackData(name).rfrIndex].insert(d);
647 nRfr++;
648 } else {
649 fixings_[i.name()].insert(d);
650 nOis++;
651 }
652 }
653 DLOG("added " << nOis << " OIS and " << nRfr << " Rfr fallback fixings for OIS fallback '" << i.name()
654 << "' (from eval op, prob fcts)");
655 } else {
656 // ... and all the others here:
657 IndexInfo imkt(name, market_);
658 for (const auto& [d, prob] : fixings) {
659 fixings_[i.name()].insert((prob ? imkt : i).index()->fixingCalendar().adjust(d, Preceding));
660 }
661 DLOG("added " << fixings.size() << " fixings for '" << i.name() << "' (from eval op, prob fcts)");
662 }
663 }
664 }
665
666 // add fixings from FWDCOMP(), FWDAVG()
667 for (auto const& f : staticAnalyser_->fwdCompAvgFixingDates()) {
668 QL_REQUIRE(IndexInfo(f.first).isIr(),
669 "FWD[COMP|AVG]() only supports IR ON indices, got '" << f.first << "' during fixing map population");
670 fixings_[f.first].insert(f.second.begin(), f.second.end());
671 DLOG("added " << f.second.size() << " fixings for '" << f.first << "' (from FWD[COMP|AVG]())");
672 }
673}
674
676 DLOG("Extract pay ccys and determine the model's base ccy");
677 for (auto const& c : staticAnalyser_->payObsDates()) {
678 payCcys_.insert(c.first);
679 DLOG("got pay currency " << c.first);
680 }
681}
682
684 std::set<std::string> baseCcyCandidates;
685
686 // candidates are target currencies from the fx indices
687 for (auto const& i : fxIndices_) {
688 std::string ccy = i.fx()->targetCurrency().code();
689 baseCcyCandidates.insert(ccy);
690 DLOG("add base ccy candidate " << ccy << " from " << i);
691 }
692
693 // add pay currencies as base ccy canditate only if there are no candidates from the fx indices
694 if (baseCcyCandidates.empty()) {
695 for (auto const& p : payCcys_) {
696 baseCcyCandidates.insert(p);
697 DLOG("add base ccy candidate " << p << " from pay ccys");
698 }
699 }
700
701 // if there is only one candidate and we do not enforce the base ccy from the model parameters we take that,
702 // otherweise the base ccy from the model parameters
703
704 if (baseCcyCandidates.size() == 1 && !enforceBaseCcy_) {
705 baseCcy_ = *baseCcyCandidates.begin();
706 } else {
708 }
709
710 DLOG("base ccy is " << baseCcy_
711 << (amcCam_ != nullptr ? "(this choice might be overwritten below for AMC builders)" : ""));
712}
713
715 QL_REQUIRE(e.isEq(), "ScriptedTradeEngineBuilder::getEqCcy(): expected eq index, got " << e.name());
716 // the eq currency can only be retrieved from the market
717 Currency tmp = market_->equityCurve(e.eq()->name(), configuration(MarketContext::pricing))->currency();
718 QL_REQUIRE(!tmp.empty(), "ScriptedTradeEngineBuilder: Cannot find currency for equity '"
719 << e.eq()->name() << "'. Check if equity is present in curveconfig.");
720 return tmp.code();
721}
722
724 QL_REQUIRE(e.isComm(), "ScriptedTradeEngineBuilder::getCommCcy(): expected comm index, got " << e.name());
725 // the comm currency can only be retrieved from the market
726 Currency tmp = market_->commodityPriceCurve(e.commName(), configuration(MarketContext::pricing))->currency();
727 QL_REQUIRE(!tmp.empty(), "ScriptedTradeEngineBuilder: Cannot find currency for commodity '"
728 << e.commName() << "'. Check if Commodity is present in curveconfig.");
729 return tmp.code();
730}
731
733 std::set<std::string> tmpCcys;
734 tmpCcys.insert(baseCcy_);
735
736 DLOG("Compile the model currencies list");
737
738 for (auto const& c : payCcys_) {
739 tmpCcys.insert(c);
740 }
741
742 for (auto const& i : fxIndices_) {
743 std::string f = i.fx()->sourceCurrency().code();
744 std::string d = i.fx()->targetCurrency().code();
745 tmpCcys.insert(f);
746 tmpCcys.insert(d);
747 }
748
749 // ir index currencies are only added for the cam model, for bs or local vol they are not needed
750 // inf index currencies are not needed for the dk in the cam model, but for jy they are
751 if (modelParam_ == "GaussianCam") {
752 for (auto const& i : irIndices_)
753 tmpCcys.insert(i.ir()->currency().code());
754 if (infModelType_ == "DK") {
755 for (auto const& i : infIndices_)
756 tmpCcys.insert(i.inf()->currency().code());
757 }
758 }
759
760 // we only add eqCurrencies / comCurrencies to the modelCcys if we
761 // - build the GaussianCam model which requires all relevant currencies to be present
762 // - or require a dynamic FX process for each currency
763 // In case we build a GaussianCam and fullDynamicIr_ = false, there will be a zero vol process set up
764 // for the IR component though, if there is no requirement for the currency from an IR index.
765 // If fullDynamicFx_ = false the FX components for that currency will be zero vol, too, if there is no
766 // FX index requiring the ccy. See buildGaussianCam().
767 if (fullDynamicFx_ || modelParam_ == "GaussianCam") {
768 for (auto const& e : eqIndices_) {
769 tmpCcys.insert(getEqCcy(e));
770 }
771 for (auto const& c : commIndices_) {
772 tmpCcys.insert(getCommCcy(c));
773 }
774 }
775
776 // if we build an AMC builder, we set the base ccy to the amc model base ccy, otherwise we won't have the
777 // required FX spot processes in the projected model we use for the scripted trade; the only exception is
778 // if we have only one ccy in the final scripted trade model anyway (i.e. only one IR process), in which
779 // case we can go with that one currency and don't need a more complicated model
780 if (amcCam_ != nullptr) {
781 std::string newBaseCcy_ = amcCam_->ir(0)->currency().code();
782 if (newBaseCcy_ == baseCcy_) {
783 DLOG("base ccy and AMC model base ccy are identical (" << baseCcy_ << ")");
784 } else {
785 if (tmpCcys.size() > 1) {
786 DLOG("base ccy " << baseCcy_ << " is overwritten with AMC model base ccy " << newBaseCcy_
787 << ", since more than one ccy is needed in the final model.");
788 baseCcy_ = newBaseCcy_;
789 } else {
790 DLOG("base ccy " << baseCcy_ << " is kept although AMC model base ccy is different (" << newBaseCcy_
791 << "), because it is a single currency model");
792 }
793 }
794 }
795
796 // build currency vector with the base ccy at the front
797 modelCcys_ = std::vector<std::string>(1, baseCcy_);
798 for (auto const& c : tmpCcys)
799 if (c != baseCcy_)
800 modelCcys_.push_back(c);
801
802 // log ccys
803 for (auto const& c : modelCcys_)
804 DLOG("model ccy " << c << " added");
805}
806
808 for (auto const& i : eqIndices_) {
809 modelIndices_.push_back(i.name());
810 modelIndicesCurrencies_.push_back(getEqCcy(i));
811 DLOG("added model index " << modelIndices_.back());
812 }
813
814 for (auto const& i : commIndices_) {
815 modelIndices_.push_back(i.name());
817 DLOG("added model index " << modelIndices_.back());
818 }
819
820 // cover the ccys from the actual fx indices
821 std::set<std::string> coveredCcys;
822 coveredCcys.insert(baseCcy_);
823 for (auto const& i : fxIndices_) {
824 std::string targetCcy = i.fx()->targetCurrency().code();
825 std::string sourceCcy = i.fx()->sourceCurrency().code();
826 if (sourceCcy != baseCcy_ &&
827 std::find(coveredCcys.begin(), coveredCcys.end(), sourceCcy) == coveredCcys.end()) {
828 modelIndices_.push_back("FX-GENERIC-" + sourceCcy + "-" + baseCcy_);
829 modelIndicesCurrencies_.push_back(sourceCcy);
830 coveredCcys.insert(sourceCcy);
831 DLOG("added model index " << modelIndices_.back());
832 }
833 if (targetCcy != baseCcy_ &&
834 std::find(coveredCcys.begin(), coveredCcys.end(), targetCcy) == coveredCcys.end()) {
835 modelIndices_.push_back("FX-GENERIC-" + targetCcy + "-" + baseCcy_);
836 modelIndicesCurrencies_.push_back(targetCcy);
837 coveredCcys.insert(targetCcy);
838 DLOG("added model index " << modelIndices_.back());
839 }
840 }
841
842 // cover the remaining model currencies, if we require this via the fullDynamicFx parameter
843 if (fullDynamicFx_) {
844 for (Size i = 1; i < modelCcys_.size(); ++i) {
845 if (std::find(coveredCcys.begin(), coveredCcys.end(), modelCcys_[i]) == coveredCcys.end()) {
846 modelIndices_.push_back("FX-GENERIC-" + modelCcys_[i] + "-" + baseCcy_);
848 coveredCcys.insert(modelCcys_[i]);
849 DLOG("added model index " << modelIndices_.back() << " (since fullDynamicFx = true)");
850 }
851 }
852 }
853
854 for (auto const& i : irIndices_) {
855 if (i.irSwap())
856 modelIrIndices_.push_back(
857 std::make_pair(i.name(), *market_->swapIndex(i.name(), configuration(MarketContext::pricing))));
858 else
859 modelIrIndices_.push_back(
860 std::make_pair(i.name(), *market_->iborIndex(i.name(), configuration(MarketContext::pricing))));
861 DLOG("added model ir index " << i.name());
862 }
863
864 for (auto const& i : infIndices_) {
865 modelInfIndices_.push_back(
866 std::make_pair(i.name(), *market_->zeroInflationIndex(i.infName(), configuration(MarketContext::pricing))));
867 DLOG("added model inf index " << i.name());
868 }
869}
870
872
873 if (zeroVolatility_) {
874 DLOG("skipping correlation setup because we are using zero volatility");
875 return;
876 }
877
878 // collect pairs of model index names and correlation curve lookup names
879 std::set<std::pair<std::string, std::string>> tmp;
880
881 // EQ, FX, COMM indices
882 for (auto const& m : modelIndices_) {
883 IndexInfo ind(m);
884 if (ind.isComm()) {
885 // for COMM indices we expect the correlation on the COMM name level (not on single futures)
886 // notice we might have different COMM indices on the same name (via spot, future, dynamic future
887 // reference) - for those the correlationCurve() call below will return a constant 1.0 correlation
888 tmp.insert(std::make_pair(m, "COMM-" + ind.commName()));
889 } else {
890 // for EQ, FX the lookup name is the same as the model index name
891 tmp.insert(std::make_pair(m, m));
892 }
893 }
894
895 // need the ir, inf indices only for GaussianCam
896 if (modelParam_ == "GaussianCam") {
897 for (auto const& irIdx : modelIrIndices_) {
898 // for IR the lookup name is the same as the model index name
899 tmp.insert(std::make_pair(irIdx.first, irIdx.first));
900 }
901 for (auto const& infIdx : modelInfIndices_) {
902 // for INF the lookup name is without the #L, #F suffix
903 IndexInfo ind(infIdx.first);
904 tmp.insert(std::make_pair(infIdx.first, ind.infName()));
905 }
906 }
907
908 DLOG("adding correlations for indices:");
909 for (auto const& n : tmp)
910 DLOG("model index '" << n.first << "' lookup name '" << n.second << "'");
911
912 std::vector<std::pair<std::string, std::string>> corrModelIndices(tmp.begin(), tmp.end());
913
914 for (Size i = 0; i < corrModelIndices.size(); ++i) {
915 for (Size j = 0; j < i; ++j) {
916 try {
917 correlations_[std::make_pair(corrModelIndices[i].first, corrModelIndices[j].first)] =
918 correlationCurve(corrModelIndices[i].second, corrModelIndices[j].second);
919 DLOG("added correlation for " << corrModelIndices[j].second << " ~ " << corrModelIndices[i].second);
920 } catch (const std::exception& e) {
921 WLOG("no correlation provided for " << corrModelIndices[j].second << " ~ " << corrModelIndices[i].second
922 << "(" << e.what() << ")");
923 }
924 }
925 }
926}
927
929 lastRelevantDate_ = Date::minDate();
930 for (auto const& s : staticAnalyser_->indexEvalDates())
931 for (auto const& d : s.second)
933 for (auto const& d : staticAnalyser_->regressionDates())
935 for (auto const& s : staticAnalyser_->payObsDates())
936 for (auto const& d : s.second)
938 for (auto const& s : staticAnalyser_->payPayDates())
939 for (auto const& d : s.second)
941 for (auto const& s : staticAnalyser_->discountObsDates())
942 for (auto const& d : s.second)
944 for (auto const& s : staticAnalyser_->discountPayDates())
945 for (auto const& d : s.second)
947 DLOG("last relevant date: " << lastRelevantDate_);
948}
949
951 Handle<BlackVolTermStructure> vol;
952 if (zeroVolatility_) {
953 vol = Handle<BlackVolTermStructure>(
954 QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), 0.0, ActualActual(ActualActual::ISDA)));
955 DLOG("using zero volatility processes");
956 }
957 for (Size i = 0; i < modelIndices_.size(); ++i) {
958 IndexInfo ind(modelIndices_[i]);
959 if (ind.isEq()) {
960 std::string name = ind.eq()->name();
961 auto spot = market_->equitySpot(name, configuration(MarketContext::pricing));
962 auto div = market_->equityDividendCurve(name, configuration(MarketContext::pricing));
963 auto fc = market_->equityForecastCurve(name, configuration(MarketContext::pricing));
964 if (!zeroVolatility_)
966 processes_.push_back(QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spot, div, fc, vol));
967 DLOG("added process for equity " << name);
968 } else if (ind.isComm()) {
969 std::string name = ind.commName();
970 auto spot = Handle<Quote>(QuantLib::ext::make_shared<DerivedPriceQuote>(
971 market_->commodityPriceCurve(name, configuration(MarketContext::pricing))));
972 auto priceCurve = market_->commodityPriceCurve(name, configuration(MarketContext::pricing));
974 auto div = Handle<YieldTermStructure>(QuantLib::ext::make_shared<PriceTermStructureAdapter>(*priceCurve, *fc));
975 div->enableExtrapolation();
976 if (!zeroVolatility_)
977 vol = market_->commodityVolatility(name, configuration(MarketContext::pricing));
978 processes_.push_back(QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spot, div, fc, vol));
979 DLOG("added process for commodity " << name);
980 } else if (ind.isFx()) {
981 std::string targetCcy = ind.fx()->targetCurrency().code();
982 std::string sourceCcy = ind.fx()->sourceCurrency().code();
983 auto spot = market_->fxSpot(sourceCcy + targetCcy, configuration(MarketContext::pricing));
984 auto div = market_->discountCurve(sourceCcy, configuration(MarketContext::pricing));
985 auto fc = market_->discountCurve(targetCcy, configuration(MarketContext::pricing));
986 if (!zeroVolatility_)
987 vol = market_->fxVol(sourceCcy + targetCcy, configuration(MarketContext::pricing));
988 processes_.push_back(QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spot, div, fc, vol));
989 DLOG("added process for fx " << sourceCcy << "-" << targetCcy);
990 } else {
991 QL_FAIL("unexpected model index " << ind);
992 }
993 }
994}
995
997 if (zeroVolatility_) {
998 DLOG("skipping IR reversion setup because we are using zero volatility");
999 return;
1000 }
1001 // get reversions for ir index currencies ...
1002 std::set<std::string> irCcys;
1003 for (auto const& i : irIndices_)
1004 irCcys.insert(i.ir()->currency().code());
1005 // ... or all currencies if we require dynamic processes for all
1006 if (fullDynamicIr_) {
1007 irCcys.insert(modelCcys_.begin(), modelCcys_.end());
1008 }
1009 for (auto const& ccy : irCcys) {
1010 string revStr = modelParameter("IrReversion_" + ccy, {resolvedProductTag_}, false, "");
1011 if (revStr.empty())
1012 revStr = modelParameter("IrReversion", {resolvedProductTag_}, false, "");
1013 QL_REQUIRE(!revStr.empty(), "Did not find reversion for "
1014 << ccy
1015 << ", need IrReversion_CCY or IrReversion parameter in pricing engine config.");
1016 irReversions_[ccy] = parseReal(revStr);
1017 DLOG("got Hull White reversion " << irReversions_[ccy] << " for " << ccy);
1018 }
1019}
1020
1021namespace {
1022QuantLib::ext::shared_ptr<ZeroInflationIndex>
1023getInfMarketIndex(const std::string& name,
1024 const std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<ZeroInflationIndex>>>& indices) {
1025 for (auto const& i : indices) {
1026 if (i.first == name)
1027 return i.second;
1028 }
1029 QL_FAIL("ScriptedTradeEngineBuilder::compileSimulationAndAddDates(): did not find zero inflation index '"
1030 << name << "' in model indices, this is unexpected");
1031}
1032} // namespace
1033
1035 DLOG("compile simulation and additional dates...");
1036
1037 for (auto const& s : staticAnalyser_->indexEvalDates()) {
1038 IndexInfo info(s.first);
1039 // skip generic indices, for them we do not to add the obs date to sim or add dates
1040 if (info.isGeneric())
1041 continue;
1042 // need ir / inf index observation dates only for GaussianCam as simulation dates, for LocalVol, BS
1043 // we don't need to add them at all
1044 if ((!info.isIr() && !info.isInf()) || modelParam_ == "GaussianCam") {
1045 if (info.isInf()) {
1046 // inf needs special considerations
1047 QuantLib::ext::shared_ptr<ZeroInflationIndex> marketIndex = getInfMarketIndex(info.name(), modelInfIndices_);
1048 Size lag = getInflationSimulationLag(marketIndex);
1049 for (auto const& d : s.second) {
1050 auto lim = inflationPeriod(d, info.inf()->frequency());
1051 simulationDates_.insert(lim.first + lag);
1052 QL_DEPRECATED_DISABLE_WARNING
1053 // This will be removed in a later release and all inf indices are then flat
1054 if (info.inf()->interpolated())
1055 simulationDates_.insert(d + lag);
1056 QL_DEPRECATED_ENABLE_WARNING
1057 }
1058 } else {
1059 // for all other indices we just take the original dates
1060 simulationDates_.insert(s.second.begin(), s.second.end());
1061 }
1062 DLOG("added " << s.second.size() << " simulation dates for '" << s.first << "' (from eval op obs dates)");
1063 }
1064 }
1065
1066 for (auto const& s : staticAnalyser_->indexFwdDates()) {
1067 IndexInfo info(s.first);
1068 // do not need ir / inf index fwd dates (not for LocalVol, BS, but also not for GaussianCam)
1069 if (!info.isIr() && !info.isInf()) {
1070 addDates_.insert(s.second.begin(), s.second.end());
1071 DLOG("added " << s.second.size() << " additional dates for '" << s.first << "' (from eval op fwd dates)");
1072 }
1073 }
1074
1075 for (auto const& s : staticAnalyser_->payObsDates()) {
1076 // need pay obs dates as simulation dates only for GaussianCam, for Local Vol, BS
1077 // add them as addDaes except the pay ccy is not base and there is an fx index with
1078 // the pay ccy as for ccy (then the simulated fx index will be used for ccy conversion)
1079 std::string payCcy = s.first;
1080 if (modelParam_ == "GaussianCam" ||
1081 (baseCcy_ != payCcy &&
1082 std::find_if(modelIndices_.begin(), modelIndices_.end(), [&payCcy](const std::string& s) {
1083 IndexInfo ind(s);
1084 return ind.isFx() && ind.fx()->sourceCurrency().code() == payCcy;
1085 }) != modelIndices_.end())) {
1086 simulationDates_.insert(s.second.begin(), s.second.end());
1087 DLOG("added " << s.second.size() << " simulation dates for '" << payCcy << "' (from pay() obs dates)");
1088 } else {
1089 addDates_.insert(s.second.begin(), s.second.end());
1090 DLOG("added " << s.second.size() << " additional dates for '" << payCcy << "' (from pay() obs dates)");
1091 }
1092 }
1093
1094 simulationDates_.insert(staticAnalyser_->regressionDates().begin(), staticAnalyser_->regressionDates().end());
1095 DLOG("added " << staticAnalyser_->regressionDates().size() << " simulation dates (from npv() regression dates)");
1096
1097 for (auto const& s : staticAnalyser_->payPayDates()) {
1098 addDates_.insert(s.second.begin(), s.second.end());
1099 DLOG("added " << s.second.size() << " additional dates for '" << s.first << "' (from pay() pay dates)");
1100 }
1101
1102 for (auto const& s : staticAnalyser_->discountObsDates()) {
1103 // need disocunt obs dates only for GaussianCam as simulation dates, for Local Vol, BS
1104 // add them as addDates
1105 if (modelParam_ == "GaussianCam") {
1106 simulationDates_.insert(s.second.begin(), s.second.end());
1107 DLOG("added " << s.second.size() << " simulation dates for '" << s.first
1108 << "' (from discount() obs dates)");
1109 } else {
1110 addDates_.insert(s.second.begin(), s.second.end());
1111 DLOG("added " << s.second.size() << " additional dates for '" << s.first
1112 << "' (from discount() obs dates)");
1113 }
1114 DLOG("added " << s.second.size() << " additional dates for '" << s.first << "' (from discount() obs dates)");
1115 }
1116
1117 for (auto const& s : staticAnalyser_->discountPayDates()) {
1118 addDates_.insert(s.second.begin(), s.second.end());
1119 DLOG("added " << s.second.size() << " additional dates for '" << s.first << "' (from discount() pay dates)");
1120 }
1121
1122 for (auto const& s : staticAnalyser_->fwdCompAvgEvalDates()) {
1123 // need fwd comp eval dates only for GaussianCam as simulation dates, for Local Vol, BS
1124 // add them as addDates
1125 if (modelParam_ == "GaussianCam") {
1126 simulationDates_.insert(s.second.begin(), s.second.end());
1127 DLOG("added " << s.second.size() << " simulation dates for '" << s.first
1128 << "' (from fwd[Comp|Avg]() obs dates)");
1129 } else {
1130 addDates_.insert(s.second.begin(), s.second.end());
1131 DLOG("added " << s.second.size() << " additional dates for '" << s.first
1132 << "' (from fwd[Comp|Avg]() obs dates)");
1133 }
1134 }
1135
1136 for (auto const& s : staticAnalyser_->fwdCompAvgStartEndDates()) {
1137 addDates_.insert(s.second.begin(), s.second.end());
1138 DLOG("added " << s.second.size() << " additional dates for '" << s.first
1139 << "' (from fwd[Comp|Avg]() start/end dates)");
1140 }
1141}
1142
1143namespace {
1144
1145// filter out "dummy" strikes, such as up and out barriers set to 1E6 to indicate +inf
1146std::map<std::string, std::vector<Real>> filterBlackScholesCalibrationStrikes(
1147 const std::map<std::string, std::vector<Real>>& strikes, const std::vector<std::string>& modelIndices,
1148 const std::vector<QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess>>& processes, const Real T) {
1149 QL_REQUIRE(modelIndices.size() == processes.size(), "filterBlackScholesCalibrationStrikes: processes size ("
1150 << processes.size() << ") must match modelIndices size ("
1151 << modelIndices.size() << ")");
1152 std::map<std::string, std::vector<Real>> result;
1153 if (T < 0.0 || close_enough(T, 0.0)) {
1154 DLOG("excluding all calibration strikes, because last relevant time is not positive (" << T << ")");
1155 return result;
1156 }
1157 const Real normInvEps = 2.0 * InverseCumulativeNormal()(1 - 1E-6); // hardcoded epsilon
1158 for (auto const& ks : strikes) {
1159 auto m = std::find(modelIndices.begin(), modelIndices.end(), ks.first);
1160 if (m != modelIndices.end()) {
1161 Size index = std::distance(modelIndices.begin(), m);
1162 Real atmf = processes[index]->x0() / processes[index]->riskFreeRate()->discount(T) *
1163 processes[index]->dividendYield()->discount(T);
1164 Real sigmaSqrtT =
1165 std::max(processes[index]->blackVolatility()->blackVol(T, atmf), 0.1) * std::sqrt(std::max(T, 1.0));
1166 Real xmin = std::exp(std::log(atmf) - normInvEps * sigmaSqrtT);
1167 Real xmax = std::exp(std::log(atmf) + normInvEps * sigmaSqrtT);
1168 for (auto const k : ks.second) {
1169 if (k < xmin || k > xmax) {
1170 DLOG("excluding calibration strike (" << k << ") for index '" << modelIndices[index]
1171 << "', bounds = [" << xmin << "," << xmax << "]");
1172 } else {
1173 result[ks.first].push_back(k);
1174 }
1175 }
1176 } else {
1177 result[ks.first] = ks.second;
1178 }
1179 }
1180 return result;
1181}
1182
1183// build vector of calibration strikes per model index from map
1184std::vector<std::vector<Real>> getCalibrationStrikesVector(const std::map<std::string, std::vector<Real>>& strikes,
1185 const std::vector<std::string>& modelIndices) {
1186 std::vector<std::vector<Real>> result;
1187 for (auto const& m : modelIndices) {
1188 auto s = strikes.find(m);
1189 if (s != strikes.end())
1190 result.push_back(s->second);
1191 else
1192 result.push_back({});
1193 }
1194 return result;
1195}
1196
1197} // namespace
1198
1200 const IborFallbackConfig& iborFallbackConfig) {
1201 Real T = modelCurves_.front()->timeFromReference(lastRelevantDate_);
1202 auto filteredStrikes = filterBlackScholesCalibrationStrikes(calibrationStrikes_, modelIndices_, processes_, T);
1203 // ignore timeStepsPerYear if we have no correlations, i.e. we can take large timesteps without changing anything
1204 auto builder = QuantLib::ext::make_shared<BlackScholesModelBuilder>(
1206 calibration_, getCalibrationStrikesVector(filteredStrikes, modelIndices_));
1207 if (useCg_) {
1208 modelCG_ = QuantLib::ext::make_shared<BlackScholesCG>(
1210 modelIndicesCurrencies_, builder->model(), correlations_, simulationDates_, iborFallbackConfig,
1211 calibration_, filteredStrikes);
1212 } else {
1213 model_ = QuantLib::ext::make_shared<BlackScholes>(modelSize_, modelCcys_, modelCurves_, modelFxSpots_, modelIrIndices_,
1215 builder->model(), correlations_, mcParams_, simulationDates_,
1216 iborFallbackConfig, calibration_, filteredStrikes);
1217 }
1218 modelBuilders_.insert(std::make_pair(id, builder));
1219}
1220
1222 const IborFallbackConfig& iborFallbackConfig) {
1223 Real T = modelCurves_.front()->timeFromReference(lastRelevantDate_);
1224 auto filteredStrikes = filterBlackScholesCalibrationStrikes(calibrationStrikes_, modelIndices_, processes_, T);
1225 auto builder = QuantLib::ext::make_shared<BlackScholesModelBuilder>(
1227 getCalibrationStrikesVector(filteredStrikes, modelIndices_));
1228 model_ = QuantLib::ext::make_shared<FdBlackScholesBase>(
1230 modelIndicesCurrencies_, payCcys_, builder->model(), correlations_, simulationDates_, iborFallbackConfig,
1233 modelBuilders_.insert(std::make_pair(id, builder));
1234}
1235
1236void ScriptedTradeEngineBuilder::buildLocalVol(const std::string& id, const IborFallbackConfig& iborFallbackConfig) {
1238 if (modelParam_ == "LocalVolDupire")
1240 else if (modelParam_ == "LocalVolAndreasenHuge")
1242 else {
1243 QL_FAIL("local vol model type " << modelParam_ << " not recognised.");
1244 }
1245 auto builder = QuantLib::ext::make_shared<LocalVolModelBuilder>(modelCurves_, processes_, simulationDates_, addDates_,
1248 model_ = QuantLib::ext::make_shared<LocalVol>(modelSize_, modelCcys_, modelCurves_, modelFxSpots_, modelIrIndices_,
1250 correlations_, mcParams_, simulationDates_, iborFallbackConfig);
1251 modelBuilders_.insert(std::make_pair(id, builder));
1252}
1253
1254namespace {
1255// return first ir ibor index in given set whose currency matches the parameter ccy, or ccy if no such index exists
1256std::string getFirstIrIndexOrCcy(const std::string& ccy, const std::set<IndexInfo> irIndices) {
1257 for (auto const& index : irIndices) {
1258 if (index.isIrSwap() && index.irSwap()->iborIndex()->currency().code() == ccy)
1259 return IndexNameTranslator::instance().oreName(index.irSwap()->iborIndex()->name());
1260 if (index.isIrIbor() && index.irIbor()->currency().code() == ccy)
1261 return index.name();
1262 }
1263 return ccy;
1264}
1265} // namespace
1266
1267void ScriptedTradeEngineBuilder::buildGaussianCam(const std::string& id, const IborFallbackConfig& iborFallbackConfig,
1268 const std::vector<std::string>& conditionalExpectationModelStates) {
1269 // compile cam correlation matrix
1270 // - we want to use the maximum tenor of an ir index in a correlation pair if several are given (to have
1271 // a well defined rule how to derive the LGM IR correlations); to get there we store the correlations
1272 // together with the index tenors (or null if not applicatble), so that we can decide if we overwrite
1273 // an existing correlation with another candidate or not
1274 // - correlations are for index pair names and must be constant; if not given for a pair, we assume zero
1275 // correlation;
1276 // - correlations for IR processes are taken from IR index correlations, if several indices exist
1277 // for one ccy, the index with the longest tenor T is selected; we do not apply an LGM adjustment for this
1278 // value due to time dependent volatility, since this is usually negligible
1279 // - for inf we do not apply an adjustment either => TODO is that suitable for both DK and JY approximately?
1280 // - for inf JY we have two driving factors (f1,f2) where f1 drives the real rate process and f2 drives the
1281 // inflation index ("fx") process; we assume the correlation between f1 and any other factor (including f2)
1282 // to be zero and set the correlation between f2 and any other factor to the correlation read from the
1283 // market data for the inflation index, TODO is that assumption reasonable?
1284 std::map<std::pair<std::string, std::string>,
1285 std::tuple<Handle<QuantExt::CorrelationTermStructure>, Period, Period>>
1286 tmpCorrelations;
1287 for (auto const& c : correlations_) {
1288 std::pair<std::string, Period> firstEntry = convertIndexToCamCorrelationEntry(c.first.first);
1289 std::pair<std::string, Period> secondEntry = convertIndexToCamCorrelationEntry(c.first.second);
1290 // if we have identical CAM entries (e.g. IR:EUR, IR:EUR) we skip this pair, since we can't specify a
1291 // correlation in this case
1292 if (firstEntry.first == secondEntry.first)
1293 continue;
1294 auto e = tmpCorrelations.find(std::make_pair(firstEntry.first, secondEntry.first));
1295 if (e == tmpCorrelations.end() ||
1296 (firstEntry.second > std::get<1>(e->second) && secondEntry.second > std::get<2>(e->second))) {
1297 tmpCorrelations[std::make_pair(firstEntry.first, secondEntry.first)] =
1298 std::make_tuple(c.second, firstEntry.second, secondEntry.second);
1299 }
1300 }
1301
1302 map<CorrelationKey, Handle<Quote>> camCorrelations;
1303 for (auto const& c : tmpCorrelations) {
1304 CorrelationFactor f_1 = parseCorrelationFactor(c.first.first, '#');
1305 CorrelationFactor f_2 = parseCorrelationFactor(c.first.second, '#');
1306 // update index for JY from 0 to 1 (i.e. to the factor driving the inf index ("fx") process)
1307 // in all other cases the index 0 is fine, since there is only one driving factor always
1308 if (infModelType_ == "JY") {
1309 if (f_1.type == CrossAssetModel::AssetType::INF)
1310 f_1.index = 1;
1311 if (f_2.type == CrossAssetModel::AssetType::INF)
1312 f_2.index = 1;
1313 }
1314 auto q = Handle<Quote>(QuantLib::ext::make_shared<CorrelationValue>(std::get<0>(c.second), 0.0));
1315 camCorrelations[std::make_pair(f_1, f_2)] = q;
1316 DLOG("added correlation for " << c.first.first << "/" << c.first.second << ": " << q->value());
1317 }
1318
1319 // correlation overwrite from pricing engine parameters
1320
1321 std::set<CorrelationFactor> allCorrRiskFactors;
1322
1323 for (auto const& m : modelIndices_)
1324 allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m).first, '#'));
1325 for (auto const& m : modelIrIndices_)
1326 allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first, '#'));
1327 for (auto const& m : modelInfIndices_)
1328 allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first, '#'));
1329 for (auto const& ccy : modelCcys_)
1330 allCorrRiskFactors.insert({CrossAssetModel::AssetType::IR, ccy, 0});
1331
1332 for (auto const& c1 : allCorrRiskFactors) {
1333 for (auto const& c2 : allCorrRiskFactors) {
1334 // determine the number of driving factors for f_1 and f_2
1335 Size nf_1 = c1.type == CrossAssetModel::AssetType::INF && infModelType_ == "JY" ? 2 : 1;
1336 Size nf_2 = c2.type == CrossAssetModel::AssetType::INF && infModelType_ == "JY" ? 2 : 1;
1337 for (Size k = 0; k < nf_1; ++k) {
1338 for (Size l = 0; l < nf_2; ++l) {
1339 auto f_1 = c1;
1340 auto f_2 = c2;
1341 f_1.index = k;
1342 f_2.index = l;
1343 if (f_1 == f_2)
1344 continue;
1345 // lookup names are IR:GBP:0 and IR:GBP whenever the index is zero
1346 auto s_1 = ore::data::to_string(f_1);
1347 auto s_2 = ore::data::to_string(f_2);
1348 std::set<std::string> lookupnames1, lookupnames2;
1349 lookupnames1.insert(s_1);
1350 lookupnames2.insert(s_2);
1351 if (k == 0)
1352 lookupnames1.insert(s_1.substr(0, s_1.size() - 2));
1353 if (l == 0)
1354 lookupnames2.insert(s_2.substr(0, s_2.size() - 2));
1355 for (auto const& l1 : lookupnames1) {
1356 for (auto const& l2 : lookupnames2) {
1357 if (auto overwrite = modelParameter(
1358 "Correlation",
1359 {resolvedProductTag_ + "_" + l1 + "_" + l2, l1 + "_" + l2, resolvedProductTag_},
1360 false);
1361 !overwrite.empty()) {
1362 camCorrelations[std::make_pair(f_1, f_2)] =
1363 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(parseReal(overwrite)));
1364 }
1365 }
1366 }
1367 }
1368 }
1369 }
1370 }
1371
1372 // set up the cam and calibrate it using the cam builder
1373 // if for a non-base currency no fx index is given, we set up a zero vol FX process for this
1374 // if fullDynamicIr is false, we set up a zero vol IR process for currencies that are not irIndex currencies
1375
1376 std::vector<QuantLib::ext::shared_ptr<IrModelData>> irConfigs;
1377 std::vector<QuantLib::ext::shared_ptr<InflationModelData>> infConfigs;
1378 std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
1379 std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
1380 std::vector<QuantLib::ext::shared_ptr<CommoditySchwartzData>> comConfigs;
1381
1382 // calibration expiries and terms for IR, INF, FX, EQ parametrisations (this will only work for a fixed reference
1383 // date, due to the way the cam builder and nested builders work, see ticket #940)
1384 Date referenceDate = modelCurves_.front()->referenceDate();
1385 std::vector<Date> calibrationDates;
1386 std::vector<std::string> calibrationExpiries, calibrationTerms;
1387
1388 for (auto const& d : simulationDates_) {
1389 if (d > referenceDate) {
1390 calibrationDates.push_back(d);
1391 calibrationExpiries.push_back(ore::data::to_string(d));
1392 // make sure the underlying swap has at least 1M to run, QL will throw otherwise during calibration
1393 calibrationTerms.push_back(ore::data::to_string(std::max(d + 1 * Months, lastRelevantDate_)));
1394 }
1395 }
1396
1397 // calibration times (need one less than calibration dates)
1398 std::vector<Real> calibrationTimes;
1399 for (Size i = 0; i + 1 < calibrationDates.size(); ++i) {
1400 calibrationTimes.push_back(modelCurves_.front()->timeFromReference(calibrationDates[i]));
1401 }
1402
1403 // IR configs
1404 for (Size i = 0; i < modelCcys_.size(); ++i) {
1405 auto config = QuantLib::ext::make_shared<IrLgmData>();
1406 config->qualifier() = getFirstIrIndexOrCcy(modelCcys_[i], irIndices_);
1407 config->reversionType() = LgmData::ReversionType::HullWhite;
1408 config->volatilityType() = LgmData::VolatilityType::Hagan;
1409 config->calibrateH() = false;
1410 config->hParamType() = ParamType::Constant;
1411 config->hTimes() = std::vector<Real>();
1412 config->shiftHorizon() =
1413 modelCurves_.front()->timeFromReference(lastRelevantDate_) * 0.5; // TODO hardcode 0.5 here?
1414 config->scaling() = 1.0;
1415 std::string ccy = modelCcys_[i];
1416 // if we don't require fullDynamicIr and there is no model index in the currency, we set up a zero
1417 // vol IR component for this ccy; we also do this if zero vol is specified in the model params
1418 if (calibrationExpiries.empty() || zeroVolatility_ ||
1419 (!fullDynamicIr_ &&
1420 std::find_if(modelIrIndices_.begin(), modelIrIndices_.end(),
1421 [&ccy](const std::pair<std::string, QuantLib::ext::shared_ptr<InterestRateIndex>>& index) {
1422 return index.second->currency().code() == ccy;
1423 }) == modelIrIndices_.end())) {
1424
1425 DLOG("set up zero vol IrLgmData for currency '" << modelCcys_[i] << "'");
1426 // zero vol
1427 config->calibrationType() = CalibrationType::None;
1428 config->hValues() = {0.0};
1429 config->calibrateA() = false;
1430 config->aParamType() = ParamType::Constant;
1431 config->aTimes() = std::vector<Real>();
1432 config->aValues() = {0.0};
1433 } else {
1434 DLOG("set up IrLgmData for currency '" << modelCcys_[i] << "'");
1435 // bootstrapped on atm swaption vols
1436 auto rev = irReversions_.find(modelCcys_[i]);
1437 QL_REQUIRE(rev != irReversions_.end(), "reversion for ccy " << modelCcys_[i] << " not found");
1438 config->calibrationType() = CalibrationType::Bootstrap;
1439 config->hValues() = {rev->second};
1440 config->calibrateA() = true;
1441 config->aParamType() = ParamType::Piecewise;
1442 config->aTimes() = calibrationTimes;
1443 config->aValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030); // start value for optimiser
1444 config->optionExpiries() = calibrationExpiries;
1445 config->optionTerms() = calibrationTerms;
1446 config->optionStrikes() =
1447 std::vector<std::string>(calibrationExpiries.size(), "ATM"); // hardcoded ATM calibration strike
1448 }
1449 irConfigs.push_back(config);
1450 }
1451
1452 // INF configs
1453 for (Size i = 0; i < modelInfIndices_.size(); ++i) {
1454 QuantLib::ext::shared_ptr<InflationModelData> config;
1455 if (zeroVolatility_) {
1456 // for both DK and JY we can just use a zero vol dk component
1457 config = QuantLib::ext::make_shared<InfDkData>(
1458 CalibrationType::None, std::vector<CalibrationBasket>(), modelInfIndices_[i].second->currency().code(),
1459 IndexInfo(modelInfIndices_[i].first).infName(),
1463 } else {
1464 // build calibration basket (CPI Floors at calibration strike or if that is not given, ATM strike)
1465 QuantLib::ext::shared_ptr<BaseStrike> calibrationStrike;
1466 if (auto k = calibrationStrikes_.find(modelInfIndices_[i].first);
1467 k != calibrationStrikes_.end() && !k->second.empty()) {
1468 calibrationStrike = QuantLib::ext::make_shared<AbsoluteStrike>(k->second.front());
1469 } else {
1470 calibrationStrike = QuantLib::ext::make_shared<AtmStrike>(QuantLib::DeltaVolQuote::AtmType::AtmFwd);
1471 }
1472 std::vector<QuantLib::ext::shared_ptr<CalibrationInstrument>> calInstr;
1473 for (auto const& d : calibrationDates)
1474 calInstr.push_back(
1475 QuantLib::ext::make_shared<CpiCapFloor>(QuantLib::CapFloor::Type::Floor, d, calibrationStrike));
1476 std::vector<CalibrationBasket> calBaskets(1, CalibrationBasket(calInstr));
1477 if (infModelType_ == "DK") {
1478 // build DK config
1479 std::string infName = IndexInfo(modelInfIndices_[i].first).infName();
1480 Real vol = parseReal(modelParameter("InfDkVolatility",
1481 {resolvedProductTag_ + "_" + infName, infName, resolvedProductTag_},
1482 false, "0.0050"));
1483 config = QuantLib::ext::make_shared<InfDkData>(
1484 CalibrationType::Bootstrap, calBaskets, modelInfIndices_[i].second->currency().code(),
1485 IndexInfo(modelInfIndices_[i].first).infName(),
1489 // ignore duplicate expiry times among calibration instruments
1490 true);
1491 } else if (infModelType_ == "JY") {
1492 // build JY config
1493 // we calibrate the index ("fx") process to CPI cap/floors and set the real rate process reversion equal to
1494 // the nominal process reversion. The real rate vol is set to a fixed multiple of nominal rate vol, the
1495 // multiplier is taken from the pe config model parameter "InfJyRealToNominalVolRatio"
1496 std::string infName = IndexInfo(modelInfIndices_[i].first).infName();
1497 Size ccyIndex =
1498 std::distance(modelCcys_.begin(), std::find(modelCcys_.begin(), modelCcys_.end(),
1499 modelInfIndices_[i].second->currency().code()));
1500 ReversionParameter realRateRev = QuantLib::ext::static_pointer_cast<LgmData>(irConfigs[ccyIndex])->reversionParameter();
1501 VolatilityParameter realRateVol = QuantLib::ext::static_pointer_cast<LgmData>(irConfigs[ccyIndex])->volatilityParameter();
1502 realRateRev.setCalibrate(false);
1503 realRateVol.setCalibrate(false);
1504 Real realRateToNominalRateRatio = parseReal(
1505 modelParameter("InfJyRealToNominalVolRatio",
1506 {resolvedProductTag_ + "_" + infName, infName, resolvedProductTag_}, false, "1.0"));
1507 QL_REQUIRE(ccyIndex < modelCcys_.size(),
1508 "ScriptedTrade::buildGaussianCam(): internal error, inflation index currency "
1509 << modelInfIndices_[i].second->currency().code() << " not found in model ccy list.");
1510 realRateVol.mult(realRateToNominalRateRatio);
1511 config = QuantLib::ext::make_shared<InfJyData>(
1512 CalibrationType::Bootstrap, calBaskets, modelInfIndices_[i].second->currency().code(), infName,
1513 // real rate reversion and vol
1514 realRateRev, realRateVol,
1515 // index ("fx") vol, start value 0.10 for calibration
1516 VolatilityParameter(true, ParamType::Piecewise, {}, {0.10}),
1517 // no parameter trafo, no optimisation constraints (TODO do we need boundaries?)
1519 // ignore duplicate expiry times among calibration instruments
1520 true,
1521 // link real to nominal rate params
1522 true,
1523 // real rate to nominal rate ratio
1524 realRateToNominalRateRatio);
1525 } else {
1526 QL_FAIL("invalid infModelType '" << infModelType_ << "', expected DK or JY");
1527 }
1528 }
1529 infConfigs.push_back(config);
1530 }
1531
1532 // FX configs
1533 for (Size i = 1; i < modelCcys_.size(); ++i) {
1534 auto config = QuantLib::ext::make_shared<FxBsData>();
1535 config->foreignCcy() = modelCcys_[i];
1536 config->domesticCcy() = modelCcys_[0];
1537 // if we do not have a FX index for the currency, we set up a zero vol process (FX indices are added above
1538 // for all non-base ccys if fullDynamicFx is specified)
1539 bool haveFxIndex = false;
1540 for (Size j = 0; j < modelIndices_.size(); ++j) {
1541 if (IndexInfo(modelIndices_[j]).isFx() && modelIndicesCurrencies_[j] == modelCcys_[i])
1542 haveFxIndex = true;
1543 }
1544 if (calibrationExpiries.empty() || !haveFxIndex || zeroVolatility_) {
1545 DLOG("set up zero vol FxBsData for currency '" << modelCcys_[i] << "'");
1546 // zero vols
1547 config->calibrationType() = CalibrationType::None;
1548 config->calibrateSigma() = false;
1549 config->sigmaParamType() = ParamType::Constant;
1550 config->sigmaTimes() = std::vector<Real>();
1551 config->sigmaValues() = {0.0};
1552 } else {
1553 DLOG("set up FxBsData for currency '" << modelCcys_[i] << "'");
1554 // bootstrapped on atm fx vols
1555 config->calibrationType() = CalibrationType::Bootstrap;
1556 config->calibrateSigma() = true;
1557 config->sigmaParamType() = ParamType::Piecewise;
1558 config->sigmaTimes() = calibrationTimes;
1559 config->sigmaValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.10); // start value for optimiser
1560 config->optionExpiries() = calibrationExpiries;
1561 config->optionStrikes() =
1562 std::vector<std::string>(calibrationExpiries.size(), "ATMF"); // hardcoded ATMF calibration strike
1563 }
1564 fxConfigs.push_back(config);
1565 }
1566
1567 // EQ configs
1568 for (auto const& eq : eqIndices_) {
1569 auto config = QuantLib::ext::make_shared<EqBsData>();
1570 config->currency() = getEqCcy(eq);
1571 config->eqName() = eq.eq()->name();
1572 if (calibrationExpiries.empty() || zeroVolatility_) {
1573 DLOG("set up zero vol EqBsData for underlying " << eq.eq()->name());
1574 // zero vols
1575 config->calibrationType() = CalibrationType::None;
1576 config->calibrateSigma() = false;
1577 config->sigmaParamType() = ParamType::Constant;
1578 config->sigmaTimes() = std::vector<Real>();
1579 config->sigmaValues() = {0.0};
1580 } else {
1581 DLOG("set up EqBsData for underlying '" << eq.eq()->name() << "'");
1582 // bootstrapped on atm eq vols
1583 config->calibrationType() = CalibrationType::Bootstrap;
1584 config->calibrateSigma() = true;
1585 config->sigmaParamType() = ParamType::Piecewise;
1586 config->sigmaTimes() = calibrationTimes;
1587 config->sigmaValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.10); // start value for optimiser
1588 config->optionExpiries() = calibrationExpiries;
1589 config->optionStrikes() =
1590 std::vector<std::string>(calibrationExpiries.size(), "ATMF"); // hardcoded ATMF calibration strike
1591 }
1592 eqConfigs.push_back(config);
1593 }
1594
1595 // TODO
1596 std::vector<QuantLib::ext::shared_ptr<CrLgmData>> crLgmConfigs;
1597 std::vector<QuantLib::ext::shared_ptr<CrCirData>> crCirConfigs;
1598
1599 // COMM configs
1600 for (auto const& comm : commIndices_) {
1601 auto config = QuantLib::ext::make_shared<CommoditySchwartzData>();
1602 config->currency() = getCommCcy(comm);
1603 config->name() = comm.commName();
1604 if (calibrationExpiries.empty() || zeroVolatility_) {
1605 config->calibrationType() = CalibrationType::None;
1606 config->calibrateSigma() = false;
1607 config->sigmaParamType() = ParamType::Constant;
1608 config->sigmaValue() = 0.0;
1609 } else {
1610 config->calibrationType() = CalibrationType::BestFit;
1611 config->calibrateSigma() = true;
1612 config->sigmaParamType() = ParamType::Constant;
1613 config->sigmaValue() = 0.10; // start value for optimizer
1614 config->optionExpiries() = calibrationExpiries;
1615 config->optionStrikes() =
1616 std::vector<std::string>(calibrationExpiries.size(), "ATMF"); // hardcoded ATMF calibration strike
1617 }
1618 comConfigs.push_back(config);
1619 }
1620
1621 std::string configurationInCcy = configuration(MarketContext::irCalibration);
1622 std::string configurationXois = configuration(MarketContext::pricing);
1623 auto discretization = useCg_ ? CrossAssetModel::Discretization::Euler : CrossAssetModel::Discretization::Exact;
1624 auto camBuilder = QuantLib::ext::make_shared<CrossAssetModelBuilder>(
1625 market_,
1626 QuantLib::ext::make_shared<CrossAssetModelData>(irConfigs, fxConfigs, eqConfigs, infConfigs, crLgmConfigs, crCirConfigs,
1627 comConfigs, 0, camCorrelations, bootstrapTolerance_, "LGM",
1628 discretization),
1629 configurationInCcy, configurationXois, configurationXois, configurationInCcy, configurationInCcy,
1631 SalvagingAlgorithm::Spectral, id);
1632
1633 // effective time steps per year: zero for exact evolution, otherwise the pricing engine parameter
1634 if (useCg_) {
1635 modelCG_ = QuantLib::ext::make_shared<GaussianCamCG>(
1638 camBuilder->model()->discretization() == CrossAssetModel::Discretization::Exact ? 0 : timeStepsPerYear_,
1639 iborFallbackConfig, std::vector<Size>(), conditionalExpectationModelStates);
1640 } else {
1641 model_ = QuantLib::ext::make_shared<GaussianCam>(
1644 camBuilder->model()->discretization() == CrossAssetModel::Discretization::Exact ? 0 : timeStepsPerYear_,
1645 iborFallbackConfig, std::vector<Size>(), conditionalExpectationModelStates);
1646 }
1647
1648 modelBuilders_.insert(std::make_pair(id, camBuilder));
1649}
1650
1652 const IborFallbackConfig& iborFallbackConfig) {
1653
1654 Date referenceDate = modelCurves_.front()->referenceDate();
1655 std::vector<Date> calibrationDates;
1656 std::vector<std::string> calibrationExpiries, calibrationTerms;
1657
1658 for (auto const& d : simulationDates_) {
1659 if (d > referenceDate) {
1660 calibrationDates.push_back(d);
1661 calibrationExpiries.push_back(ore::data::to_string(d));
1662 // make sure the underlying swap has at least 1M to run, QL will throw otherwise during calibration
1663 calibrationTerms.push_back(ore::data::to_string(std::max(d + 1 * Months, lastRelevantDate_)));
1664 }
1665 }
1666
1667 // calibration times (need one less than calibration dates)
1668 std::vector<Real> calibrationTimes;
1669 for (Size i = 0; i + 1 < calibrationDates.size(); ++i) {
1670 calibrationTimes.push_back(modelCurves_.front()->timeFromReference(calibrationDates[i]));
1671 }
1672
1673 // determine calibration strike
1674 std::string calibrationStrike = "ATM";
1675 if(calibration_ == "Deal") {
1676 // first model index found in calibration strike spec and first specified strike therein is used
1677 for (auto const& m : modelIrIndices_) {
1678 if (auto f = calibrationStrikes_.find(m.first); f != calibrationStrikes_.end()) {
1679 if(!f->second.empty()) {
1680 calibrationStrike = boost::lexical_cast<std::string>(f->second.front());
1681 }
1682 }
1683 }
1684 }
1685
1686 // IR config
1687 QL_REQUIRE(modelCcys_.size() == 1,
1688 "ScriptedTradeEngineBuilder::buildFdGaussianCam(): only one ccy is supported, got "
1689 << modelCcys_.size());
1690
1691 auto config = QuantLib::ext::make_shared<IrLgmData>();
1692 config->qualifier() = getFirstIrIndexOrCcy(modelCcys_.front(), irIndices_);
1693 config->reversionType() = LgmData::ReversionType::HullWhite;
1694 config->volatilityType() = LgmData::VolatilityType::Hagan;
1695 config->calibrateH() = false;
1696 config->hParamType() = ParamType::Constant;
1697 config->hTimes() = std::vector<Real>();
1698 config->shiftHorizon() =
1699 modelCurves_.front()->timeFromReference(lastRelevantDate_) * 0.5; // TODO hardcode 0.5 here?
1700 config->scaling() = 1.0;
1701 std::string ccy = modelCcys_.front();
1702 if (zeroVolatility_ ) {
1703 DLOG("set up zero vol IrLgmData for currency '" << modelCcys_.front() << "'");
1704 // zero vol
1705 config->calibrationType() = CalibrationType::None;
1706 config->hValues() = {0.0};
1707 config->calibrateA() = false;
1708 config->aParamType() = ParamType::Constant;
1709 config->aTimes() = std::vector<Real>();
1710 config->aValues() = {0.0};
1711 } else {
1712 DLOG("set up IrLgmData for currency '" << modelCcys_.front() << "'");
1713 auto rev = irReversions_.find(modelCcys_.front());
1714 QL_REQUIRE(rev != irReversions_.end(), "reversion for ccy " << modelCcys_.front() << " not found");
1715 config->calibrationType() = CalibrationType::Bootstrap;
1716 config->hValues() = {rev->second};
1717 config->calibrateA() = true;
1718 config->aParamType() = ParamType::Piecewise;
1719 config->aTimes() = calibrationTimes;
1720 config->aValues() = std::vector<Real>(calibrationTimes.size() + 1, 0.0030); // start value for optimiser
1721 config->optionExpiries() = calibrationExpiries;
1722 config->optionTerms() = calibrationTerms;
1723 config->optionStrikes() = std::vector<std::string>(calibrationExpiries.size(), calibrationStrike);
1724 }
1725
1726 std::string configurationInCcy = configuration(MarketContext::irCalibration);
1727 std::string configurationXois = configuration(MarketContext::pricing);
1728
1729 auto camBuilder = QuantLib::ext::make_shared<CrossAssetModelBuilder>(
1730 market_,
1731 QuantLib::ext::make_shared<CrossAssetModelData>(
1732 std::vector<QuantLib::ext::shared_ptr<IrModelData>>{config}, std::vector<QuantLib::ext::shared_ptr<FxBsData>>{},
1733 std::vector<QuantLib::ext::shared_ptr<EqBsData>>{}, std::vector<QuantLib::ext::shared_ptr<InflationModelData>>{},
1734 std::vector<QuantLib::ext::shared_ptr<CrLgmData>>{}, std::vector<QuantLib::ext::shared_ptr<CrCirData>>{},
1735 std::vector<QuantLib::ext::shared_ptr<CommoditySchwartzData>>{}, 0,
1736 std::map<CorrelationKey, QuantLib::Handle<QuantLib::Quote>>{}, bootstrapTolerance_, "LGM",
1737 CrossAssetModel::Discretization::Exact),
1738 configurationInCcy, configurationXois, configurationXois, configurationInCcy, configurationInCcy,
1740 SalvagingAlgorithm::Spectral, id);
1741
1742 model_ = QuantLib::ext::make_shared<FdGaussianCam>(camBuilder->model(), modelCcys_.front(), modelCurves_.front(),
1744 mesherEpsilon_, iborFallbackConfig);
1745
1746 modelBuilders_.insert(std::make_pair(id, camBuilder));
1747}
1748
1749void ScriptedTradeEngineBuilder::buildAMCCGModel(const std::string& id, const IborFallbackConfig& iborFallbackConfig,
1750 const std::vector<std::string>& conditionalExpectationModelStates) {
1751 // nothing to build really, the resulting model is exactly the input model
1752 QL_REQUIRE(useCg_, "building gaussian cam from external amc cg model, useCg must be set to true in this case.");
1754}
1755
1757 const std::string& id, const IborFallbackConfig& iborFallbackConfig,
1758 const std::vector<std::string>& conditionalExpectationModelStates) {
1759
1760 QL_REQUIRE(!useCg_, "building gaussian cam from external amc cam, useCg must be set to false in this case.");
1761
1762 std::vector<std::pair<CrossAssetModel::AssetType, Size>> selectedComponents;
1763
1764 // IR configs
1765 for (Size i = 0; i < modelCcys_.size(); ++i) {
1766 selectedComponents.push_back(
1767 std::make_pair(CrossAssetModel::AssetType::IR, amcCam_->ccyIndex(parseCurrency(modelCcys_[i]))));
1768 }
1769
1770 // INF configs
1771 for (Size i = 0; i < modelInfIndices_.size(); ++i) {
1772 selectedComponents.push_back(std::make_pair(CrossAssetModel::AssetType::INF,
1773 amcCam_->infIndex(IndexInfo(modelInfIndices_[i].first).infName())));
1774 }
1775
1776 // FX configs
1777 for (Size i = 1; i < modelCcys_.size(); ++i) {
1778 selectedComponents.push_back(
1779 std::make_pair(CrossAssetModel::AssetType::FX, amcCam_->ccyIndex(parseCurrency(modelCcys_[i])) - 1));
1780 }
1781
1782 // EQ configs
1783 for (auto const& eq : eqIndices_) {
1784 selectedComponents.push_back(std::make_pair(CrossAssetModel::AssetType::EQ, amcCam_->eqIndex(eq.eq()->name())));
1785 }
1786
1787 // COMM configs, not supported at this point
1788 QL_REQUIRE(commIndices_.empty(), "GaussianCam model does not support commodity underlyings currently");
1789
1790 std::vector<Size> projectedStateProcessIndices;
1791 Handle<CrossAssetModel> projectedModel(
1792 getProjectedCrossAssetModel(amcCam_, selectedComponents, projectedStateProcessIndices));
1793
1794 // effective time steps per year: zero for exact evolution, otherwise the pricing engine parameter
1795 if (useCg_) {
1796 modelCG_ = QuantLib::ext::make_shared<GaussianCamCG>(
1799 projectedModel->discretization() == CrossAssetModel::Discretization::Exact ? 0 : timeStepsPerYear_,
1800 iborFallbackConfig, projectedStateProcessIndices, conditionalExpectationModelStates);
1801 } else {
1802 model_ = QuantLib::ext::make_shared<GaussianCam>(
1805 projectedModel->discretization() == CrossAssetModel::Discretization::Exact ? 0 : timeStepsPerYear_,
1806 iborFallbackConfig, projectedStateProcessIndices, conditionalExpectationModelStates);
1807 }
1808
1809 DLOG("built GuassianCam model as projection of xva evolution model");
1810 for (auto const& p : projectedStateProcessIndices)
1811 DLOG(" got projected state process index: " << p);
1812}
1813
1814void ScriptedTradeEngineBuilder::addAmcGridToContext(QuantLib::ext::shared_ptr<Context>& context) const {
1815 // the amc grid might be empty, but we add the _AMC_SimDates variable to the context anyway, since
1816 // a script might rely on its existence
1817 DLOG("adding amc date grid (" << amcGrid_.size() << ") to context as _AMC_SimDates");
1818 std::vector<ValueType> tmp;
1819 for (auto const& d : amcGrid_)
1820 tmp.push_back(EventVec{modelSize_, d});
1821 context->arrays["_AMC_SimDates"] = tmp;
1822}
1823
1825 const QuantLib::ext::shared_ptr<Context>& context) {
1826 calibrationStrikes_ = getCalibrationStrikes(script.calibrationSpec(), context);
1827}
1828
1829} // namespace data
1830} // namespace ore
ast printer
std::string script
black scholes model for n underlyings (fx, equity or commodity)
black scholes model for n underlyings (fx, equity or commodity)
builder for an array of black scholes processes
QuantLib::ext::shared_ptr< Market > market_
const string & engine() const
Return the engine name.
std::string modelParameter(const std::string &p, const std::vector< std::string > &qualifiers={}, const bool mandatory=true, const std::string &defaultValue="") const
std::string engineParameter(const std::string &p, const std::vector< std::string > &qualifiers={}, const bool mandatory=true, const std::string &defaultValue="") const
const string & configuration(const MarketContext &key)
Return a configuration (or the default one if key not found)
set< std::pair< string, QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > > > modelBuilders_
std::map< std::string, std::string > globalParameters_
string additionalField(const std::string &name, const bool mandatory=true, const std::string &defaultValue=std::string()) const
Definition: envelope.cpp:118
const FallbackData & fallbackData(const string &iborIndex) const
QuantLib::ext::shared_ptr< Index > index(const Date &obsDate=Date()) const
Definition: utilities.cpp:449
std::string infName() const
Definition: utilities.cpp:479
std::string name() const
Definition: utilities.hpp:98
bool isComm() const
Definition: utilities.hpp:102
QuantLib::ext::shared_ptr< ZeroInflationIndex > inf() const
Definition: utilities.hpp:123
bool isIr() const
Definition: utilities.hpp:103
QuantLib::ext::shared_ptr< EquityIndex2 > eq() const
Definition: utilities.hpp:110
QuantLib::ext::shared_ptr< FallbackIborIndex > irIborFallback(const IborFallbackConfig &iborFallbackConfig, const Date &asof=QuantLib::Date::maxDate()) const
Definition: utilities.cpp:501
bool isGeneric() const
Definition: utilities.hpp:107
QuantLib::ext::shared_ptr< QuantExt::CommodityIndex > comm(const Date &obsDate=Date()) const
Definition: utilities.cpp:467
bool isFx() const
Definition: utilities.hpp:100
QuantLib::ext::shared_ptr< FxIndex > fx() const
Definition: utilities.hpp:109
bool isEq() const
Definition: utilities.hpp:101
QuantLib::ext::shared_ptr< FallbackOvernightIndex > irOvernightFallback(const IborFallbackConfig &iborFallbackConfig, const Date &asof=QuantLib::Date::maxDate()) const
Definition: utilities.cpp:514
std::string commName() const
Definition: utilities.cpp:474
bool isInf() const
Definition: utilities.hpp:106
@ Hagan
Parametrize LGM H(t) as H(t) = int_0^t h(s) ds with constant or piecewise h(s)
@ Hagan
Parametrize volatility as Hagan alpha(t)
void mult(const Real f)
void setCalibrate(const bool b)
std::map< std::string, Real > irReversions_
QuantLib::ext::shared_ptr< StaticAnalyser > staticAnalyser_
void buildLocalVol(const std::string &id, const IborFallbackConfig &iborFallbackConfig)
std::vector< std::string > modelIndices_
void buildFdGaussianCam(const std::string &id, const IborFallbackConfig &iborFallbackConfig)
void buildAMCCGModel(const std::string &id, const IborFallbackConfig &iborFallbackConfig, const std::vector< std::string > &conditionalExpectationModelStates)
void buildFdBlackScholes(const std::string &id, const IborFallbackConfig &iborFallbackConfig)
void extractIndices(const QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > &referenceData=nullptr)
std::vector< std::string > modelIndicesCurrencies_
const std::map< std::string, std::set< Date > > & fixings() const
std::map< std::pair< std::string, std::string >, Handle< QuantExt::CorrelationTermStructure > > correlations_
std::vector< Handle< Quote > > modelFxSpots_
std::map< std::string, std::set< Date > > fixings_
const QuantLib::ext::shared_ptr< QuantExt::CrossAssetModel > amcCam_
const QuantLib::ext::shared_ptr< ore::data::ModelCG > amcCgModel_
QuantLib::ext::shared_ptr< Model > model_
void buildGaussianCam(const std::string &id, const IborFallbackConfig &iborFallbackConfig, const std::vector< std::string > &conditionalExpectationModelStates)
std::vector< std::string > modelCcys_
void buildGaussianCamAMC(const std::string &id, const IborFallbackConfig &iborFallbackConfig, const std::vector< std::string > &conditionalExpectationModelStates)
const std::vector< Date > amcGrid_
void setupCalibrationStrikes(const ScriptedTradeScriptData &script, const QuantLib::ext::shared_ptr< Context > &context)
QuantLib::ext::shared_ptr< ModelCG > modelCG_
void addAmcGridToContext(QuantLib::ext::shared_ptr< Context > &context) const
std::vector< std::pair< std::string, QuantLib::ext::shared_ptr< ZeroInflationIndex > > > modelInfIndices_
void buildBlackScholes(const std::string &id, const IborFallbackConfig &iborFallbackConfig)
std::string getCommCcy(const IndexInfo &e)
std::map< std::string, std::vector< Real > > calibrationStrikes_
std::vector< Handle< YieldTermStructure > > modelCurves_
void populateFixingsMap(const IborFallbackConfig &iborFallbackConfig)
std::vector< std::pair< std::string, QuantLib::ext::shared_ptr< InterestRateIndex > > > modelIrIndices_
void deriveProductClass(const std::vector< ScriptedTradeValueTypeData > &indices)
std::map< std::string, ASTNodePtr > astCache_
virtual QuantLib::Handle< QuantExt::CorrelationTermStructure > correlationCurve(const std::string &index1, const std::string &index2)
std::string getEqCcy(const IndexInfo &e)
std::vector< QuantLib::ext::shared_ptr< GeneralizedBlackScholesProcess > > processes_
const std::vector< ScriptedTradeValueTypeData > & indices() const
const std::vector< ScriptedTradeValueTypeData > & daycounters() const
const std::vector< ScriptedTradeValueTypeData > & numbers() const
const std::vector< ScriptedTradeValueTypeData > & currencies() const
const std::vector< ScriptedTradeEventData > & events() const
const Envelope & envelope() const
Definition: trade.hpp:135
script engine context holding variable names and values
black scholes fd model base class for n underlyings (fx, equity or commodity)
fd gaussian cross asset model for single underlying ir model
gaussian cross asset model for ir, fx, eq, com
Gaussian CAM model.
SequenceType parseSequenceType(const std::string &s)
Convert string to sequence type.
Definition: parsers.cpp:668
QuantLib::LsmBasisSystem::PolynomialType parsePolynomType(const std::string &s)
Convert text to QuantLib::LsmBasisSystem::PolynomialType.
Definition: parsers.cpp:527
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
bool parseBool(const string &s)
Convert text to bool.
Definition: parsers.cpp:144
SobolRsg::DirectionIntegers parseSobolRsgDirectionIntegers(const std::string &s)
Convert text to QuantLib::SobolRsg::DirectionIntegers.
Definition: parsers.cpp:579
Real parseRealOrNull(const string &s)
Convert text to Real, empty string to Null<Real>()
Definition: parsers.cpp:120
bool isPseudoCurrency(const string &code)
check for pseudo currency = precious metal or crypto currency *‍/
Definition: parsers.cpp:318
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
SobolBrownianGenerator::Ordering parseSobolBrownianGeneratorOrdering(const std::string &s)
Convert text to QuantLib::SobolBrownianGenerator::Ordering.
Definition: parsers.cpp:567
Integer parseInteger(const string &s)
Convert text to QuantLib::Integer.
Definition: parsers.cpp:136
translates between QuantLib::Index::name() and ORE names
IR component data for the cross asset model.
local vol model for n underlyings (fx, equity or commodity)
builder for an array of local vol processes
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define DLOGGERSTREAM(text)
Definition: log.hpp:632
Classes for representing a strike using various conventions.
market data related utilties
class for holding details of a zero coupon CPI cap floor calibration instrument.
Date referenceDate
Definition: utilities.cpp:442
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
boost::shared_ptr< CrossAssetModel > getProjectedCrossAssetModel(const boost::shared_ptr< CrossAssetModel > &model, const std::vector< std::pair< CrossAssetModel::AssetType, Size > > &selectedComponents, std::vector< Size > &projectedStateProcessIndices)
CorrelationFactor parseCorrelationFactor(const string &name, const char separator)
ASTNodePtr parseScript(const std::string &code)
Definition: utilities.cpp:121
Handle< YieldTermStructure > indexOrYieldCurve(const QuantLib::ext::shared_ptr< Market > &market, const std::string &name, const std::string &configuration)
Definition: marketdata.cpp:65
void addNewSchedulesToContext(QuantLib::ext::shared_ptr< Context > context, const std::vector< ScriptedTradeScriptData::NewScheduleData > &newSchedules)
Definition: utilities.cpp:322
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
std::map< std::string, std::vector< Real > > getCalibrationStrikes(const std::vector< ScriptedTradeScriptData::CalibrationData > &calibrationSpec, const QuantLib::ext::shared_ptr< Context > &context)
Definition: utilities.cpp:673
void checkDuplicateName(const QuantLib::ext::shared_ptr< Context > context, const std::string &name)
Definition: utilities.cpp:156
Size getInflationSimulationLag(const QuantLib::ext::shared_ptr< ZeroInflationIndex > &index)
Definition: utilities.cpp:660
std::pair< std::string, ScriptedTradeScriptData > getScript(const ScriptedTrade &scriptedTrade, const ScriptLibraryData &scriptLibrary, const std::string &purpose, const bool fallBackOnEmptyPurpose)
Definition: utilities.cpp:105
std::pair< std::string, Period > convertIndexToCamCorrelationEntry(const std::string &i)
Definition: utilities.cpp:138
QuantLib::ext::shared_ptr< Context > makeContext(Size nPaths, const std::string &gridCoarsening, const std::vector< std::string > &schedulesEligibleForCoarsening, const QuantLib::ext::shared_ptr< ReferenceDataManager > &referenceData, const std::vector< ScriptedTradeEventData > &events, const std::vector< ScriptedTradeValueTypeData > &numbers, const std::vector< ScriptedTradeValueTypeData > &indices, const std::vector< ScriptedTradeValueTypeData > &currencies, const std::vector< ScriptedTradeValueTypeData > &daycounters)
Definition: utilities.cpp:163
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Reference data model and serialization.
trade schedule data model and serialization
scripted instrument
scripted instrument pricing engine
scripted instrument pricing engine using a cg model
script parser
QuantExt::CrossAssetModel::AssetType type
bool externalDeviceCompatibilityMode
Definition: model.hpp:56
QuantExt::SequenceType sequenceType
Definition: model.hpp:54
QuantLib::SobolBrownianGenerator::Ordering sobolOrdering
Definition: model.hpp:59
QuantExt::SequenceType trainingSequenceType
Definition: model.hpp:55
QuantLib::SobolRsg::DirectionIntegers sobolDirectionIntegers
Definition: model.hpp:60
QuantLib::Real regressionVarianceCutoff
Definition: model.hpp:61
QuantLib::LsmBasisSystem::PolynomialType polynomType
Definition: model.hpp:58
vector< Real > strikes
string conversion utilities
string name