Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
creditmigrationhelper.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
19/*! \file aggregation/creditmigrationhelper.hpp
20 \brief Credit migration helper class
21 \ingroup engine
22*/
23
25
28
31
32#include <ql/math/distributions/normaldistribution.hpp>
33#include <ql/time/daycounters/actualactual.hpp>
34
35using namespace QuantLib;
36using namespace QuantExt;
37
38namespace ore {
39namespace analytics {
40
41CreditMigrationHelper::CreditMigrationHelper(const QuantLib::ext::shared_ptr<CreditSimulationParameters> parameters,
42 const QuantLib::ext::shared_ptr<NPVCube> cube,
43 const QuantLib::ext::shared_ptr<NPVCube> nettedCube,
44 const QuantLib::ext::shared_ptr<AggregationScenarioData> aggData,
45 const Size cubeIndexCashflows, const Size cubeIndexStateNpvs,
46 const Real distributionLowerBound, const Real distributionUpperBound,
47 const Size buckets, const Matrix& globalFactorCorrelation,
48 const string& baseCurrency)
49 : parameters_(parameters), cube_(cube), nettedCube_(nettedCube), aggData_(aggData),
50 cubeIndexCashflows_(cubeIndexCashflows), cubeIndexStateNpvs_(cubeIndexStateNpvs),
51 globalFactorCorrelation_(globalFactorCorrelation), baseCurrency_(baseCurrency),
52 creditMode_(parseCreditMode(parameters_->creditMode())),
53 loanExposureMode_(parseLoanExposureMode(parameters_->loanExposureMode())),
54 evaluation_(parseEvaluation(parameters_->evaluation())),
55 bucketing_(distributionLowerBound, distributionUpperBound, buckets) {
56
57 rescaledTransitionMatrices_.resize(cube_->numDates());
58 init();
61} // CreditMigrationHelper()
62
63namespace {
64
65Real conditionalProb(const Real p, const Real m, const Real v) {
66 QuantLib::CumulativeNormalDistribution nd;
67 QuantLib::InverseCumulativeNormal icn;
68 if (close_enough(p, 0.0))
69 return 0.0;
70 if (close_enough(p, 1.0))
71 return 1.0;
72 Real icnP = icn(p);
73 if (close_enough(v, 1.0))
74 return icnP >= m ? 1.0 : 0.0;
75 return nd((icnP - m) / std::sqrt(1.0 - v));
76}
77
78Real prob_tauA_lt_tauB_lt_T(const Real pa, const Real pb, const Real T) {
79 Real l1 = -std::log(1.0 - pa) / T;
80 Real l2 = -std::log(1.0 - pb) / T;
81 Real res = (1.0 - std::exp(-l2 * T)) - l2 / (l1 + l2) * (1 - std::exp(-(l1 + l2) * T));
82 return res;
83}
84
85} // anonymous namespace
86
87std::map<string, Matrix> CreditMigrationHelper::rescaledTransitionMatrices(const Size date) {
88
89 std::map<string, Matrix> transMat; // rescaled transition matrix per (matrix) name
90
91 // have we computed the result for the date index already?
92 if (!rescaledTransitionMatrices_[date].empty()) {
93 transMat = rescaledTransitionMatrices_[date];
94 } else {
95 Time t = cubeTimes_[date];
96
97 const std::vector<string>& entities = parameters_->entities();
98 const std::vector<string>& matrixNames = parameters_->transitionMatrices();
99
100 for (Size i = 0; i < entities.size(); ++i) {
101 if (transMat.count(matrixNames[i]) > 0) {
102 DLOG("Transition matrix for " << entities[i] << " (" << matrixNames[i] << ") cached, nothing to do.");
103 } else {
104 QL_REQUIRE(parameters_->transitionMatrix().count(parameters_->transitionMatrices()[i]) > 0,
105 "No transition matrix defined for " << parameters_->entities()[i] << " / "
106 << parameters_->transitionMatrices()[i]);
107 Matrix m = parameters_->transitionMatrix().at(parameters_->transitionMatrices()[i]);
108 DLOG("Transition matrix (1y) for " << parameters_->entities()[i] << " is "
109 << parameters_->transitionMatrices()[i] << ":");
110 DLOGGERSTREAM(m);
111 if (n_ == Null<Size>()) {
112 n_ = m.rows();
113 } else {
114 QL_REQUIRE(m.rows() == n_, "Found transition matrix with different dimension "
115 << m.rows() << "x" << m.columns() << " expected " << n_ << "x" << n_
116 << " for " << parameters_->entities()[i] << " / "
117 << parameters_->transitionMatrices()[i]);
118 }
119 QL_REQUIRE(date < cube_->numDates(),
120 "date index " << date << " outside range, cube has " << cube_->numDates() << " dates.");
122 DLOG("Sanitised transition matrix:");
123 DLOGGERSTREAM(m);
124 Matrix g = QuantExt::generator(m);
125 DLOG("Generator matrix:")
126 DLOGGERSTREAM(g);
128 Matrix mt = QuantExt::Expm(t * g);
129 DLOG("Scaled transition matrix (t=" << t << "):");
130 DLOGGERSTREAM(mt);
132 Matrix mcheck = m;
133 for (Size c = 1; c < static_cast<Size>(std::round(t)); ++c) {
134 mcheck = mcheck * m;
135 }
136 DLOG("Elementary transition matrix (t=" << std::round(t) << ", just for plausibility):");
137 DLOGGERSTREAM(mcheck);
138 transMat[matrixNames[i]] = mt;
139 }
140 }
141 rescaledTransitionMatrices_[date] = transMat;
142 }
143
144 return transMat;
145} // rescaledTransitionMatrices
146
148
149 LOG("CreditMigrationHelper Init");
150
151 n_ = Null<Size>(); // number of states
152
153 Date asof = cube_->asof();
154 for (Size d = 0; d < cube_->numDates(); ++d) {
155 Date tDate = cube_->dates()[d];
156 cubeTimes_.push_back(
157 ActualActual(ActualActual::ISDA).yearFraction(asof, tDate)); // FIME make dc consistent with sim
158 }
159
160 const std::vector<Array>& loadings = parameters_->factorLoadings();
161 Size f = globalFactorCorrelation_.rows();
162 globalVar_.resize(parameters_->entities().size(), 0.0);
163 for (Size i = 0; i < parameters_->entities().size(); ++i) {
164 // variance of global factors
165 QL_REQUIRE(parameters_->factorLoadings()[i].size() == f, "wrong size for loadings for entity "
166 << parameters_->entities()[i] << " ("
167 << loadings.size() << "), expected " << f);
168 // don't optimise, code is simpler, matrix is small
169 for (Size j = 0; j < loadings[i].size(); ++j) {
170 for (Size k = 0; k < loadings[i].size(); ++k) {
171 globalVar_[i] += loadings[i][j] * loadings[i][k] * globalFactorCorrelation_[j][k];
172 }
173 }
174 }
175
176 globalStates_.resize(cube_->numDates(), std::vector<std::vector<Real>>(parameters_->entities().size(),
177 std::vector<Real>(cube_->samples())));
178 Array globalFactors(f);
179 std::vector<string> numStr(f);
180 for (Size i = 0; i < f; ++i) {
181 std::ostringstream num;
182 num << i;
183 numStr[i] = num.str();
184 }
185 for (Size d = 0; d < cube_->numDates(); ++d) {
186 for (Size i = 0; i < parameters_->entities().size(); ++i) {
187 for (Size j = 0; j < cube_->samples(); ++j) {
188 for (Size ii = 0; ii < f; ++ii) {
189 globalFactors[ii] = aggData_->get(d, j, AggregationScenarioDataType::CreditState, numStr[ii]);
190 }
191 globalStates_[d][i][j] = DotProduct(loadings[i], globalFactors / std::sqrt(cubeTimes_[d]));
192 }
193 }
194 }
195
196 LOG("CreditMigration Init done.");
197
198} // init
199
201
202 LOG("Init entity state simulation");
203
205 std::vector<std::vector<Size>>(parameters_->entities().size(), std::vector<Size>(cube_->samples()));
206
207 LOG("Init entity state simulation done.");
208
209} // initEntityStatesSimulation
210
211std::vector<Matrix> CreditMigrationHelper::initEntityStateSimulation(const Size date, const Size path) {
212 std::vector<Matrix> res = std::vector<Matrix>(parameters_->entities().size(), Matrix(n_, n_, 0.0));
213
214 const std::vector<string>& matrixNames = parameters_->transitionMatrices();
215
216 // build terminal matrices conditional on global states
217 Size numWarnings = 0;
218 auto transMat = rescaledTransitionMatrices(date);
219 for (Size i = 0; i < parameters_->entities().size(); ++i) {
220 const Matrix& m = transMat.at(matrixNames[i]);
221 for (Size ii = 0; ii < m.rows(); ++ii) {
222 Real p = 0.0, condProb0 = 0.0;
223 for (Size jj = 0; jj < m.columns(); ++jj) {
224 p += m[ii][jj];
225 Real condProb = conditionalProb(p, globalStates_[date][i][path], globalVar_[i]);
226 res[i][ii][jj] = condProb - condProb0;
227 condProb0 = condProb;
228 }
229 }
230 try {
231 checkTransitionMatrix(res[i]);
232 } catch (const std::exception& e) {
233 if (++numWarnings <= 10) {
234 WLOG("Invalid conditional transition matrix (path=" << path << ", date=" << date << ", entity =" << i
235 << ": " << e.what());
236 } else if (numWarnings == 11) {
237 WLOG("Suppress further warnings on invalid conditional transition matrices");
238 }
240 }
241 }
242
243 // ... and finally build partial sums over columns for the simulation
244 for (Size i = 0; i < parameters_->entities().size(); ++i) {
245 Matrix& m = res[i];
246 for (Size ii = 0; ii < m.rows(); ++ii) {
247 for (Size jj = 1; jj < m.columns(); ++jj) {
248 m[ii][jj] += m[ii][jj - 1];
249 }
250 }
251 }
252
253 return res;
254}
255
256void CreditMigrationHelper::simulateEntityStates(const std::vector<Matrix>& cond, const Size path,
257 const MersenneTwisterUniformRng& mt) {
258
259 QL_REQUIRE(evaluation_ != Evaluation::Analytic,
260 "CreditMigrationHelper::simulateEntityStates() unexpected call, not in simulation mode");
261
262 for (Size i = 0; i < parameters_->entities().size(); ++i) {
263 Size initialState = parameters_->initialStates()[i];
264 Real tmp = mt.next().value;
265 Size entityState =
266 std::lower_bound(cond[i].row_begin(initialState), cond[i].row_end(initialState), tmp) -
267 cond[i].row_begin(initialState);
268 entityState = std::min(entityState, cond[i].columns() - 1); // play safe
269 simulatedEntityState_[i][path] = entityState;
270 initialState = entityState;
271 }
272
273} // simulateEntityStates
274
275Size CreditMigrationHelper::simulatedEntityState(const Size i, const Size path) const {
276 QL_REQUIRE(evaluation_ != Evaluation::Analytic,
277 "CreditMigrationHelper::simulatedEntityState() unexpected call, not in simulation mode");
278 return simulatedEntityState_[i][path];
279} // simulatedEntityState
280
281Real CreditMigrationHelper::generateMigrationPnl(const Size date, const Size path, const Size n) const {
282
283 QL_REQUIRE(!parameters_->doubleDefault(),
284 "CreditMigrationHelper::generateMigrationPnl() does not support double default");
285
286 const std::vector<string>& entities = parameters_->entities();
287 Real pnl = 0.0;
288
289 for (Size i = 0; i < entities.size(); ++i) {
290 // compute credit state of entitiy
291 // issuer migration risk
292 Size simEntityState = simulatedEntityState(i, path);
293 for (auto const& tradeId : issuerTradeIds_[i]) {
294 try {
295 Size tid = cube_->idsAndIndexes().at(tradeId);
296 Real baseValue = cube_->get(tid, date, path, 0);
297 Real stateValue = cube_->get(tid, date, path, cubeIndexStateNpvs_ + simEntityState);
299 if (tradeNotionals_.find(tradeId) != tradeNotionals_.end()) {
300 // this is a bond
301 string tradeCcy = tradeCurrencies_.at(tradeId);
302 string ccypair = tradeCcy + baseCurrency_;
303 Real fx = 1.0;
304 if (tradeCcy != baseCurrency_) {
305 QL_REQUIRE(aggData_->has(AggregationScenarioDataType::FXSpot, ccypair),
306 "FX spot data not found in aggregation data for currency pair " << ccypair);
307 fx = aggData_->get(date, path, AggregationScenarioDataType::FXSpot, ccypair);
308 }
309 // FIXME: We actually need the correct current notional as of the future horizon date,
310 // but we have the current notional as of today
311 baseValue = tradeNotionals_.at(tradeId) * fx;
312 // FIXME: get the bond's recovery rate
313 Real rr = 0.0;
314 stateValue = simEntityState == n - 1 ? rr * baseValue : baseValue;
315 }
316 if (tradeCdsCptyIdx_.find(tradeId) != tradeCdsCptyIdx_.end()) {
317 // this is a cds
318 baseValue = 0.0;
319 if (simEntityState < n - 1)
320 stateValue = 0.0;
321 else
322 stateValue *= aggData_->get(date, path, AggregationScenarioDataType::Numeraire);
323 }
324 }
325 if (creditMode_ == CreditMode::Default && simEntityState < n - 1) {
326 stateValue = baseValue;
327 }
328 pnl += stateValue - baseValue;
329 } catch (const std::exception& e) {
330 ALOG("can not get state npv for trade " << tradeId << " (reason:" << e.what() << "), state "
331 << simEntityState << ", assume zero credit migration pnl");
332 }
333 }
334 // default risk for derivative exposure
335 // TODO, assuming a zero recovery here...
336 for (auto const& nettingSetId : cptyNettingSetIds_[i]) {
337 Size nid = nettedCube_->idsAndIndexes().at(nettingSetId);
338 QL_REQUIRE(nettedCube_, "empty netted cube");
339 if (simEntityState == n - 1)
340 pnl -= std::max(nettedCube_->get(nid, date, path), 0.0);
341 }
342 }
343 return pnl;
344} // generateMigrationPnl
345
346void CreditMigrationHelper::generateConditionalMigrationPnl(const Size date, const Size path,
347 const std::map<string, Matrix>& transMat,
348 std::vector<Array>& condProbs,
349 std::vector<Array>& pnl) const {
350
351 Real t = cubeTimes_[date];
352
353 const std::vector<string>& entities = parameters_->entities();
354 const std::vector<string>& matrixNames = parameters_->transitionMatrices();
355
356 for (Size i = 0; i < entities.size(); ++i) {
357 // compute conditional migration prob
358 Size initialState = parameters_->initialStates()[i];
359 Real p = 0.0, condProb0 = 0.0;
360 const Matrix& m = transMat.at(matrixNames[i]);
361 for (Size j = 0; j < n_; ++j) {
362 p += m[initialState][j];
363 Real condProb = conditionalProb(p, globalStates_[date][i][path], globalVar_[i]);
364 condProbs[i][j] = condProb - condProb0;
365 condProb0 = condProb;
366 }
367 // issuer migration risk
368 Size cdsCptyIdx = Null<Size>();
369 for (auto const& tradeId : issuerTradeIds_[i]) {
370 for (Size j = 0; j < n_; ++j) {
371 try {
372 Size tid = cube_->idsAndIndexes().at(tradeId);
373 Real baseValue = cube_->get(tid, date, path, 0);
374 Real stateValue = cube_->get(tid, date, path, cubeIndexStateNpvs_ + j);
376 if (tradeNotionals_.find(tradeId) != tradeNotionals_.end()) {
377 // this is a bond
378 string tradeCcy = tradeCurrencies_.at(tradeId);
379 string ccypair = tradeCcy + baseCurrency_;
380 Real fx = 1.0;
381 if (tradeCcy != baseCurrency_) {
382 QL_REQUIRE(aggData_->has(AggregationScenarioDataType::FXSpot, ccypair),
383 "FX spot data not found in aggregation data for currency pair " << ccypair);
384 fx = aggData_->get(date, path, AggregationScenarioDataType::FXSpot, ccypair);
385 }
386 // FIXME: We actually need the correct current notional as of the future horizon date,
387 // but we have the current notional as of today
388 baseValue = tradeNotionals_.at(tradeId) * fx;
389 // FIXME: get the bond's recovery rate
390 Real rr = 0.0;
391 stateValue = j == n_ - 1 ? rr * baseValue : baseValue;
392 }
393 if (tradeCdsCptyIdx_.find(tradeId) != tradeCdsCptyIdx_.end()) {
394 // this is a cds
395 baseValue = 0.0;
396 if (j < n_ - 1)
397 stateValue = 0.0;
398 else
399 stateValue *= aggData_->get(date, path, AggregationScenarioDataType::Numeraire);
400 }
401 }
402 if (creditMode_ == CreditMode::Default && j < n_ - 1) {
403 stateValue = baseValue;
404 }
405 pnl[i][j] += stateValue - baseValue;
406 // pnl for additional double default state
407 if (j == n_ - 1)
408 pnl[i][n_] += stateValue - baseValue;
409 // for a CDS we have to subdivide the default migration event into two events (see above)
410 if (parameters_->doubleDefault() && j == n_ - 1 &&
411 tradeCdsCptyIdx_.find(tradeId) != tradeCdsCptyIdx_.end()) {
412 // FIXME currently we can not handle two CDS cptys for same underlying issuer
413 QL_REQUIRE(cdsCptyIdx == Null<Size>() || cdsCptyIdx == tradeCdsCptyIdx_.at(tradeId),
414 "CreditMigrationHelper: Two different CDS cptys found for same issuer "
415 << entities[i]);
416 // only adjust probability once
417 if (cdsCptyIdx == Null<Size>()) {
418 Real cptyDefaultPd =
419 transMat.at(matrixNames[tradeCdsCptyIdx_.at(tradeId)])[initialState][n_ - 1];
420 Real pd = prob_tauA_lt_tauB_lt_T(cptyDefaultPd, condProbs[i][n_ - 1], t);
421 QL_REQUIRE(pd <= condProbs[i][n_ - 1],
422 "CreditMigrationHelper: unexpected probability for double default event "
423 << pd << " > " << condProbs[i][n_ - 1]);
424 condProbs[i][n_ - 1] -= pd;
425 condProbs[i][n_] = pd;
426 cdsCptyIdx = tradeCdsCptyIdx_.at(tradeId);
427 // pnl for new state is zero
428 pnl[i][n_] -= stateValue - baseValue;
429 }
430 }
431 } catch (const std::exception& e) {
432 ALOG("can not get state npv for trade " << tradeId << " (reason:" << e.what() << "), state " << j
433 << ", assume zero credit migration pnl");
434 }
435 }
436 }
437 // default risk for derivative exposure
438 // TODO, assuming a zero recovery here...
439 for (auto const& nettingSetId : cptyNettingSetIds_[i]) {
440 auto nid = nettedCube_->idsAndIndexes().at(nettingSetId);
441 QL_REQUIRE(nettedCube_, "empty netted cube");
442 pnl[i][n_ - 1] -= std::max(nettedCube_->get(nid, date, path), 0.0);
443 }
444 }
445} // generateConditionalMigrationPnl
446
448
449 // FIXME if we ask this method for more than one time step, it might be more efficient to
450 // pass a vector of those time steps and compute the distirbutions in one sweep here
451 // in particular step 2b-1 (in forward simulation mode)
452
453 LOG("Compute PnL distribution for date " << date);
454 QL_REQUIRE(date < cube_->numDates(), "date index " << date << " out of range 0..." << cube_->numDates() - 1);
455 const std::vector<string>& entities = parameters_->entities();
456
457 // 1 get transition matrices for entities and rescale them to horizon
458
459 std::map<string, Matrix> transMat; // rescaled transition matrix per (matrix) name
460
461 // 1 get transition matrices for time step and compute variance of global factors for each entiity
462
463 if (parameters_->creditRisk()) {
464 transMat = rescaledTransitionMatrices(date);
465 }
466
467 // 2 compute conditional pnl distributions and average over paths
468
469 const std::set<std::string>& tradeIds = cube_->ids();
470 Array res(bucketing_.buckets(), 0.0);
471
472 Size numPaths = cube_->samples();
473 Real avgCash = 0.0;
474
476
477 MersenneTwisterUniformRng mt(parameters_->seed());
478
479 for (Size path = 0; path < numPaths; ++path) {
480
481 // 2a market pnl (t0 to horizon date, over whole cube)
482
483 Real cash = 0.0;
484
485 if (parameters_->marketRisk()) {
486 for (Size j = 0; j <= date + 1; ++j) {
487 for (auto const& tradeId : tradeIds) {
488 Size i = cube_->idsAndIndexes().at(tradeId);
489 // get cumulative survival probability on the path
490 Real sp = 1.0;
491 //Real rr = 0.0;
492 // FIXME 1
493 // Methodology question: Do we need/want to multiply with the stochastic discount factor
494 // here if we do an explicit credit default simulation at horizon?
495 // FIXME 2
496 // make CDS PnL neutral bei weighting flows with surv prob and generating protection flow
497 // with default prob
498 if (parameters_->zeroMarketPnl() && j > 0 &&
499 tradeCreditCurves_.find(tradeId) != tradeCreditCurves_.end()) {
500 string creditCurve = tradeCreditCurves_.at(tradeId);
501 sp = aggData_->get(j - 1, path, AggregationScenarioDataType::SurvivalWeight, creditCurve);
502 //rr = aggData_->get(j - 1, path, AggregationScenarioDataType::RecoveryRate, creditCurve);
503 }
504 if (j == 0) {
505 // at t0 we flip the sign of the npvs to get the initial cash balance
506 cash -= cube_->getT0(i, 0);
507 // collect intermediate cashflows
508 if (cubeIndexCashflows_ != Null<Size>())
509 cash += cube_->getT0(i, cubeIndexCashflows_);
510 } else if (j <= date) {
511 // collect intermediate cashflows
512 if (cubeIndexCashflows_ != Null<Size>())
513 cash += sp * cube_->get(i, j - 1, path, cubeIndexCashflows_);
514 } else {
515 // at the horizon date we realise the npv
516 cash += sp * cube_->get(i, j - 1, path, 0);
517 }
518 }
519 } // for data
520 } // if market risk
521
522 if (!parameters_->creditRisk()) {
523 // if we just add scalar market pnl realisations, we don't really need
524 // the bucketing algorithm to do that, we just update the result
525 // distribution directly
526 res[hwBucketing.index(cash)] += 1.0 / static_cast<Real>(numPaths);
527 continue;
528 }
529
530 // 2b credit migration pnl (at horizon date, over entities specified in credit simulation parameters)
531
532 std::vector<Array> condProbs, pnl;
533
535 // 2b-1 generate pnl on the path using simulated idiosyncratic factors
536 condProbs.resize(1, Array(parameters_->paths(), 1.0 / static_cast<Real>(parameters_->paths())));
537 // we could build the distribution more efficiently here, but later in 2c we add the market pnl
538 // maybe extend the hw bucketing so that we can feed precomputed distributions and just update
539 // these with additional data?
540 pnl.resize(1, Array(parameters_->paths(), 0.0));
541 auto cond = initEntityStateSimulation(date, path);
542 for (Size path2 = 0; path2 < parameters_->paths(); ++path2) {
543 simulateEntityStates(cond, path, mt);
544 pnl[0][path2] = generateMigrationPnl(date, path, n_);
545 }
546 } else {
547 // 2b-2 generate pnl distribution without simulation of idiosyncratic factors using the conditional
548 // independence of migration on the path / systemic factors
549
550 // n+1 states, since for CDS we have to subdivide the issuer default into
551 // i) default of issuer and non-default of CDS cpty
552 // ii) default of issuer, default of CDS cpty (but after the issuer default)
553 // iii) default of issuer, default of CDS cpty (before the issuer default)
554 // for non-CDS trades for all sub-states the pnl will be set to the same value
555 // for CDS trades i)+ii) will have the same pnl, but iii) will have a zero pnl
556 // in total, we only have to distinguish i)+ii) and iii), i.e. we need one
557 // additional state
558
559 condProbs.resize(entities.size(), Array(n_ + 1, 0.0));
560 pnl.resize(entities.size(), Array(n_ + 1, 0.0));
561 generateConditionalMigrationPnl(date, path, transMat, condProbs, pnl);
562 }
563
564 // 2c aggregate market pnl and credit migration pnl
565
566 if (parameters_->marketRisk()) {
567 condProbs.push_back(Array(1, 1.0));
568 pnl.push_back(Array(1, cash));
569 }
570
571 hwBucketing.computeMultiState(condProbs.begin(), condProbs.end(), pnl.begin());
572
573 // 2d add pnl contribution of path to result distribution
574 res += hwBucketing.probability() / static_cast<Real>(numPaths);
575 // average market risk pnl
576 avgCash += cash / static_cast<Real>(numPaths);
577
578 } // for path
579
580 DLOG("Expected Market Risk PnL at date " << date << ": " << avgCash);
581 return res;
582} // pnlDistribution
583
584void CreditMigrationHelper::build(const std::map<std::string, QuantLib::ext::shared_ptr<Trade>>& trades) {
585 LOG("CreditMigrationHelper: Build trade ID map");
586 issuerTradeIds_.resize(parameters_->entities().size());
587 cptyNettingSetIds_.resize(parameters_->entities().size());
588 for (Size i = 0; i < parameters_->entities().size(); ++i) {
589 string entity = parameters_->entities()[i];
590 std::set<string> tmp, tmp2;
591 for (const auto& [_, t] : trades) {
592 if (t->issuer() == entity) {
593 tmp.insert(t->id());
594 }
595 if (t->envelope().counterparty() == entity &&
596 std::find(parameters_->nettingSetIds().begin(), parameters_->nettingSetIds().end(),
597 t->envelope().nettingSetId()) != parameters_->nettingSetIds().end()) {
598 tmp2.insert(t->envelope().nettingSetId());
599 }
600 QuantLib::ext::shared_ptr<Bond> bond = QuantLib::ext::dynamic_pointer_cast<Bond>(t);
601 if (bond) {
602 tradeCreditCurves_[t->id()] = bond->bondData().creditCurveId();
603 // FIXME: We actually need the notional schedule here to determine future notionals
604 tradeNotionals_[t->id()] = bond->notional();
605 tradeCurrencies_[t->id()] = bond->bondData().currency();
606 }
607 QuantLib::ext::shared_ptr<CreditDefaultSwap> cds = QuantLib::ext::dynamic_pointer_cast<CreditDefaultSwap>(t);
608 if (cds) {
609 string cpty = cds->envelope().counterparty();
610 QL_REQUIRE(cpty != t->issuer(), "CDS has same CPTY and issuer " << cpty);
611 Size idx = std::find(parameters_->entities().begin(), parameters_->entities().end(), cpty) -
612 parameters_->entities().begin();
613 if (idx < parameters_->entities().size())
614 tradeCdsCptyIdx_[t->id()] = idx;
615 else
616 WLOG("CreditMigrationHelepr: CDS trade "
617 << t->id() << " has cpty " << cpty
618 << " which is not in the list of simulated entities, "
619 "ignore joint default event of issuer and cpty for this CDS");
620 }
621 }
622 issuerTradeIds_[i] = tmp;
623 cptyNettingSetIds_[i] = tmp2;
624 }
625 LOG("CreditMigrationHelper: Built issuer and cpty trade ID sets for " << parameters_->entities().size()
626 << " entities.");
627 for (Size i = 0; i < parameters_->entities().size(); ++i) {
628 DLOG("Entity " << parameters_->entities()[i] << ": " << issuerTradeIds_[i].size()
629 << " trades with issuer risk, " << cptyNettingSetIds_[i].size()
630 << " nettings sets with derivative exposure risk.");
631 }
632}
633
635 static map<string, CreditMigrationHelper::CreditMode> m = {
638
639 auto it = m.find(s);
640 if (it != m.end()) {
641 return it->second;
642 } else {
643 QL_FAIL("Credit Mode \"" << s << "\" not recognized");
644 }
645}
646
648 static map<string, CreditMigrationHelper::LoanExposureMode> m = {
651
652 auto it = m.find(s);
653 if (it != m.end()) {
654 return it->second;
655 } else {
656 QL_FAIL("Loan EAD \"" << s << "\" not recognized");
657 }
658}
659
661 static map<string, CreditMigrationHelper::Evaluation> m = {
664
665 auto it = m.find(s);
666 if (it != m.end()) {
667 return it->second;
668 } else {
669 QL_FAIL("Evaluation \"" << s << "\" not recognized");
670 }
671}
672
673} // namespace analytics
674} // namespace ore
Size buckets() const
Size index(const Real x) const
const std::vector< Real > & upperBucketBound() const
void computeMultiState(I1 pBegin, I1 pEnd, I2 lossesBegin)
const Array & probability() const
std::vector< std::set< std::string > > issuerTradeIds_
std::map< string, Matrix > rescaledTransitionMatrices(const Size date)
void initEntityStateSimulation()
Allocate 3d storage for the simulated idiosyncratic factors by entity, date and sample.
QuantLib::ext::shared_ptr< NPVCube > nettedCube_
QuantLib::ext::shared_ptr< CreditSimulationParameters > parameters_
CreditMigrationHelper(const QuantLib::ext::shared_ptr< CreditSimulationParameters > parameters, const QuantLib::ext::shared_ptr< NPVCube > cube, const QuantLib::ext::shared_ptr< NPVCube > nettedCube, const QuantLib::ext::shared_ptr< AggregationScenarioData > aggData, const Size cubeIndexCashflows, const Size cubeIndexStateNpvs, const Real distributionLowerBound, const Real distributionUpperBound, const Size buckets, const Matrix &globalFactorCorrelation, const std::string &baseCurrency)
void build(const std::map< std::string, QuantLib::ext::shared_ptr< Trade > > &trades)
builds the helper for a specific subset of trades stored in the cube
Size simulatedEntityState(const Size i, const Size path) const
Look up the simulated entity credit state for the given entity, date and path.
std::vector< std::vector< std::vector< Real > > > globalStates_
std::vector< std::set< std::string > > cptyNettingSetIds_
void generateConditionalMigrationPnl(const Size date, const Size path, const std::map< string, Matrix > &transMat, std::vector< Array > &condProbs, std::vector< Array > &pnl) const
std::map< std::string, std::string > tradeCreditCurves_
QuantLib::ext::shared_ptr< AggregationScenarioData > aggData_
std::map< std::string, Size > tradeCdsCptyIdx_
std::map< std::string, Real > tradeNotionals_
std::map< std::string, std::string > tradeCurrencies_
std::vector< std::map< string, Matrix > > rescaledTransitionMatrices_
std::vector< std::vector< Size > > simulatedEntityState_
Real generateMigrationPnl(const Size date, const Size path, const Size n) const
QuantLib::ext::shared_ptr< NPVCube > cube_
void simulateEntityStates(const std::vector< Matrix > &cond, const Size path, const MersenneTwisterUniformRng &mt)
Credit migration helper class.
#define LOG(text)
#define DLOG(text)
#define ALOG(text)
#define WLOG(text)
#define DLOGGERSTREAM(text)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
QuantLib::Matrix Expm(const QuantLib::Matrix &m)
void checkTransitionMatrix(const Matrix &t)
void checkGeneratorMatrix(const Matrix &g)
Matrix generator(const Matrix &t, const Real horizon)
void sanitiseTransitionMatrix(Matrix &m)
CreditMigrationHelper::LoanExposureMode parseLoanExposureMode(const std::string &s)
CreditMigrationHelper::Evaluation parseEvaluation(const std::string &s)
CreditMigrationHelper::CreditMode parseCreditMode(const std::string &s)
Size size(const ValueType &v)
Date asof(14, Jun, 2018)