Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
scriptedinstrumentpricingenginecg.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 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
22
25#include <qle/ad/ssaform.hpp>
27
29
33
34#include <boost/accumulators/accumulators.hpp>
35#include <boost/accumulators/statistics/mean.hpp>
36#include <boost/accumulators/statistics/stats.hpp>
37#include <boost/accumulators/statistics/variance.hpp>
38
39namespace ore {
40namespace data {
41
42namespace {
43
44// helper that converts a context value to a ql additional result (i.e. boost::any)
45
46struct anyGetterCg : public boost::static_visitor<boost::any> {
47 boost::any operator()(const RandomVariable& x) const { QL_FAIL("unexpected call to anyGetter (RandomVariable)"); }
48 boost::any operator()(const EventVec& x) const { return x.value; }
49 boost::any operator()(const IndexVec& x) const { return x.value; }
50 boost::any operator()(const CurrencyVec& x) const { return x.value; }
51 boost::any operator()(const DaycounterVec& x) const { return x.value; }
52 boost::any operator()(const Filter& x) const { QL_FAIL("unexpected call to anyGetter (Filter)"); }
53};
54
55boost::any valueToAnyCg(const ValueType& v) { return boost::apply_visitor(anyGetterCg(), v); }
56
57double externalAverage(const std::vector<double>& v) {
58 boost::accumulators::accumulator_set<double, boost::accumulators::stats<boost::accumulators::tag::mean>> acc;
59 for (auto const& x : v) {
60 acc(x);
61 }
62 return boost::accumulators::mean(acc);
63}
64
65} // namespace
66
69 ComputeEnvironment::instance().context().disposeCalculation(externalCalculationId_);
70}
71
73 const std::string& npv, const std::vector<std::pair<std::string, std::string>>& additionalResults,
74 const QuantLib::ext::shared_ptr<ModelCG>& model, const ASTNodePtr ast,
75 const QuantLib::ext::shared_ptr<Context>& context, const Model::McParams& mcParams, const std::string& script,
76 const bool interactive, const bool generateAdditionalResults, const bool includePastCashflows,
77 const bool useCachedSensis, const bool useExternalComputeFramework,
78 const bool useDoublePrecisionForExternalCalculation)
79 : npv_(npv), additionalResults_(additionalResults), model_(model), ast_(ast), context_(context),
80 mcParams_(mcParams), script_(script), interactive_(interactive),
81 generateAdditionalResults_(generateAdditionalResults), includePastCashflows_(includePastCashflows),
82 useCachedSensis_(useCachedSensis), useExternalComputeFramework_(useExternalComputeFramework),
83 useDoublePrecisionForExternalCalculation_(useDoublePrecisionForExternalCalculation) {
84
85 // register with model
86
87 registerWith(model_);
88
89 // get ops + labels, grads and op node requirements
90
95 } else {
100 }
101}
102
104
105 if (cgVersion_ != model_->cgVersion()) {
106
107 auto g = model_->computationGraph();
108
109 // clear NPVMem() regression coefficients
110
111 model_->resetNPVMem();
112
113 // set up copy of initial context to run the cg builder against
114
115 workingContext_ = QuantLib::ext::make_shared<Context>(*context_);
116
117 // set TODAY in the context
118
120 Date referenceDate = model_->referenceDate();
121 workingContext_->scalars["TODAY"] = EventVec{model_->size(), referenceDate};
122 workingContext_->constants.insert("TODAY");
123
124 // set variables from initial context
125
126 for (auto const& v : workingContext_->scalars) {
127 if (v.second.which() == ValueTypeWhich::Number) {
128 auto r = QuantLib::ext::get<RandomVariable>(v.second);
129 QL_REQUIRE(r.deterministic(), "ScriptedInstrumentPricingEngineCG::calculate(): expected variable '"
130 << v.first << "' from initial context to be deterministic, got "
131 << r);
132 g->setVariable(v.first + "_0", cg_const(*g, r.at(0)));
133 }
134 }
135
136 for (auto const& a : workingContext_->arrays) {
137 for (Size i = 0; i < a.second.size(); ++i) {
138 if (a.second[i].which() == ValueTypeWhich::Number) {
139 auto r = QuantLib::ext::get<RandomVariable>(a.second[i]);
140 QL_REQUIRE(r.deterministic(), "ScriptedInstrumentPricingEngineCG::calculate(): expected variable '"
141 << a.first << "[" << i
142 << "]' from initial context to be deterministic, got " << r);
143 g->setVariable(a.first + "_" + std::to_string(i), cg_const(*g, r.at(0)));
144 }
145 }
146 }
147
148 // build graph
149
152 cgVersion_ = model_->cgVersion();
153 DLOG("Built computation graph version " << cgVersion_ << " size is " << g->size());
155 keepNodes_ = cgBuilder.keepNodes();
156 payLogEntries_ = cgBuilder.payLogEntries();
157
158 // clear stored base model params
159
160 haveBaseValues_ = false;
161 }
162}
163
165
166 // TODOs
168 "ScriptedInstrumentPricingEngineCG: when using external compute framework, generation of additional "
169 "results is not supported yet.");
171 "ScriptedInstrumentPricingEngineCG: when using external compute framework, usage of cached sensis is "
172 "not supported yet");
173 QL_REQUIRE(model_->trainingSamples() == Null<Size>(), "ScriptedInstrumentPricingEngineCG: separate training phase "
174 "not supported, trainingSamples can not be specified.");
175
177
179
181
182 // calculate NPV and Sensis ("base scenario"), store base npv + sensis + base model params
183
184 auto g = model_->computationGraph();
185
186 bool newExternalCalc = false;
188 QL_REQUIRE(ComputeEnvironment::instance().hasContext(),
189 "ScriptedInstrumentPricingEngineCG::calculate(): no compute enviroment context selected.");
190 ComputeContext::Settings settings;
191 settings.debug = false;
192 settings.useDoublePrecision = useDoublePrecisionForExternalCalculation_;
193 settings.rngSequenceType = mcParams_.sequenceType;
194 settings.rngSeed = mcParams_.seed;
195 settings.regressionOrder = mcParams_.regressionOrder;
196 std::tie(externalCalculationId_, newExternalCalc) =
197 ComputeEnvironment::instance().context().initiateCalculation(model_->size(), externalCalculationId_,
198 cgVersion_, settings);
199 DLOG("initiated external calculation id " << externalCalculationId_ << ", version " << cgVersion_);
200 }
201
202 // populate values
203
204 std::vector<RandomVariable> values(g->size(), RandomVariable(model_->size()));
205
206 std::vector<ExternalRandomVariable> valuesExternal;
208 valuesExternal.resize(g->size());
209
210 // set constants
211
212 for (auto const& c : g->constants()) {
214 valuesExternal[c.second] = ExternalRandomVariable(c.first);
215 } else {
216 values[c.second] = RandomVariable(model_->size(), c.first);
217 }
218 }
219
220 // set model parameters
221
222 baseModelParams_ = model_->modelParameters();
223 for (auto const& p : baseModelParams_) {
224 TLOG("setting model parameter at node " << p.first << " to value " << std::setprecision(16) << p.second);
226 valuesExternal[p.first] = ExternalRandomVariable(p.second);
227 } else {
228 values[p.first] = RandomVariable(model_->size(), p.second);
229 }
230 }
231 DLOG("set " << baseModelParams_.size() << " model parameters");
232
233 // set random variates
234
235 auto const& rv = model_->randomVariates();
236 if (!rv.empty()) {
238 if (newExternalCalc) {
239 auto gen =
240 ComputeEnvironment::instance().context().createInputVariates(rv.size(), rv.front().size());
241 for (Size k = 0; k < rv.size(); ++k) {
242 for (Size j = 0; j < rv.front().size(); ++j)
243 valuesExternal[rv[k][j]] = ExternalRandomVariable(gen[k][j]);
244 }
245 }
246 } else {
247 if (mcParams_.sequenceType == QuantExt::SequenceType::MersenneTwister &&
249 // use same order for rng generation as it is (usually) done on external devices
250 // this is mainly done to be able to reconcile results produced on external devices
251 auto rng = std::make_unique<MersenneTwisterUniformRng>(mcParams_.seed);
252 QuantLib::InverseCumulativeNormal icn;
253 for (Size j = 0; j < rv.front().size(); ++j) {
254 for (Size i = 0; i < rv.size(); ++i) {
255 for (Size path = 0; path < model_->size(); ++path) {
256 values[rv[i][j]].set(path, icn(rng->nextReal()));
257 }
258 }
259 }
260 } else {
261 // use the 'usual' path generation that we also use elsewhere
262 auto gen = makeMultiPathVariateGenerator(mcParams_.sequenceType, rv.size(), rv.front().size(),
265 for (Size path = 0; path < model_->size(); ++path) {
266 auto p = gen->next();
267 for (Size j = 0; j < rv.front().size(); ++j) {
268 for (Size k = 0; k < rv.size(); ++k) {
269 values[rv[k][j]].set(path, p.value[j][k]);
270 }
271 }
272 }
273 }
274 }
275 DLOG("generated random variates for dim = " << rv.size() << ", steps = " << rv.front().size());
276 }
277
278 // set flags for nodes we want to keep (model params, npv and additional results)
279
280 std::vector<bool> keepNodes(g->size(), false);
281
282 keepNodes[cg_var(*g, npv_ + "_0")] = true;
283
284 for (auto const& p : baseModelParams_)
285 keepNodes[p.first] = true;
286
288 for (auto const& r : additionalResults_) {
289 auto s = workingContext_->scalars.find(r.second);
290 if (s != workingContext_->scalars.end() && s->second.which() == ValueTypeWhich::Number) {
291 keepNodes[cg_var(*g, r.second + "_0")] = true;
292 }
293 auto v = workingContext_->arrays.find(r.second);
294 if (v != workingContext_->arrays.end()) {
295 for (Size i = 0; i < v->second.size(); ++i) {
296 keepNodes[cg_var(*g, r.second + "_" + std::to_string(i))] = true;
297 }
298 }
299 }
300 for (auto const& n : keepNodes_)
301 keepNodes[n] = true;
302 }
303
304 // run the forward evaluation
305
306 std::size_t baseNpvNode = cg_var(*g, npv_ + "_0");
307
309 if (newExternalCalc) {
311 opNodeRequirements_, keepNodes);
312 valuesExternal[baseNpvNode].declareAsOutput();
313 externalOutput_ = std::vector<std::vector<double>>(1, std::vector<double>(model_->size()));
314 externalOutputPtr_ = std::vector<double*>(1, &externalOutput_.front()[0]);
315 DLOG("ran forward evaluation");
316 }
317 } else {
319 keepNodes);
320 DLOG("ran forward evaluation");
321 }
322
324
325 // extract npv result and set it
326
328 ComputeEnvironment::instance().context().finalizeCalculation(externalOutputPtr_);
329 baseNpv_ = results_.value = externalAverage(externalOutput_[0]);
330 } else {
331 baseNpv_ = results_.value = model_->extractT0Result(values[baseNpvNode]);
332 }
333
334 DLOG("got NPV = " << results_.value << " " << model_->baseCcy());
335
336 // extract additional results
337
339
341
342 for (auto const& r : additionalResults_) {
343
344 auto s = workingContext_->scalars.find(r.second);
345 bool resultSet = false;
346 if (s != workingContext_->scalars.end()) {
347 if (s->second.which() == ValueTypeWhich::Number) {
349 model_->extractT0Result(values[cg_var(*g, r.second + "_0")]);
350 } else {
351 boost::any t = valueToAnyCg(s->second);
352 instrumentAdditionalResults_[r.first] = t;
353 }
354 DLOG("got additional result '" << r.first << "' referencing script variable '" << r.second << "'");
355 resultSet = true;
356 }
357 auto v = workingContext_->arrays.find(r.second);
358 if (v != workingContext_->arrays.end()) {
359 QL_REQUIRE(!resultSet, "result variable '"
360 << r.first << "' referencing script variable '" << r.second
361 << "' appears both as a scalar and an array, this is unexpected");
362 QL_REQUIRE(!v->second.empty(), "result variable '" << v->first << "' is an empty array.");
363 std::vector<double> tmpdouble;
364 std::vector<std::string> tmpstring;
365 std::vector<QuantLib::Date> tmpdate;
366 for (Size i = 0; i < v->second.size(); ++i) {
367 if (v->second[i].which() == ValueTypeWhich::Number) {
368 tmpdouble.push_back(
369 model_->extractT0Result(values[cg_var(*g, r.second + "_" + std::to_string(i))]));
370 } else {
371 boost::any t = valueToAnyCg(v->second[i]);
372 if (t.type() == typeid(std::string))
373 tmpstring.push_back(boost::any_cast<std::string>(t));
374 else if (t.type() == typeid(QuantLib::Date))
375 tmpdate.push_back(boost::any_cast<QuantLib::Date>(t));
376 else {
377 QL_FAIL("unexpected result type '" << t.type().name() << "' for result variable '"
378 << r.first << "' referencing script variable '"
379 << r.second << "'");
380 }
381 }
382 }
383 QL_REQUIRE((int)!tmpdouble.empty() + (int)!tmpstring.empty() + (int)!tmpdate.empty() == 1,
384 "expected exactly one result type in result array '" << v->first << "'");
385 DLOG("got additional result '" << r.first << "' referencing script variable '" << r.second
386 << "' vector of size "
387 << tmpdouble.size() + tmpstring.size() + tmpdate.size());
388 if (!tmpdouble.empty())
389 instrumentAdditionalResults_[r.first] = tmpdouble;
390 else if (!tmpstring.empty())
391 instrumentAdditionalResults_[r.first] = tmpstring;
392 else if (!tmpdate.empty())
393 instrumentAdditionalResults_[r.first] = tmpdate;
394 else {
395 QL_FAIL("got empty result vector for result variable '"
396 << r.first << "' referencing script variable '" << r.second << "', this is unexpected");
397 }
398 resultSet = true;
399 }
400 QL_REQUIRE(resultSet, "could not set additional result '"
401 << r.first << "' referencing script variable '" << r.second << "'");
402 }
403
404 // set contents from paylog as additional results
405
406 auto paylog = QuantLib::ext::make_shared<PayLog>();
407 for (auto const& p : payLogEntries_) {
408 paylog->write(values[p.value],
409 !close_enough(values[p.filter], RandomVariable(values[p.filter].size(), 0.0)), p.obs,
410 p.pay, p.ccy, p.legNo, p.cashflowType, p.slot);
411 }
412
413 paylog->consolidateAndSort();
414 std::vector<CashFlowResults> cashFlowResults(paylog->size());
415 for (Size i = 0; i < paylog->size(); ++i) {
416 // cashflow is written as expectation of deflated base ccy amount at T0, converted to flow ccy
417 // with the T0 FX Spot and compounded back to the pay date on T0 curves
418 Real fx = 1.0;
419 Real discount = 1.0;
420 if (paylog->dates().at(i) > model_->referenceDate()) {
421 fx = model_->getDirectFxSpotT0(paylog->currencies().at(i), model_->baseCcy());
422 discount = model_->getDirectDiscountT0(paylog->dates().at(i), paylog->currencies().at(i));
423 }
424 cashFlowResults[i].amount = model_->extractT0Result(paylog->amounts().at(i)) / fx / discount;
425 cashFlowResults[i].payDate = paylog->dates().at(i);
426 cashFlowResults[i].currency = paylog->currencies().at(i);
427 cashFlowResults[i].legNumber = paylog->legNos().at(i);
428 cashFlowResults[i].type = paylog->cashflowTypes().at(i);
429 DLOG("got cashflow " << QuantLib::io::iso_date(cashFlowResults[i].payDate) << " "
430 << cashFlowResults[i].currency << cashFlowResults[i].amount << " "
431 << cashFlowResults[i].currency << "-" << model_->baseCcy() << " " << fx
432 << "discount(" << cashFlowResults[i].currency << ") " << discount);
433 }
434 instrumentAdditionalResults_["cashFlowResults"] = cashFlowResults;
435
436 // set additional results from the model
437
438 instrumentAdditionalResults_.insert(model_->additionalResults().begin(), model_->additionalResults().end());
439
440 } // if generate additional results
441
442 if (useCachedSensis_) {
443
444 // extract sensis and store them
445
446 std::vector<RandomVariable> derivatives(g->size(), RandomVariable(model_->size(), 0.0));
447 derivatives[cg_var(*g, npv_ + "_0")] = RandomVariable(model_->size(), 1.0);
448 backwardDerivatives(*g, values, derivatives, grads_, RandomVariable::deleter, keepNodes);
449
450 sensis_.resize(baseModelParams_.size());
451 for (Size i = 0; i < baseModelParams_.size(); ++i) {
452 sensis_[i] = model_->extractT0Result(derivatives[baseModelParams_[i].first]);
453 }
454 DLOG("got backward sensitivities");
455
456 // set flag indicating that we can use cached sensis in subsequent calculations
457
458 haveBaseValues_ = true;
459 }
460
461 } else {
462
463 // useCachedSensis => calculate npv from stored base npv, sensis, model params
464
465 auto modelParams = model_->modelParameters();
466
467 double npv = baseNpv_;
468 DLOG("computing npv using baseNpv " << baseNpv_ << " and sensis.");
469
470 for (Size i = 0; i < baseModelParams_.size(); ++i) {
471 QL_REQUIRE(modelParams[i].first == baseModelParams_[i].first, "internal error: modelParams["
472 << i << "] node " << modelParams[i].first
473 << " does not match baseModelParams node "
474 << baseModelParams_[i].first);
475 Real tmp = sensis_[i] * (modelParams[i].second - baseModelParams_[i].second);
476 npv += tmp;
477 DLOG("node " << modelParams[i].first << ": [" << modelParams[i].second << " (current) - "
478 << baseModelParams_[i].second << " (base) ] * " << sensis_[i] << " (delta) => " << tmp);
479 }
480
481 results_.value = npv;
482 }
483
485 results_.additionalResults = instrumentAdditionalResults_;
486 }
487
489}
490
491} // namespace data
492} // namespace ore
std::string script
const Instrument::results * results_
static std::function< void(ExternalRandomVariable &)> deleter
const std::set< std::size_t > & keepNodes() const
const std::vector< PayLogEntry > & payLogEntries() const
void run(const bool generatePayLog, const bool includePastCashflows=false, const std::string &script="", bool interactive=false)
std::vector< ExternalRandomVariableGrad > gradsExternal_
std::vector< ComputationGraphBuilder::PayLogEntry > payLogEntries_
std::vector< std::pair< std::size_t, double > > baseModelParams_
std::vector< RandomVariableOpNodeRequirements > opNodeRequirements_
const std::vector< std::pair< std::string, std::string > > additionalResults_
std::map< std::string, boost::any > instrumentAdditionalResults_
ScriptedInstrumentPricingEngineCG(const std::string &npv, const std::vector< std::pair< std::string, std::string > > &additionalResults, const QuantLib::ext::shared_ptr< ModelCG > &model, const ASTNodePtr ast, const QuantLib::ext::shared_ptr< Context > &context, const Model::McParams &mcParams, const std::string &script="", const bool interactive=false, const bool generateAdditionalResults=false, const bool includePastCashflows=false, const bool useCachedSensis=false, const bool useExternalComputeFramework=false, const bool useDoublePrecisionForExternalCalculation=false)
bool & interactive_
const std::string script_
Context & context_
const QuantLib::ext::shared_ptr< ModelCG > model_
const bool includePastCashflows_
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define TLOGGERSTREAM(text)
Definition: log.hpp:633
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
Date referenceDate
Definition: utilities.cpp:442
std::size_t cg_const(ComputationGraph &g, const double value)
void forwardEvaluation(const ComputationGraph &g, std::vector< T > &values, const std::vector< std::function< T(const std::vector< const T * > &)> > &ops, std::function< void(T &)> deleter={}, bool keepValuesForDerivatives=true, const std::vector< std::function< std::pair< std::vector< bool >, bool >(const std::size_t)> > &opRequiresNodesForDerivatives={}, const std::vector< bool > &keepNodes={})
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::vector< ExternalRandomVariableOp > getExternalRandomVariableOps()
std::string ssaForm(const ComputationGraph &g, const std::vector< std::string > &opCodeLabels, const std::vector< T > &values)
std::vector< RandomVariableOpNodeRequirements > getRandomVariableOpNodeRequirements()
boost::shared_ptr< MultiPathVariateGeneratorBase > makeMultiPathVariateGenerator(const SequenceType s, const Size dimension, const Size timeSteps, const BigNatural seed, const SobolBrownianGenerator::Ordering ordering, const SobolRsg::DirectionIntegers directionIntegers)
std::vector< ExternalRandomVariableGrad > getExternalRandomVariableGradients()
std::size_t cg_var(ComputationGraph &g, const std::string &name, const bool createIfNotExists)
std::vector< std::string > getRandomVariableOpLabels()
std::vector< RandomVariableOp > getRandomVariableOps(const Size size, const std::map< Size, std::vector< std::function< RandomVariable(const std::vector< const RandomVariable * > &)> > > &basisFn)
std::vector< RandomVariableGrad > getRandomVariableGradients(const Size size, const double eps, const std::vector< std::function< RandomVariable(const std::vector< const RandomVariable * > &)> > &basisFn)
void backwardDerivatives(const ComputationGraph &g, const std::vector< T > &values, std::vector< T > &derivatives, const std::vector< std::function< std::vector< T >(const std::vector< const T * > &, const T *)> > &grad, std::function< void(T &)> deleter={}, const std::vector< bool > &keepNodes={})
Size size(const ValueType &v)
Definition: value.cpp:145
void checkDuplicateName(const QuantLib::ext::shared_ptr< Context > context, const std::string &name)
Definition: utilities.cpp:156
boost::variant< RandomVariable, EventVec, CurrencyVec, IndexVec, DaycounterVec, Filter > ValueType
Definition: value.hpp:60
QuantLib::ext::shared_ptr< ASTNode > ASTNodePtr
Definition: ast.hpp:46
Serializable Credit Default Swap.
Definition: namespaces.docs:23
amc calculator for scripted trades
scripted instrument pricing engine using a cg model
some utility functions
static std::function< void(RandomVariable &)> deleter
bool externalDeviceCompatibilityMode
Definition: model.hpp:56
QuantExt::SequenceType sequenceType
Definition: model.hpp:54
QuantLib::SobolBrownianGenerator::Ordering sobolOrdering
Definition: model.hpp:59
QuantLib::SobolRsg::DirectionIntegers sobolDirectionIntegers
Definition: model.hpp:60
QuantLib::Real regressionVarianceCutoff
Definition: model.hpp:61
QuantLib::LsmBasisSystem::PolynomialType polynomType
Definition: model.hpp:58