32#include <ql/math/distributions/normaldistribution.hpp>
33#include <ql/time/daycounters/actualactual.hpp>
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),
55 bucketing_(distributionLowerBound, distributionUpperBound, buckets) {
65Real conditionalProb(
const Real p,
const Real m,
const Real v) {
66 QuantLib::CumulativeNormalDistribution nd;
67 QuantLib::InverseCumulativeNormal icn;
74 return icnP >= m ? 1.0 : 0.0;
75 return nd((icnP - m) / std::sqrt(1.0 - v));
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));
89 std::map<string, Matrix> transMat;
97 const std::vector<string>& entities =
parameters_->entities();
98 const std::vector<string>& matrixNames =
parameters_->transitionMatrices();
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.");
105 "No transition matrix defined for " <<
parameters_->entities()[i] <<
" / "
108 DLOG(
"Transition matrix (1y) for " <<
parameters_->entities()[i] <<
" is "
111 if (
n_ == Null<Size>()) {
114 QL_REQUIRE(m.rows() ==
n_,
"Found transition matrix with different dimension "
115 << m.rows() <<
"x" << m.columns() <<
" expected " <<
n_ <<
"x" <<
n_
119 QL_REQUIRE(date < cube_->numDates(),
120 "date index " << date <<
" outside range, cube has " <<
cube_->numDates() <<
" dates.");
122 DLOG(
"Sanitised transition matrix:");
125 DLOG(
"Generator matrix:")
129 DLOG(
"Scaled transition matrix (t=" << t <<
"):");
133 for (Size c = 1; c < static_cast<Size>(std::round(t)); ++c) {
136 DLOG(
"Elementary transition matrix (t=" << std::round(t) <<
", just for plausibility):");
138 transMat[matrixNames[i]] = mt;
149 LOG(
"CreditMigrationHelper Init");
154 for (Size d = 0; d <
cube_->numDates(); ++d) {
155 Date tDate =
cube_->dates()[d];
157 ActualActual(ActualActual::ISDA).yearFraction(
asof, tDate));
160 const std::vector<Array>& loadings =
parameters_->factorLoadings();
165 QL_REQUIRE(
parameters_->factorLoadings()[i].size() == f,
"wrong size for loadings for entity "
167 << loadings.size() <<
"), expected " << f);
169 for (Size j = 0; j < loadings[i].size(); ++j) {
170 for (Size k = 0; k < loadings[i].size(); ++k) {
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;
183 numStr[i] = num.str();
185 for (Size d = 0; d <
cube_->numDates(); ++d) {
187 for (Size j = 0; j <
cube_->samples(); ++j) {
188 for (Size ii = 0; ii < f; ++ii) {
196 LOG(
"CreditMigration Init done.");
202 LOG(
"Init entity state simulation");
205 std::vector<std::vector<Size>>(
parameters_->entities().size(), std::vector<Size>(
cube_->samples()));
207 LOG(
"Init entity state simulation done.");
212 std::vector<Matrix> res = std::vector<Matrix>(
parameters_->entities().size(), Matrix(
n_,
n_, 0.0));
214 const std::vector<string>& matrixNames =
parameters_->transitionMatrices();
217 Size numWarnings = 0;
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) {
226 res[i][ii][jj] = condProb - condProb0;
227 condProb0 = condProb;
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");
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];
257 const MersenneTwisterUniformRng& mt) {
260 "CreditMigrationHelper::simulateEntityStates() unexpected call, not in simulation mode");
263 Size initialState =
parameters_->initialStates()[i];
264 Real tmp = mt.next().value;
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);
270 initialState = entityState;
277 "CreditMigrationHelper::simulatedEntityState() unexpected call, not in simulation mode");
284 "CreditMigrationHelper::generateMigrationPnl() does not support double default");
286 const std::vector<string>& entities =
parameters_->entities();
289 for (Size i = 0; i < entities.size(); ++i) {
295 Size tid =
cube_->idsAndIndexes().at(tradeId);
296 Real baseValue =
cube_->get(tid, date, path, 0);
306 "FX spot data not found in aggregation data for currency pair " << ccypair);
314 stateValue = simEntityState == n - 1 ? rr * baseValue : baseValue;
319 if (simEntityState < n - 1)
326 stateValue = baseValue;
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");
337 Size nid =
nettedCube_->idsAndIndexes().at(nettingSetId);
339 if (simEntityState == n - 1)
340 pnl -= std::max(
nettedCube_->get(nid, date, path), 0.0);
347 const std::map<string, Matrix>& transMat,
348 std::vector<Array>& condProbs,
349 std::vector<Array>& pnl)
const {
353 const std::vector<string>& entities =
parameters_->entities();
354 const std::vector<string>& matrixNames =
parameters_->transitionMatrices();
356 for (Size i = 0; i < entities.size(); ++i) {
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];
364 condProbs[i][j] = condProb - condProb0;
365 condProb0 = condProb;
368 Size cdsCptyIdx = Null<Size>();
370 for (Size j = 0; j <
n_; ++j) {
372 Size tid =
cube_->idsAndIndexes().at(tradeId);
373 Real baseValue =
cube_->get(tid, date, path, 0);
383 "FX spot data not found in aggregation data for currency pair " << ccypair);
391 stateValue = j ==
n_ - 1 ? rr * baseValue : baseValue;
403 stateValue = baseValue;
405 pnl[i][j] += stateValue - baseValue;
408 pnl[i][
n_] += stateValue - baseValue;
413 QL_REQUIRE(cdsCptyIdx == Null<Size>() || cdsCptyIdx ==
tradeCdsCptyIdx_.at(tradeId),
414 "CreditMigrationHelper: Two different CDS cptys found for same issuer "
417 if (cdsCptyIdx == Null<Size>()) {
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;
428 pnl[i][
n_] -= stateValue - baseValue;
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");
440 auto nid =
nettedCube_->idsAndIndexes().at(nettingSetId);
442 pnl[i][
n_ - 1] -= std::max(
nettedCube_->get(nid, date, path), 0.0);
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();
459 std::map<string, Matrix> transMat;
469 const std::set<std::string>& tradeIds =
cube_->ids();
472 Size numPaths =
cube_->samples();
479 for (Size path = 0; path < numPaths; ++path) {
486 for (Size j = 0; j <= date + 1; ++j) {
487 for (
auto const& tradeId : tradeIds) {
488 Size i =
cube_->idsAndIndexes().at(tradeId);
506 cash -=
cube_->getT0(i, 0);
510 }
else if (j <= date) {
516 cash += sp *
cube_->get(i, j - 1, path, 0);
526 res[hwBucketing.
index(cash)] += 1.0 /
static_cast<Real
>(numPaths);
532 std::vector<Array> condProbs, pnl;
542 for (Size path2 = 0; path2 <
parameters_->paths(); ++path2) {
559 condProbs.resize(entities.size(), Array(
n_ + 1, 0.0));
560 pnl.resize(entities.size(), Array(
n_ + 1, 0.0));
567 condProbs.push_back(Array(1, 1.0));
568 pnl.push_back(Array(1, cash));
574 res += hwBucketing.
probability() /
static_cast<Real
>(numPaths);
576 avgCash += cash /
static_cast<Real
>(numPaths);
580 DLOG(
"Expected Market Risk PnL at date " << date <<
": " << avgCash);
585 LOG(
"CreditMigrationHelper: Build trade ID map");
590 std::set<string> tmp, tmp2;
591 for (
const auto& [_, t] : trades) {
592 if (t->issuer() == entity) {
595 if (t->envelope().counterparty() == entity &&
597 t->envelope().nettingSetId()) !=
parameters_->nettingSetIds().end()) {
598 tmp2.insert(t->envelope().nettingSetId());
600 QuantLib::ext::shared_ptr<Bond> bond = QuantLib::ext::dynamic_pointer_cast<Bond>(t);
607 QuantLib::ext::shared_ptr<CreditDefaultSwap> cds = QuantLib::ext::dynamic_pointer_cast<CreditDefaultSwap>(t);
609 string cpty = cds->envelope().counterparty();
610 QL_REQUIRE(cpty != t->issuer(),
"CDS has same CPTY and issuer " << cpty);
613 if (idx < parameters_->entities().
size())
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");
625 LOG(
"CreditMigrationHelper: Built issuer and cpty trade ID sets for " <<
parameters_->entities().size()
630 <<
" nettings sets with derivative exposure risk.");
635 static map<string, CreditMigrationHelper::CreditMode> m = {
643 QL_FAIL(
"Credit Mode \"" << s <<
"\" not recognized");
648 static map<string, CreditMigrationHelper::LoanExposureMode> m = {
656 QL_FAIL(
"Loan EAD \"" << s <<
"\" not recognized");
661 static map<string, CreditMigrationHelper::Evaluation> m = {
669 QL_FAIL(
"Evaluation \"" << s <<
"\" not recognized");
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)
std::vector< Real > cubeTimes_
std::vector< Real > globalVar_
void initEntityStateSimulation()
Allocate 3d storage for the simulated idiosyncratic factors by entity, date and sample.
std::string baseCurrency_
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
QuantExt::Bucketing bucketing_
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_
Matrix globalFactorCorrelation_
QuantLib::ext::shared_ptr< AggregationScenarioData > aggData_
LoanExposureMode loanExposureMode_
Array pnlDistribution(const Size date)
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 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)