Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
amcvaluationengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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
20
24
31
38
39#include <ql/instruments/compositeinstrument.hpp>
40
42
43#include <boost/timer/timer.hpp>
44
45#include <future>
46
47using namespace ore::data;
48using namespace ore::analytics;
49
50namespace ore {
51namespace analytics {
52
53namespace {
54
55Real fx(const std::vector<std::vector<std::vector<Real>>>& fxBuffer, const Size ccyIndex, const Size timeIndex,
56 const Size sample) {
57 if (ccyIndex == 0)
58 return 1.0;
59 return fxBuffer[ccyIndex - 1][timeIndex][sample];
60}
61
62Real state(const std::vector<std::vector<std::vector<Real>>>& irStateBuffer, const Size ccyIndex, const Size timeIndex,
63 const Size sample) {
64 return irStateBuffer[ccyIndex][timeIndex][sample];
65}
66
67Real numRatio(const QuantLib::ext::shared_ptr<CrossAssetModel>& model,
68 const std::vector<std::vector<std::vector<Real>>>& irStateBuffer, const Size ccyIndex,
69 const Size timeIndex, const Real time, const Size sample) {
70 if (ccyIndex == 0)
71 return 1.0;
72 Real state_base = state(irStateBuffer, 0, timeIndex, sample);
73 Real state_curr = state(irStateBuffer, ccyIndex, timeIndex, sample);
74 return model->numeraire(ccyIndex, time, state_curr) / model->numeraire(0, time, state_base);
75}
76
77Real num(const QuantLib::ext::shared_ptr<CrossAssetModel>& model,
78 const std::vector<std::vector<std::vector<Real>>>& irStateBuffer, const Size ccyIndex, const Size timeIndex,
79 const Real time, const Size sample) {
80 Real state_curr = state(irStateBuffer, ccyIndex, timeIndex, sample);
81 return model->numeraire(ccyIndex, time, state_curr);
82}
83
84Real discount(const QuantLib::ext::shared_ptr<CrossAssetModel>& model,
85 const std::vector<std::vector<std::vector<Real>>>& irStateBuffer, const Size ccyIndex,
86 const Size timeIndex, const Real t, const Real T, const Size sample) {
87 Real state_curr = state(irStateBuffer, ccyIndex, timeIndex, sample);
88 return model->discountBond(ccyIndex, t, T, state_curr);
89}
90
91std::vector<QuantExt::RandomVariable>
92simulatePathInterface2(const QuantLib::ext::shared_ptr<AmcCalculator>& amcCalc, const std::vector<Real>& pathTimes,
93 std::vector<std::vector<RandomVariable>>& paths, const std::vector<size_t>& pathIdx,
94 const std::vector<size_t>& timeIdx,
95 const std::string& tradeLabel,
96 const std::string& tradeType) {
97 QL_REQUIRE(pathIdx.size() == timeIdx.size(),
98 "internal error, mismatch between relevant path idx and timegrid idx, please contact dev");
99 try {
100 return amcCalc->simulatePath(pathTimes, paths, pathIdx, timeIdx);
101 } catch (const std::exception& e) {
102 StructuredTradeErrorMessage(tradeLabel, tradeType, "error during amc path simulation for trade.", e.what())
103 .log();
104 return std::vector<QuantExt::RandomVariable>(pathIdx.size() + 1,
105 RandomVariable(paths.front().front().size()));
106 }
107}
108
109std::vector<QuantExt::RandomVariable>
110feeContributions(const Size j, const QuantLib::ext::shared_ptr<ScenarioGeneratorData>& sgd, const Date& asof,
111 const Size samples, const std::vector<std::vector<std::tuple<Size, Real, QuantLib::Date>>>& tradeFees,
112 const QuantLib::ext::shared_ptr<CrossAssetModel>& model,
113 const std::vector<std::vector<std::vector<Real>>>& fxBuffer,
114 const std::vector<std::vector<std::vector<Real>>>& irStateBuffer) {
115 std::vector<QuantExt::RandomVariable> result;
116 for (Size k = 0; k < sgd->getGrid()->timeGrid().size(); ++k) {
117 Date simDate = k == 0 ? asof : sgd->getGrid()->dates()[k - 1];
118 // slight approximation: we treat premiums as seen from the closeout date the same as if priced from the
119 // the valuation date in sticky date mode with mpor grid
120 if (k == 0 || !sgd->withCloseOutLag() || !sgd->withMporStickyDate() ||
121 sgd->getGrid()->isValuationDate()[k - 1]) {
122 result.push_back(RandomVariable(samples, 0.0));
123 if (tradeFees[j].empty())
124 continue;
125 for (Size i = 0; i < samples; ++i) {
126 Real tmp = 0.0;
127 for (Size f = 0; f < tradeFees[j].size(); ++f) {
128 if (std::get<2>(tradeFees[j][f]) > simDate) {
129 Real t = sgd->getGrid()->timeGrid()[k];
130 Real T = model->irModel(0)->termStructure()->timeFromReference(std::get<2>(tradeFees[j][f]));
131 tmp += std::get<1>(tradeFees[j][f]) * fx(fxBuffer, std::get<0>(tradeFees[j][f]), k, i) *
132 discount(model, irStateBuffer, std::get<0>(tradeFees[j][f]), k, t, T, i) *
133 num(model, irStateBuffer, 0, k, t, i);
134 }
135 }
136 result.back().set(i, tmp);
137 }
138 }
139 }
140 return result;
141}
142
143void runCoreEngine(const QuantLib::ext::shared_ptr<ore::data::Portfolio>& portfolio,
144 const QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel>& model,
145 const QuantLib::ext::shared_ptr<ore::data::Market>& market,
146 const QuantLib::ext::shared_ptr<ore::analytics::ScenarioGeneratorData>& sgd,
147 const std::vector<string>& aggDataIndices, const std::vector<string>& aggDataCurrencies,
148 const Size aggDataNumberCreditStates, QuantLib::ext::shared_ptr<ore::analytics::AggregationScenarioData> asd,
149 QuantLib::ext::shared_ptr<NPVCube> outputCube, QuantLib::ext::shared_ptr<ProgressIndicator> progressIndicator) {
150
151 std::ostringstream detail;
152 detail << portfolio->size() << " trade" << (portfolio->size() == 1 ? "" : "s");
153 progressIndicator->updateProgress(0, portfolio->size(), detail.str());
154
155 // base currency is the base currency of the cam
156
157 Currency baseCurrency = model->irlgm1f(0)->currency();
158
159 // timings
160
161 boost::timer::cpu_timer timer, timerTotal;
162 Real calibrationTime = 0.0, valuationTime = 0.0, asdTime = 0.0, bufferTime = 0.0, pathGenTime = 0.0, residualTime,
163 totalTime;
164 timerTotal.start();
165
166 // prepare for asd writing
167
168 std::vector<Size> asdCurrencyIndex; // FX Spots
169 std::vector<string> asdCurrencyCode;
170 std::vector<QuantLib::ext::shared_ptr<LgmImpliedYtsFwdFwdCorrected>> asdIndexCurve; // Ibor Indices
171 std::vector<QuantLib::ext::shared_ptr<Index>> asdIndex;
172 std::vector<Size> asdIndexIndex;
173 std::vector<string> asdIndexName;
174 if (asd != nullptr) {
175 LOG("Collect information for aggregation scenario data...");
176 // fx spots
177 for (auto const& c : aggDataCurrencies) {
178 Currency cur = parseCurrency(c);
179 if (cur == baseCurrency)
180 continue;
181 Size ccyIndex = model->ccyIndex(cur);
182 asdCurrencyIndex.push_back(ccyIndex);
183 asdCurrencyCode.push_back(c);
184 }
185 // ibor indices
186 for (auto const& i : aggDataIndices) {
187 QuantLib::ext::shared_ptr<IborIndex> tmp;
188 try {
189 tmp = *market->iborIndex(i);
190 } catch (const std::exception& e) {
191 ALOG("index \"" << i << "\" not found in market, skipping. (" << e.what() << ")");
192 }
193 Size ccyIndex = model->ccyIndex(tmp->currency());
194 asdIndexCurve.push_back(
195 QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(model->lgm(ccyIndex), tmp->forwardingTermStructure()));
196 asdIndex.push_back(tmp->clone(Handle<YieldTermStructure>(asdIndexCurve.back())));
197 asdIndexIndex.push_back(ccyIndex);
198 asdIndexName.push_back(i);
199 }
200 } else {
201 LOG("No asd object set, won't write aggregation scenario data...");
202 }
203
204 // extract AMC calculators, fees and some other infos we need from the ore wrapper
205
206 LOG("Extract AMC Calculators...");
207 std::vector<QuantLib::ext::shared_ptr<AmcCalculator>> amcCalculators;
208 std::vector<Size> tradeId;
209 std::vector<std::string> tradeLabel, tradeType;
210 std::vector<Real> effectiveMultiplier;
211 std::vector<Size> currencyIndex;
212 std::vector<std::vector<std::tuple<Size, Real, QuantLib::Date>>> tradeFees;
213 timer.start();
214 Size progressCounter = 0;
215
216 // reset timing stats
217 RandomVariableStats::instance().enabled = true;
218 RandomVariableStats::instance().data_ops = 0;
219 RandomVariableStats::instance().calc_ops = 0;
220 RandomVariableStats::instance().data_timer.start();
221 RandomVariableStats::instance().data_timer.stop();
222 RandomVariableStats::instance().calc_timer.start();
223 RandomVariableStats::instance().calc_timer.stop();
224 McEngineStats::instance().other_timer.start();
225 McEngineStats::instance().other_timer.stop();
226 McEngineStats::instance().path_timer.start();
227 McEngineStats::instance().path_timer.stop();
228 McEngineStats::instance().calc_timer.start();
229 McEngineStats::instance().calc_timer.stop();
230
231 auto extractAmcCalculator = [&amcCalculators, &tradeId, &tradeLabel, &tradeType, &effectiveMultiplier,
232 &currencyIndex, &tradeFees, &model,
233 &outputCube](const std::pair<std::string, QuantLib::ext::shared_ptr<Trade>>& trade,
234 QuantLib::ext::shared_ptr<AmcCalculator> amcCalc, Real multiplier, bool addFees) {
235 LOG("AMCCalculator extracted for \"" << trade.first << "\"");
236 amcCalculators.push_back(amcCalc);
237 effectiveMultiplier.push_back(multiplier);
238 currencyIndex.push_back(model->ccyIndex(amcCalc->npvCurrency()));
239 if (auto id = outputCube->idsAndIndexes().find(trade.first); id != outputCube->idsAndIndexes().end()) {
240 tradeId.push_back(id->second);
241 } else {
242 QL_FAIL("AMCValuationEngine: trade id '" << trade.first
243 << "' is not present in output cube - internal error.");
244 }
245 tradeLabel.push_back(trade.first);
246 tradeType.push_back(trade.second->tradeType());
247 tradeFees.push_back({});
248 if (addFees) {
249 for (Size i = 0; i < trade.second->instrument()->additionalInstruments().size(); ++i) {
250 if (auto p = QuantLib::ext::dynamic_pointer_cast<QuantExt::Payment>(
251 trade.second->instrument()->additionalInstruments()[i])) {
252 tradeFees.back().push_back(std::make_tuple(model->ccyIndex(p->currency()), p->cashFlow()->amount(),
253 p->cashFlow()->date()));
254 } else {
255 StructuredTradeErrorMessage(trade.second, "Additional instrument is ignored in AMC simulation",
256 "only QuantExt::Payment is handled as additional instrument.")
257 .log();
258 }
259 }
260 }
261 };
262
263 for (auto const& trade : portfolio->trades()) {
264 QuantLib::ext::shared_ptr<AmcCalculator> amcCalc;
265 try {
266 auto inst = trade.second->instrument()->qlInstrument(true);
267 QL_REQUIRE(inst != nullptr,
268 "instrument has no ql instrument, this is not supported by the amc valuation engine.");
269 Real multiplier = trade.second->instrument()->multiplier() * trade.second->instrument()->multiplier2();
270
271 // handle composite trades
272 if (auto cInst = QuantLib::ext::dynamic_pointer_cast<CompositeInstrument>(inst)) {
273 auto addResults = cInst->additionalResults();
274 std::vector<Real> multipliers;
275 while (true) {
276 std::stringstream ss;
277 ss << multipliers.size() + 1 << "_multiplier";
278 if (addResults.find(ss.str()) == addResults.end())
279 break;
280 multipliers.push_back(inst->result<Real>(ss.str()));
281 }
282 std::vector<QuantLib::ext::shared_ptr<AmcCalculator>> amcCalcs;
283 for (Size cmpIdx = 0; cmpIdx < multipliers.size(); ++cmpIdx) {
284 std::stringstream ss;
285 ss << cmpIdx + 1 << "_amcCalculator";
286 if (addResults.find(ss.str()) != addResults.end()) {
287 amcCalcs.push_back(inst->result<QuantLib::ext::shared_ptr<AmcCalculator>>(ss.str()));
288 }
289 }
290 QL_REQUIRE(amcCalcs.size() == multipliers.size(),
291 "Did not find amc calculators for all components of composite trade.");
292 for (Size cmpIdx = 0; cmpIdx < multipliers.size(); ++cmpIdx) {
293 extractAmcCalculator(trade, amcCalc, multiplier * multipliers[cmpIdx], cmpIdx == 0);
294 }
295 continue;
296 }
297
298 // handle non-composite trades
299 amcCalc = inst->result<QuantLib::ext::shared_ptr<AmcCalculator>>("amcCalculator");
300 extractAmcCalculator(trade, amcCalc, multiplier, true);
301
302 } catch (const std::exception& e) {
303 StructuredTradeErrorMessage(trade.second, "Error building trade for AMC simulation", e.what()).log();
304 }
305 }
306
307 timer.stop();
308 calibrationTime += timer.elapsed().wall * 1e-9;
309 LOG("Extracted " << amcCalculators.size() << " AMCCalculators for " << portfolio->size() << " source trades");
310
311 // set up buffers for fx rates and ir states that we need below for the runs against interface 1 and 2
312 // we set these buffers up on the full grid (i.e. valuation + close-out dates, also including the T0 date)
313
314 std::vector<std::vector<std::vector<Real>>> fxBuffer(
315 model->components(CrossAssetModel::AssetType::FX),
316 std::vector<std::vector<Real>>(sgd->getGrid()->dates().size() + 1, std::vector<Real>(outputCube->samples())));
317 std::vector<std::vector<std::vector<Real>>> irStateBuffer(
318 model->components(CrossAssetModel::AssetType::IR),
319 std::vector<std::vector<Real>>(sgd->getGrid()->dates().size() + 1, std::vector<Real>(outputCube->samples())));
320
321 // set up cache for paths
322
323 auto process = model->stateProcess();
324 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<CrossAssetStateProcess>(process)) {
325 tmp->resetCache(sgd->getGrid()->timeGrid().size() - 1);
326 }
327 Size nStates = process->size();
328 QL_REQUIRE(sgd->getGrid()->timeGrid().size() > 0, "AMCValuationEngine: empty time grid given");
329 std::vector<Real> pathTimes(std::next(sgd->getGrid()->timeGrid().begin(), 1), sgd->getGrid()->timeGrid().end());
330 std::vector<std::vector<RandomVariable>> paths(
331 pathTimes.size(), std::vector<RandomVariable>(nStates, RandomVariable(outputCube->samples())));
332
333 // fill fx buffer, ir state buffer and write ASD
334
335 auto pathGenerator = makeMultiPathGenerator(sgd->sequenceType(), process, sgd->getGrid()->timeGrid(), sgd->seed(),
336 sgd->ordering(), sgd->directionIntegers());
337
338 LOG("Write ASD, fill internal fx and irState buffers...");
339
340 for (Size i = 0; i < outputCube->samples(); ++i) {
341 timer.start();
342 const auto& path = pathGenerator->next().value;
343 timer.stop();
344 pathGenTime += timer.elapsed().wall * 1e-9;
345
346 // populate fx and ir state buffers, populate cached paths for interface 2
347
348 timer.start();
349 for (Size k = 0; k < fxBuffer.size(); ++k) {
350 for (Size j = 0; j < sgd->getGrid()->timeGrid().size(); ++j) {
351 fxBuffer[k][j][i] = std::exp(path[model->pIdx(CrossAssetModel::AssetType::FX, k)][j]);
352 }
353 }
354 for (Size k = 0; k < irStateBuffer.size(); ++k) {
355 for (Size j = 0; j < sgd->getGrid()->timeGrid().size(); ++j) {
356 irStateBuffer[k][j][i] = path[model->pIdx(CrossAssetModel::AssetType::IR, k)][j];
357 }
358 }
359
360 for (Size k = 0; k < nStates; ++k) {
361 for (Size j = 0; j < pathTimes.size(); ++j) {
362 paths[j][k].set(i, path[k][j + 1]);
363 }
364 }
365 timer.stop();
366 bufferTime += timer.elapsed().wall * 1e-9;
367
368 // write aggregation scenario data, TODO this seems relatively slow, can we speed it up using LgmVectorised
369
370 if (asd != nullptr) {
371 timer.start();
372 Size dateIndex = 0;
373 for (Size k = 1; k < sgd->getGrid()->timeGrid().size(); ++k) {
374 // only write asd on valuation dates
375 if (!sgd->getGrid()->isValuationDate()[k - 1])
376 continue;
377 // set numeraire
378 asd->set(dateIndex, i, model->numeraire(0, path[0].time(k), path[0][k]),
380 // set fx spots
381 for (Size j = 0; j < asdCurrencyIndex.size(); ++j) {
382 asd->set(dateIndex, i, fx(fxBuffer, asdCurrencyIndex[j], k, i), AggregationScenarioDataType::FXSpot,
383 asdCurrencyCode[j]);
384 }
385 // set index fixings
386 Date d = sgd->getGrid()->dates()[k - 1];
387 for (Size j = 0; j < asdIndex.size(); ++j) {
388 asdIndexCurve[j]->move(d, state(irStateBuffer, asdIndexIndex[j], k, i));
389 auto index = asdIndex[j];
390 if (auto fb = QuantLib::ext::dynamic_pointer_cast<FallbackIborIndex>(asdIndex[j])) {
391 // proxy fallback ibor index by its rfr index's fixing
392 index = fb->rfrIndex();
393 }
394 asd->set(dateIndex, i, index->fixing(index->fixingCalendar().adjust(d)),
396 }
397 // set credit states
398 for (Size j = 0; j < aggDataNumberCreditStates; ++j) {
399 asd->set(dateIndex, i, path[model->pIdx(CrossAssetModel::AssetType::CrState, j)][k],
401 }
402 ++dateIndex;
403 }
404 timer.stop();
405 asdTime += timer.elapsed().wall * 1e-9;
406 }
407 }
408
409 // Run AmcCalculators
410
411 LOG("Run simulation...");
412 // set up vectors indicating valuation times, close-out times and all times
413
414 std::vector<size_t> allTimes;
415 std::vector<size_t> valuationTimeIdx, closeOutTimeIdx;
416 const auto& grid = sgd->getGrid();
417 const auto& dates = grid->dates();
418 size_t j = 0;
419 for (size_t i = 0; i < pathTimes.size(); ++i) {
420 allTimes.push_back(i);
421 if (sgd->withCloseOutLag()) {
422 auto& d = dates[i];
423 if (grid->isValuationDate()[i]) {
424 valuationTimeIdx.push_back(i);
425 auto closeOutDate = grid->closeOutDateFromValuationDate(d);
426 while (j < pathTimes.size() && dates[j] != closeOutDate) {
427 ++j;
428 }
429 QL_REQUIRE(j < pathTimes.size(),
430 "AmcValuationEngine:: couldnt find close out date" << to_string(closeOutDate));
431 closeOutTimeIdx.push_back(j);
432 }
433 }
434 }
435 // loop over amc calculators, get result and populate cube
436
437 timer.start();
438 for (Size j = 0; j < amcCalculators.size(); ++j) {
439 auto resFee = feeContributions(j, sgd, model->irModel(0)->termStructure()->referenceDate(),
440 outputCube->samples(), tradeFees, model, fxBuffer, irStateBuffer);
441
442 if (!sgd->withCloseOutLag()) {
443 // no close-out lag, fill depth 0 with npv on path
444 auto res = simulatePathInterface2(amcCalculators[j], pathTimes, paths, allTimes, allTimes, tradeLabel[j],
445 tradeType[j]);
446 Real v = outputCube->getT0(tradeId[j], 0);
447 outputCube->setT0(v +
448 res[0].at(0) * fx(fxBuffer, currencyIndex[j], 0, 0) *
449 numRatio(model, irStateBuffer, currencyIndex[j], 0, 0.0, 0) *
450 effectiveMultiplier[j] +
451 resFee[0][0],
452 tradeId[j], 0);
453 for (Size k = 1; k < res.size(); ++k) {
454 Real t = sgd->getGrid()->timeGrid()[k];
455 for (Size i = 0; i < outputCube->samples(); ++i) {
456 Real v = outputCube->get(tradeId[j], k - 1, i, 0);
457 outputCube->set(v +
458 res[k][i] * fx(fxBuffer, currencyIndex[j], k, i) *
459 numRatio(model, irStateBuffer, currencyIndex[j], k, t, i) *
460 effectiveMultiplier[j] +
461 resFee[k][i],
462 tradeId[j], k - 1, i, 0);
463 }
464 }
465 } else {
466 // with close-out lag, fill depth 0 with valuation date npvs, depth 1 with (inflated) close-out npvs
467 if (sgd->withMporStickyDate()) {
468 // sticky date mpor mode. simulate the valuation times...
469 auto res = simulatePathInterface2(amcCalculators[j], pathTimes, paths, valuationTimeIdx,
470 valuationTimeIdx, tradeLabel[j], tradeType[j]);
471 // ... and then the close-out times, but times moved to the valuation times
472 auto resLag = simulatePathInterface2(amcCalculators[j], pathTimes, paths, closeOutTimeIdx,
473 valuationTimeIdx, tradeLabel[j], tradeType[j]);
474 Real v = outputCube->getT0(tradeId[j], 0);
475 outputCube->setT0(v +
476 res[0].at(0) * fx(fxBuffer, currencyIndex[j], 0, 0) *
477 numRatio(model, irStateBuffer, currencyIndex[j], 0, 0.0, 0) *
478 effectiveMultiplier[j] +
479 resFee[0][0],
480 tradeId[j], 0);
481 int dateIndex = -1;
482 std::map<QuantLib::Date, std::vector<std::tuple<QuantLib::Date, double, size_t>>> closeOutDateToValuationDate;
483 for (Size k = 0; k < sgd->getGrid()->dates().size(); ++k) {
484
485 Real t = sgd->getGrid()->timeGrid()[k + 1];
486 if (sgd->getGrid()->isCloseOutDate()[k]) {
487 Date closeOutDate = sgd->getGrid()->dates()[k];
488 auto dateIndexIt = closeOutDateToValuationDate.find(closeOutDate);
489 QL_REQUIRE(dateIndexIt != closeOutDateToValuationDate.end() && !dateIndexIt->second.empty(),
490 "The valuation date needs to before the corresponding close out date");
491 for (const auto& [valuationDate, valuationTime, valuationIndex] : dateIndexIt->second){
492 for (Size i = 0; i < outputCube->samples(); ++i) {
493 Real v = outputCube->get(tradeId[j], valuationIndex, i, 1);
494 outputCube->set(
495 v +
496 resLag[valuationIndex + 1][i] * fx(fxBuffer, currencyIndex[j], k + 1, i) *
497 num(model, irStateBuffer, currencyIndex[j], k + 1, valuationTime, i) *
498 effectiveMultiplier[j] +
499 resFee[valuationIndex + 1][i],
500 tradeId[j], valuationIndex, i, 1);
501 }
502 }
503
504 }
505 if (sgd->getGrid()->isValuationDate()[k]) {
506 Date valuationDate = sgd->getGrid()->dates()[k];
507 Date closeOutDate = sgd->getGrid()->closeOutDateFromValuationDate(valuationDate);
508 closeOutDateToValuationDate[closeOutDate].push_back(std::make_tuple(valuationDate, t, ++dateIndex));
509 for (Size i = 0; i < outputCube->samples(); ++i) {
510 Real v = outputCube->get(tradeId[j], dateIndex, i, 0);
511 outputCube->set(v +
512 res[dateIndex + 1][i] * fx(fxBuffer, currencyIndex[j], k + 1, i) *
513 numRatio(model, irStateBuffer, currencyIndex[j], k + 1, t, i) *
514 effectiveMultiplier[j] +
515 resFee[dateIndex + 1][i],
516 tradeId[j], dateIndex, i, 0);
517 }
518 }
519 }
520 } else {
521 // actual date mpor mode: simulate all times in one go
522 auto res = simulatePathInterface2(amcCalculators[j], pathTimes, paths, allTimes, allTimes,
523 tradeLabel[j], tradeType[j]);
524 Real v = outputCube->getT0(tradeId[j], 0);
525 outputCube->setT0(v + res[0].at(0) * fx(fxBuffer, currencyIndex[j], 0, 0) *
526 numRatio(model, irStateBuffer, currencyIndex[j], 0, 0.0, 0) *
527 effectiveMultiplier[j],
528 tradeId[j], 0);
529 std::map<QuantLib::Date, std::vector<std::tuple<QuantLib::Date, double, size_t>>>
530 closeOutDateToValuationDate;
531 int dateIndex = -1;
532 for (Size k = 1; k < res.size(); ++k) {
533 Real t = sgd->getGrid()->timeGrid()[k];
534 if (sgd->getGrid()->isCloseOutDate()[k - 1]) {
535 Date closeOutDate = sgd->getGrid()->dates()[k - 1];
536 auto dateIndexIt = closeOutDateToValuationDate.find(closeOutDate);
537 QL_REQUIRE(dateIndexIt != closeOutDateToValuationDate.end() && !dateIndexIt->second.empty(),
538 "The valuation date needs to before the corresponding close out date");
539 for (const auto& [valuationDate, valuationTime, valuationIndex] : dateIndexIt->second) {
540 for (Size i = 0; i < outputCube->samples(); ++i) {
541 Real v = outputCube->get(tradeId[j], valuationIndex, i, 1);
542 outputCube->set(v +
543 res[k][i] * fx(fxBuffer, currencyIndex[j], k, i) *
544 num(model, irStateBuffer, currencyIndex[j], k, t, i) *
545 effectiveMultiplier[j] +
546 resFee[k][i],
547 tradeId[j], valuationIndex, i, 1);
548 }
549 }
550 }
551 if (sgd->getGrid()->isValuationDate()[k - 1]) {
552 Date valuationDate = sgd->getGrid()->dates()[k - 1];
553 Date closeOutDate = sgd->getGrid()->closeOutDateFromValuationDate(valuationDate);
554 closeOutDateToValuationDate[closeOutDate].push_back(std::make_tuple(valuationDate, t, ++dateIndex));
555 for (Size i = 0; i < outputCube->samples(); ++i) {
556 Real v = outputCube->get(tradeId[j], dateIndex, i, 0);
557 outputCube->set(v +
558 res[k][i] * fx(fxBuffer, currencyIndex[j], k, i) *
559 numRatio(model, irStateBuffer, currencyIndex[j], k, t, i) *
560 effectiveMultiplier[j] +
561 resFee[k][i],
562 tradeId[j], dateIndex, i, 0);
563 }
564 }
565 }
566 }
567 }
568 std::ostringstream detail;
569 detail << portfolio->size() << " trade" << (portfolio->size() == 1 ? "" : "s");
570 progressIndicator->updateProgress(++progressCounter, portfolio->size(), detail.str());
571 }
572 timer.stop();
573 valuationTime += timer.elapsed().wall * 1e-9;
574
575 totalTime = timerTotal.elapsed().wall * 1e-9;
576 residualTime = totalTime - (calibrationTime + pathGenTime + valuationTime + asdTime + bufferTime);
577 LOG("calibration time : " << calibrationTime << " sec");
578 LOG("asd time : " << asdTime << " sec");
579 LOG("buffer time : " << bufferTime << " sec");
580 LOG("path generation time : " << pathGenTime << " sec");
581 LOG("valuation time : " << valuationTime << " sec");
582 LOG("residual time : " << residualTime << " sec");
583 LOG("total time : " << totalTime << " sec");
584 LOG("AMCValuationEngine finished for one of possibly multiple threads.");
585 LOG("RandomVariableStats : ");
586 LOG("Data Ops : " << RandomVariableStats::instance().data_ops / 1E6 << " MOPS");
587 LOG("Calc Ops : " << RandomVariableStats::instance().calc_ops / 1E6 << " MOPS");
588 LOG("Data Timer : " << RandomVariableStats::instance().data_timer.elapsed().wall / 1E9 << " sec");
589 LOG("Calc Timer : " << RandomVariableStats::instance().calc_timer.elapsed().wall / 1E9 << " sec");
590 LOG("Data Performace : " << RandomVariableStats::instance().data_ops * 1E3 /
591 RandomVariableStats::instance().data_timer.elapsed().wall
592 << " MFLOPS");
593 LOG("Calc Performace : " << RandomVariableStats::instance().calc_ops * 1E3 /
594 RandomVariableStats::instance().calc_timer.elapsed().wall
595 << " MFLOPS");
596 LOG("MC Other Timer : " << McEngineStats::instance().other_timer.elapsed().wall / 1E9 << " sec");
597 LOG("MC Path Timer : " << McEngineStats::instance().path_timer.elapsed().wall / 1E9 << " sec");
598 LOG("MC Calc Timer : " << McEngineStats::instance().calc_timer.elapsed().wall / 1E9 << " sec");
599
600} // runCoreEngine()
601
602} // namespace
603
605 const QuantLib::Size nThreads, const QuantLib::Date& today, const QuantLib::Size nSamples,
606 const QuantLib::ext::shared_ptr<ore::data::Loader>& loader,
607 const QuantLib::ext::shared_ptr<ScenarioGeneratorData>& scenarioGeneratorData,
608 const std::vector<string>& aggDataIndices, const std::vector<string>& aggDataCurrencies,
609 const Size aggDataNumberCreditStates, const QuantLib::ext::shared_ptr<CrossAssetModelData>& crossAssetModelData,
610 const QuantLib::ext::shared_ptr<ore::data::EngineData>& engineData,
611 const QuantLib::ext::shared_ptr<ore::data::CurveConfigurations>& curveConfigs,
612 const QuantLib::ext::shared_ptr<ore::data::TodaysMarketParameters>& todaysMarketParams,
613 const std::string& configurationLgmCalibration, const std::string& configurationFxCalibration,
614 const std::string& configurationEqCalibration, const std::string& configurationInfCalibration,
615 const std::string& configurationCrCalibration, const std::string& configurationFinalModel,
616 const QuantLib::ext::shared_ptr<ore::data::ReferenceDataManager>& referenceData,
617 const ore::data::IborFallbackConfig& iborFallbackConfig, const bool handlePseudoCurrenciesTodaysMarket,
618 const std::function<QuantLib::ext::shared_ptr<ore::analytics::NPVCube>(
619 const QuantLib::Date&, const std::set<std::string>&, const std::vector<QuantLib::Date>&, const QuantLib::Size)>&
620 cubeFactory,
621 const QuantLib::ext::shared_ptr<Scenario>& offSetScenario,
622 const QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarketParameters>& simMarketParams)
623 : useMultithreading_(true), aggDataIndices_(aggDataIndices), aggDataCurrencies_(aggDataCurrencies),
624 aggDataNumberCreditStates_(aggDataNumberCreditStates), scenarioGeneratorData_(scenarioGeneratorData),
625 nThreads_(nThreads), today_(today), nSamples_(nSamples), loader_(loader),
626 crossAssetModelData_(crossAssetModelData), engineData_(engineData), curveConfigs_(curveConfigs),
627 todaysMarketParams_(todaysMarketParams), configurationLgmCalibration_(configurationLgmCalibration),
628 configurationFxCalibration_(configurationFxCalibration), configurationEqCalibration_(configurationEqCalibration),
629 configurationInfCalibration_(configurationInfCalibration),
630 configurationCrCalibration_(configurationCrCalibration), configurationFinalModel_(configurationFinalModel),
631 referenceData_(referenceData), iborFallbackConfig_(iborFallbackConfig),
632 handlePseudoCurrenciesTodaysMarket_(handlePseudoCurrenciesTodaysMarket), cubeFactory_(cubeFactory),
633 offsetScenario_(offSetScenario), simMarketParams_(simMarketParams) {
634#ifndef QL_ENABLE_SESSIONS
635 QL_FAIL(
636 "AMCValuationEngine requires a build with QL_ENABLE_SESSIONS = ON when ctor multi-threaded runs is called.");
637#endif
638 QL_REQUIRE(scenarioGeneratorData_->seed() != 0,
639 "AMCValuationEngine: path generation uses seed 0 - this might lead to inconsistent results to a classic "
640 "simulation run, if both are combined. Consider using a non-zero seed.");
641 if (!cubeFactory_)
642 cubeFactory_ = [](const QuantLib::Date& asof, const std::set<std::string>& ids,
643 const std::vector<QuantLib::Date>& dates, const Size samples) {
644 return QuantLib::ext::make_shared<ore::analytics::DoublePrecisionInMemoryCube>(asof, ids, dates, samples);
645 };
646}
647
648AMCValuationEngine::AMCValuationEngine(const QuantLib::ext::shared_ptr<QuantExt::CrossAssetModel>& model,
649 const QuantLib::ext::shared_ptr<ScenarioGeneratorData>& scenarioGeneratorData,
650 const QuantLib::ext::shared_ptr<Market>& market,
651 const std::vector<string>& aggDataIndices,
652 const std::vector<string>& aggDataCurrencies,
653 const Size aggDataNumberCreditStates)
654 : useMultithreading_(false), aggDataIndices_(aggDataIndices), aggDataCurrencies_(aggDataCurrencies),
655 aggDataNumberCreditStates_(aggDataNumberCreditStates), scenarioGeneratorData_(scenarioGeneratorData),
656 model_(model), market_(market) {
657
658 QL_REQUIRE((aggDataIndices.empty() && aggDataCurrencies.empty()) || market != nullptr,
659 "AMCValuationEngine: market is required for asd generation");
660 QL_REQUIRE(scenarioGeneratorData_->seed() != 0,
661 "AMCValuationEngine: path generation uses seed 0 - this might lead to inconsistent results to a classic "
662 "simulation run, if both are combined. Consider using a non-zero seed.");
663 QL_REQUIRE(
664 model->irlgm1f(0)->termStructure()->dayCounter() == scenarioGeneratorData_->getGrid()->dayCounter(),
665 "AMCValuationEngine: day counter in simulation parameters ("
666 << scenarioGeneratorData_->getGrid()->dayCounter() << ") is different from model day counter ("
667 << model->irlgm1f(0)->termStructure()->dayCounter()
668 << "), align these e.g. by setting the day counter in the simulation parameters to the model day counter");
669}
670
671void AMCValuationEngine::buildCube(const QuantLib::ext::shared_ptr<Portfolio>& portfolio,
672 QuantLib::ext::shared_ptr<NPVCube>& outputCube) {
673
674 LOG("Starting single-threaded AMCValuationEngine for " << portfolio->size() << " trades, " << outputCube->samples()
675 << " samples and "
676 << scenarioGeneratorData_->getGrid()->size() << " dates.");
677
678 QL_REQUIRE(!useMultithreading_, "AMCValuationEngine::buildCube() method was called with signature for "
679 "single-threaded run, but engine was constructed for multi-threaded runs");
680
681 QL_REQUIRE(portfolio->size() > 0, "AMCValuationEngine::buildCube: empty portfolio");
682
683 QL_REQUIRE(outputCube->numIds() == portfolio->trades().size(),
684 "cube x dimension (" << outputCube->numIds() << ") "
685 << "different from portfolio size (" << portfolio->trades().size() << ")");
686
687 QL_REQUIRE(outputCube->numDates() == scenarioGeneratorData_->getGrid()->valuationDates().size(),
688 "cube y dimension (" << outputCube->numDates() << ") "
689 << "different from number of valuation dates ("
690 << scenarioGeneratorData_->getGrid()->valuationDates().size() << ")");
691
692 try {
693 // we can use the mt progress indicator here although we are running on a single thread
695 aggDataNumberCreditStates_, asd_, outputCube,
696 QuantLib::ext::make_shared<ore::analytics::MultiThreadedProgressIndicator>(this->progressIndicators()));
697 } catch (const std::exception& e) {
698 QL_FAIL("Error during amc val engine run: " << e.what());
699 }
700
701 LOG("Finished single-threaded AMCValuationEngine run.");
702}
703
704void AMCValuationEngine::buildCube(const QuantLib::ext::shared_ptr<ore::data::Portfolio>& portfolio) {
705 LOG("Starting multi-threaded AMCValuationEngine for "
706 << portfolio->size() << " trades, " << nSamples_ << " samples and " << scenarioGeneratorData_->getGrid()->size()
707 << " dates.");
708
709 QL_REQUIRE(useMultithreading_, "AMCValuationEngine::buildCube() method was called with signature for "
710 "multi-threaded run, but engine was constructed for single-threaded runs");
711
712 QL_REQUIRE(portfolio->size() > 0, "AMCValuationEngine::buildCube: empty portfolio");
713
714 // split portfolio into nThreads parts (just distribute the trades assuming all are approximately expensive)
715
716 LOG("Splitting portfolio.");
717
718 Size eff_nThreads = std::min(portfolio->size(), nThreads_);
719
720 LOG("portfolio size = " << portfolio->size());
721 LOG("nThreads = " << nThreads_);
722 LOG("eff nThreads = " << eff_nThreads);
723
724 QL_REQUIRE(eff_nThreads > 0, "effective threads are zero, this is not allowed.");
725
726 std::vector<QuantLib::ext::shared_ptr<ore::data::Portfolio>> portfolios;
727 for (Size i = 0; i < eff_nThreads; ++i)
728 portfolios.push_back(QuantLib::ext::make_shared<ore::data::Portfolio>());
729
730 Size portfolioIndex = 0;
731 for (auto const& t : portfolio->trades()) {
732 portfolios[portfolioIndex]->add(t.second);
733 if (++portfolioIndex >= eff_nThreads)
734 portfolioIndex = 0;
735 }
736
737 // output the portfolios into strings so that the worker threads can load them from there
738
739 std::vector<std::string> portfoliosAsString;
740 for (auto const& p : portfolios) {
741 portfoliosAsString.emplace_back(p->toXMLString());
742 }
743
744 // log info on the portfolio split
745
746 for (Size i = 0; i < eff_nThreads; ++i) {
747 LOG("Portfolio #" << i << " number of trades : " << portfolios[i]->size());
748 }
749
750 // build loaders for each thread as clones of the original one
751
752 LOG("Cloning loaders for " << eff_nThreads << " threads...");
753 std::vector<QuantLib::ext::shared_ptr<ore::data::ClonedLoader>> loaders;
754 for (Size i = 0; i < eff_nThreads; ++i)
755 loaders.push_back(QuantLib::ext::make_shared<ore::data::ClonedLoader>(today_, loader_));
756
757 // build nThreads mini-cubes to which each thread writes its results
758
759 LOG("Build " << eff_nThreads << " mini result cubes...");
760 miniCubes_.clear();
761 for (Size i = 0; i < eff_nThreads; ++i) {
762 miniCubes_.push_back(
763 cubeFactory_(today_, portfolios[i]->ids(), scenarioGeneratorData_->getGrid()->valuationDates(), nSamples_));
764 }
765
766 // precompute sim dates
767
768 std::vector<Date> simDates =
769 scenarioGeneratorData_->withCloseOutLag() && !scenarioGeneratorData_->withMporStickyDate()
770 ? scenarioGeneratorData_->getGrid()->dates()
771 : scenarioGeneratorData_->getGrid()->valuationDates();
772
773 // build progress indicator consolidating the results from the threads
774
775 auto progressIndicator =
776 QuantLib::ext::make_shared<ore::analytics::MultiThreadedProgressIndicator>(this->progressIndicators());
777
778 // create the thread pool with eff_nThreads and queue size = eff_nThreads as well
779
780 // LOG("Create thread pool with " << eff_nThreads);
781 // ctpl::thread_pool threadPool(eff_nThreads);
782
783 // create the jobs and push them to the pool
784
785 using resultType = int;
786 std::vector<std::future<resultType>> results(eff_nThreads);
787
788 std::vector<std::thread> jobs; // not needed if thread pool is used
789
790 // get obs mode of main thread, so that we can set this mode in the worker threads below
791
792 ore::analytics::ObservationMode::Mode obsMode = ore::analytics::ObservationMode::instance().mode();
793
794 for (Size i = 0; i < eff_nThreads; ++i) {
795
796 auto job = [this, obsMode, &portfoliosAsString, &loaders, &simDates, &progressIndicator](int id) -> resultType {
797 // set thread local singletons
798
799 QuantLib::Settings::instance().evaluationDate() = today_;
800 ore::analytics::ObservationMode::instance().setMode(obsMode);
801
802 LOG("Start thread " << id);
803
804 int rc;
805
806 try {
807
808 // build todays market using cloned market data
809
810 QuantLib::ext::shared_ptr<ore::data::Market> initMarket = QuantLib::ext::make_shared<ore::data::TodaysMarket>(
811 today_, todaysMarketParams_, loaders[id], curveConfigs_, true, true, true, referenceData_, false,
813
814 QuantLib::ext::shared_ptr<ore::data::Market> market = initMarket;
815
816 if(offsetScenario_ != nullptr){
817 QL_REQUIRE(simMarketParams_ != nullptr,
818 "AMC Valuation Engine can not build simMarket without simMarketParam");
819 bool continueOnError = true;
820 std::string configuration = configurationFinalModel_;
821 market = QuantLib::ext::make_shared<ScenarioSimMarket>(
822 initMarket, simMarketParams_, QuantLib::ext::make_shared<FixingManager>(today_), configuration,
823 *curveConfigs_, *todaysMarketParams_, continueOnError, true, true, false, iborFallbackConfig_,
824 false, offsetScenario_);
825 }
826
827 // build cam
831 configurationFinalModel_, false, true, "", SalvagingAlgorithm::None, "xva/amc cam building");
832
833 auto cam = *modelBuilder.model();
834
835 // build portfolio against init market
836
837 auto portfolio = QuantLib::ext::make_shared<ore::data::Portfolio>();
838 portfolio->fromXMLString(portfoliosAsString[id]);
839
840 QuantLib::ext::shared_ptr<EngineData> edCopy = QuantLib::ext::make_shared<EngineData>(*engineData_);
841 edCopy->globalParameters()["GenerateAdditionalResults"] = "false";
842 edCopy->globalParameters()["RunType"] = "NPV";
843 map<MarketContext, string> configurations{{MarketContext::irCalibration, configurationLgmCalibration_},
844 {MarketContext::fxCalibration, configurationFxCalibration_},
845 {MarketContext::pricing, configurationFinalModel_}};
846
847 auto engineFactory = QuantLib::ext::make_shared<EngineFactory>(
848 edCopy, market, configurations, referenceData_, iborFallbackConfig_,
849 EngineBuilderFactory::instance().generateAmcEngineBuilders(cam, simDates), true);
850
851 portfolio->build(engineFactory, "amc-val-engine", true);
852
853 // run core engine code (asd is written for thread id 0 only)
854
855 runCoreEngine(portfolio, cam, market, scenarioGeneratorData_, aggDataIndices_, aggDataCurrencies_,
856 aggDataNumberCreditStates_, id == 0 ? asd_ : nullptr, miniCubes_[id], progressIndicator);
857
858 // return code 0 = ok
859
860 LOG("Thread " << id << " successfully finished.");
861
862 rc = 0;
863
864 } catch (const std::exception& e) {
865
866 // log error and return code 1 = not ok
867
868 ore::analytics::StructuredAnalyticsErrorMessage("AMC Valuation Engine (multithreaded mode)", "",
869 e.what())
870 .log();
871 rc = 1;
872 }
873
874 // exit
875
876 return rc;
877 };
878
879 // results[i] = threadPool.push(job);
880
881 // not needed if thread pool is used
882 std::packaged_task<resultType(int)> task(job);
883 results[i] = task.get_future();
884 std::thread thread(std::move(task), i);
885 jobs.emplace_back(std::move(thread));
886 }
887
888 // not needed if thread pool is used
889 for (auto& t : jobs)
890 t.join();
891
892 for (Size i = 0; i < results.size(); ++i) {
893 results[i].wait();
894 }
895
896 for (Size i = 0; i < results.size(); ++i) {
897 QL_REQUIRE(results[i].valid(), "internal error: did not get a valid result");
898 int rc = results[i].get();
899 QL_REQUIRE(rc == 0, "error: thread " << i << " exited with return code " << rc
900 << ". Check for structured errors from 'AMCValuationEngine'.");
901 }
902
903 // stop the thread pool, wait for unfinished jobs
904
905 // LOG("Stop thread pool");
906 // threadPool.stop(true);
907
908 LOG("Finished multi-threaded AMCValuationEngine run.");
909}
910
911} // namespace analytics
912} // namespace ore
valuation engine for amc
QuantLib::ext::shared_ptr< ore::data::CurveConfigurations > curveConfigs_
QuantLib::ext::shared_ptr< ore::data::TodaysMarketParameters > todaysMarketParams_
const QuantLib::ext::shared_ptr< QuantExt::CrossAssetModel > model_
QuantLib::ext::shared_ptr< ore::analytics::ScenarioSimMarketParameters > simMarketParams_
std::vector< QuantLib::ext::shared_ptr< ore::analytics::NPVCube > > miniCubes_
void buildCube(const QuantLib::ext::shared_ptr< ore::data::Portfolio > &portfolio, QuantLib::ext::shared_ptr< ore::analytics::NPVCube > &outputCube)
build cube in single threaded run
QuantLib::ext::shared_ptr< ore::analytics::AggregationScenarioData > asd_
QuantLib::ext::shared_ptr< ScenarioGeneratorData > scenarioGeneratorData_
const QuantLib::ext::shared_ptr< ore::data::Market > market_
QuantLib::ext::shared_ptr< ore::data::ReferenceDataManager > referenceData_
QuantLib::ext::shared_ptr< Scenario > offsetScenario_
ore::data::IborFallbackConfig iborFallbackConfig_
AMCValuationEngine(const QuantLib::ext::shared_ptr< QuantExt::CrossAssetModel > &model, const QuantLib::ext::shared_ptr< ore::analytics::ScenarioGeneratorData > &scenarioGeneratorData, const QuantLib::ext::shared_ptr< ore::data::Market > &market, const std::vector< string > &aggDataIndices, const std::vector< string > &aggDataCurrencies, const Size aggDataNumberCreditStates)
Constructor for single-threaded runs.
QuantLib::ext::shared_ptr< CrossAssetModelData > crossAssetModelData_
const std::vector< string > aggDataCurrencies_
QuantLib::ext::shared_ptr< ore::data::Loader > loader_
const std::vector< string > aggDataIndices_
std::function< QuantLib::ext::shared_ptr< ore::analytics::NPVCube >(const QuantLib::Date &, const std::set< std::string > &, const std::vector< QuantLib::Date > &, const QuantLib::Size)> cubeFactory_
QuantLib::ext::shared_ptr< ore::data::EngineData > engineData_
Handle< QuantExt::CrossAssetModel > model() const
const std::set< QuantLib::ext::shared_ptr< ProgressIndicator > > & progressIndicators() const
const QuantLib::ext::shared_ptr< ModelCG > model_
Currency parseCurrency(const string &s)
A cube implementation that stores the cube in memory.
#define LOG(text)
#define ALOG(text)
boost::shared_ptr< MultiPathGeneratorBase > makeMultiPathGenerator(const SequenceType s, const boost::shared_ptr< StochasticProcess > &process, const TimeGrid &timeGrid, const BigNatural seed, const SobolBrownianGenerator::Ordering ordering=SobolBrownianGenerator::Steps, const SobolRsg::DirectionIntegers directionIntegers=SobolRsg::JoeKuoD7)
Size size(const ValueType &v)
std::string to_string(const LocationInfo &l)
Singleton class to hold global Observation Mode.
Structured analytics error.
vector< string > curveConfigs
Date asof(14, Jun, 2018)