22#include <ql/errors.hpp>
23#include <ql/time/calendars/weekendsonly.hpp>
25#include <ql/math/distributions/normaldistribution.hpp>
26#include <ql/math/generallinearleastsquares.hpp>
27#include <ql/math/kernelfunctions.hpp>
28#include <ql/methods/montecarlo/lsmbasissystem.hpp>
29#include <ql/time/daycounters/actualactual.hpp>
34#include <boost/accumulators/accumulators.hpp>
35#include <boost/accumulators/statistics/error_of_mean.hpp>
36#include <boost/accumulators/statistics/mean.hpp>
37#include <boost/accumulators/statistics/stats.hpp>
42using namespace boost::accumulators;
48 const QuantLib::ext::shared_ptr<InputParameters>& inputs,
49 const QuantLib::ext::shared_ptr<Portfolio>& portfolio,
const QuantLib::ext::shared_ptr<NPVCube>& cube,
50 const QuantLib::ext::shared_ptr<CubeInterpretation>& cubeInterpretation,
51 const QuantLib::ext::shared_ptr<AggregationScenarioData>& scenarioData, Real quantile, Size horizonCalendarDays,
52 Size regressionOrder, std::vector<std::string> regressors, Size localRegressionEvaluations,
53 Real localRegressionBandWidth,
const std::map<std::string, Real>& currentIM)
56 regressionOrder_(regressionOrder), regressors_(regressors),
57 localRegressionEvaluations_(localRegressionEvaluations), localRegressionBandWidth_(localRegressionBandWidth) {
58 Size dates =
cube_->dates().size();
59 Size samples =
cube_->samples();
61 regressorArray_[nettingSetId] = vector<vector<Array>>(dates, vector<Array>(samples));
62 nettingSetLocalDIM_[nettingSetId] = vector<vector<Real>>(dates, vector<Real>(samples, 0.0));
69const vector<vector<Real>>&
74 QL_FAIL(
"netting set " << nettingSet <<
" not found in Local DIM results");
81 QL_FAIL(
"netting set " << nettingSet <<
" not found in Zero Order DIM results");
88 QL_FAIL(
"netting set " << nettingSet <<
" not found in Simple DIM (p) results");
95 QL_FAIL(
"netting set " << nettingSet <<
" not found in Simple DIM (c) results");
99 LOG(
"DIM Analysis by polynomial regression");
104 Size samples =
cube_->samples();
108 LsmBasisSystem::PolynomialType polynomType = LsmBasisSystem::Monomial;
110 LOG(
"DIM regression dimension = " << regressionDimension);
111 std::vector<ext::function<Real(Array)>> v(
112 LsmBasisSystem::multiPathBasisSystem(regressionDimension, polynomOrder, polynomType));
113 Real confidenceLevel = QuantLib::InverseCumulativeNormal()(
quantile_);
114 LOG(
"DIM confidence level " << confidenceLevel);
116 Size simple_dim_index_h = Size(floor(
quantile_ * (samples - 1) + 0.5));
117 Size simple_dim_index_p = Size(floor((1.0 -
quantile_) * (samples - 1) + 0.5));
119 Size nettingSetCount = 0;
121 LOG(
"Process netting set " << n);
132 TimeSeries<Real> im =
inputs_->deterministicInitialMargin(n);
133 LOG(
"External IM evolution for netting set " << n <<
" has size " << im.size());
135 WLOG(
"Try overriding DIM with externally provided IM evolution for netting set " << n);
136 for (Size j = 0; j < stopDatesLoop; ++j) {
137 Date d =
cube_->dates()[j];
144 for (Size k = 0; k < samples; ++k) {
148 }
catch(std::exception& ) {
149 QL_FAIL(
"Failed to lookup external IM for netting set " << n
150 <<
" at date " << io::iso_date(d));
153 WLOG(
"Overriding DIM for netting set " << n <<
" succeeded");
161 QL_REQUIRE(currentDim.find(n) != currentDim.end(),
"current DIM not found for netting set " << n);
162 Real t0dim = currentDim[n];
163 Real t0scaling = t0im / t0dim;
164 LOG(
"t0 scaling for netting set " << n <<
": t0im" << t0im <<
" t0dim=" << t0dim
165 <<
" t0scaling=" << t0scaling);
169 Real nettingSetDimScaling =
171 LOG(
"Netting set DIM scaling factor: " << nettingSetDimScaling);
173 for (Size j = 0; j < stopDatesLoop; ++j) {
174 accumulator_set<double, stats<boost::accumulators::tag::mean, boost::accumulators::tag::variance>> accDiff;
175 accumulator_set<double, stats<boost::accumulators::tag::mean>> accOneOverNumeraire;
176 for (Size k = 0; k < samples; ++k) {
184 accDiff((npvCloseOut * numCloseOut) + (flow * numDefault) - (npvDefault * numDefault));
185 accOneOverNumeraire(1.0 / numDefault);
191 Real stdevDiff =
sqrt(boost::accumulators::variance(accDiff));
192 Real E_OneOverNumeraire =
193 mean(accOneOverNumeraire);
198 vector<Real> rx0(samples, 0.0);
199 vector<Array> rx(samples, Array());
200 vector<Real> ry1(samples, 0.0);
201 vector<Real> ry2(samples, 0.0);
202 for (Size k = 0; k < samples; ++k) {
210 Real z = (y + f - x);
219 sort(delNpvVec_copy.begin(), delNpvVec_copy.end());
220 Real simpleDim_h = delNpvVec_copy[simple_dim_index_h];
221 Real simpleDim_p = delNpvVec_copy[simple_dim_index_p];
222 simpleDim_h *= horizonScaling;
223 simpleDim_p *= horizonScaling;
227 QL_REQUIRE(rx.size() > v.size(),
"not enough points for regression with polynom order " << polynomOrder);
229 LOG(
"DIM: Zero std dev estimation at step " << j);
231 for (Size k = 0; k < samples; ++k) {
238 LOG(
"DIM data normalisation at time step "
239 << j <<
": " << scientific << setprecision(6) <<
" x-shift = " << ls.
xShift() <<
" x-multiplier = "
241 LOG(
"DIM regression coefficients at time step " << j <<
": " << fixed << setprecision(6)
251 Size localRegressionSamples = samples;
256 for (Size k = 0; k < samples; ++k) {
261 Real e = ls.
eval(regressor, v);
263 LOG(
"Negative variance regression for date " << j <<
", sample " << k
264 <<
", regressor = " << regressor);
272 Real std =
sqrt(std::max(e, 0.0));
273 Real scalingFactor = horizonScaling * confidenceLevel * nettingSetDimScaling;
275 Real dim = std * scalingFactor / numDefault;
276 dimCube_->set(dim, nettingSetCount, j, k);
292 LOG(
"DIM by polynomial regression done");
300 if (boost::to_upper_copy(variable) ==
305 dateIndex, sampleIndex, variable);
308 sampleIndex, variable);
311 sampleIndex, variable);
313 QL_FAIL(
"scenario data does not provide data for " << variable);
324 Date today =
cube_->asof();
325 Size relevantDateIdx = 0;
326 Real sqrtTimeScaling = 1.0;
327 for (Size i = 0; i <
cube_->dates().
size(); ++i) {
328 Size daysFromT0 = (
cube_->dates()[i] - today);
335 sqrtTimeScaling = 1.0;
339 Size lastIdx = (i == 0) ? 0 : (i - 1);
340 Size lastDaysFromT0 = (
cube_->dates()[lastIdx] - today);
343 if (std::abs(daysFromT0CloseOut) <= std::abs(prevDaysFromT0CloseOut)) {
347 relevantDateIdx = lastIdx;
354 if (sqrtTimeScaling < std::sqrt(0.5) || sqrtTimeScaling > std::sqrt(2.0)) {
355 WLOG(
"T0 IM Estimation - The estimation time horizon from grid is not sufficiently close to t0+MPOR - "
356 << QuantLib::io::iso_date(
cube_->dates()[relevantDateIdx])
357 <<
", the T0 IM estimate might be inaccurate. Consider inserting a first grid tenor closer to the dim "
363 Real confidenceLevel = QuantLib::InverseCumulativeNormal()(
quantile_);
364 Size simple_dim_index_h = Size(floor(
quantile_ * (
cube_->samples() - 1) + 0.5));
365 map<string, Real> t0dimReg, t0dimSimple;
367 string key = it_map->first;
368 vector<Real> t0_dist = it_map->second[relevantDateIdx];
369 Size dist_size = t0_dist.size();
370 QL_REQUIRE(dist_size ==
cube_->samples(),
371 "T0 IM - cube samples size mismatch - " << dist_size <<
", " <<
cube_->samples());
372 Real mean_t0_dist = std::accumulate(t0_dist.begin(), t0_dist.end(), 0.0);
373 mean_t0_dist /= dist_size;
374 vector<Real> t0_delMtM_dist(dist_size, 0.0);
375 accumulator_set<double, stats<boost::accumulators::tag::mean, boost::accumulators::tag::variance>> acc_delMtm;
376 accumulator_set<double, stats<boost::accumulators::tag::mean>> acc_OneOverNum;
377 for (Size i = 0; i < dist_size; ++i) {
379 Real deltaMtmFromMean = numeraire * (t0_dist[i] - mean_t0_dist) * sqrtTimeScaling;
380 t0_delMtM_dist[i] = deltaMtmFromMean;
381 acc_delMtm(deltaMtmFromMean);
382 acc_OneOverNum(1.0 / numeraire);
384 Real E_OneOverNumeraire = mean(acc_OneOverNum);
385 Real variance_t0 = boost::accumulators::variance(acc_delMtm);
386 Real sqrt_t0 =
sqrt(variance_t0);
387 t0dimReg[key] = (sqrt_t0 * confidenceLevel * E_OneOverNumeraire);
388 std::sort(t0_delMtM_dist.begin(), t0_delMtM_dist.end());
389 t0dimSimple[key] = (t0_delMtM_dist[simple_dim_index_h] * E_OneOverNumeraire);
391 LOG(
"T0 IM (Reg) - {" << key <<
"} = " << t0dimReg[key]);
392 LOG(
"T0 IM (Simple) - {" << key <<
"} = " << t0dimSimple[key]);
394 LOG(
"T0 IM Calculations Completed");
405 dimEvolutionReport.
addColumn(
"TimeStep", Size())
415 for (
const auto& [nettingSet, _] :
dimCube_->idsAndIndexes()) {
417 LOG(
"Export DIM evolution for netting set " << nettingSet);
418 for (Size i = 0; i < stopDatesLoop; ++i) {
419 Real expectedFlow = 0.0;
420 for (Size j = 0; j < samples; ++j) {
421 expectedFlow +=
nettingSetFLOW_.find(nettingSet)->second[i][j] / samples;
424 Date defaultDate =
dimCube_->dates()[i];
425 Time t = ActualActual(ActualActual::ISDA).yearFraction(
asof, defaultDate);
427 dimEvolutionReport.
next()
439 dimEvolutionReport.
end();
440 LOG(
"Exporting expected DIM through time done");
444 const std::string& nettingSet,
const std::vector<Size>& timeSteps,
445 const std::vector<QuantLib::ext::shared_ptr<ore::data::Report>>& dimRegReports) {
447 QL_REQUIRE(dimRegReports.size() == timeSteps.size(),
448 "number of file names (" << dimRegReports.size() <<
") does not match number of time steps ("
449 << timeSteps.size() <<
")");
450 for (Size ii = 0; ii < timeSteps.size(); ++ii) {
451 Size timeStep = timeSteps[ii];
452 LOG(
"Export DIM by sample for netting set " << nettingSet <<
" and time step " << timeStep);
454 Size dates =
dimCube_->dates().size();
455 const std::map<std::string, Size>& ids =
dimCube_->idsAndIndexes();
457 auto it = ids.find(nettingSet);
459 QL_REQUIRE(it != ids.end(),
"netting set " << nettingSet <<
" not found in DIM cube");
461 QL_REQUIRE(timeStep < dates - 1,
"selected time step " << timeStep <<
" out of range [0, " << dates - 1 <<
"]");
463 Size samples =
cube_->samples();
464 vector<Real> numeraires(samples, 0.0);
465 for (Size k = 0; k < samples; ++k)
477 QuantLib::ext::shared_ptr<ore::data::Report> regReport = dimRegReports[ii];
478 regReport->addColumn(
"Sample", Size());
479 for (Size k = 0; k < reg[0].size(); ++k) {
481 o <<
"Regressor_" << k <<
"_";
483 regReport->addColumn(o.str(), Real(), 6);
485 regReport->addColumn(
"RegressionDIM", Real(), 6)
486 .addColumn(
"LocalDIM", Real(), 6)
487 .addColumn(
"ExpectedDIM", Real(), 6)
488 .addColumn(
"ZeroOrderDIM", Real(), 6)
489 .addColumn(
"DeltaNPV", Real(), 6)
490 .addColumn(
"SimpleDIM", Real(), 6);
496 for (Size j = 0; j < reg.size(); ++j) {
497 regReport->next().add(j);
498 for (Size k = 0; k < reg[j].size(); ++k)
499 regReport->add(reg[j][k]);
500 regReport->add(dim[j] * num[j])
501 .add(ldim[j] * num[j])
508 LOG(
"Exporting DIM by Sample done for");
Real standardDeviation(Real x) const
const Real yShift() const
const Array & transformedCoefficients() const
Real eval(xType x, vContainer &v, typename boost::enable_if< typename boost::is_arithmetic< xType >::type >::type *=0)
const Real yMultiplier() const
const Array & xMultiplier() const
const Array & xShift() const
Dynamic Initial Margin Calculator base class.
map< string, vector< vector< Real > > > nettingSetNPV_
Size horizonCalendarDays_
std::set< string > nettingSetIds_
map< string, vector< vector< Real > > > nettingSetCloseOutNPV_
map< string, vector< vector< Real > > > nettingSetDeltaNPV_
map< string, Real > nettingSetScaling_
map< string, vector< Real > > nettingSetExpectedDIM_
QuantLib::ext::shared_ptr< AggregationScenarioData > scenarioData_
QuantLib::ext::shared_ptr< CubeInterpretation > cubeInterpretation_
QuantLib::ext::shared_ptr< NPVCube > dimCube_
map< string, vector< vector< Real > > > nettingSetFLOW_
map< string, Real > currentIM_
QuantLib::ext::shared_ptr< InputParameters > inputs_
map< string, vector< vector< Real > > > nettingSetDIM_
QuantLib::ext::shared_ptr< NPVCube > cube_
map< string, vector< vector< Array > > > regressorArray_
const vector< Real > & zeroOrderResults(const string &nettingSet)
vector< string > regressors_
void exportDimRegression(const std::string &nettingSet, const std::vector< Size > &timeSteps, const std::vector< QuantLib::ext::shared_ptr< ore::data::Report > > &dimRegReports)
map< string, vector< vector< Real > > > nettingSetLocalDIM_
Real localRegressionBandWidth_
void exportDimEvolution(ore::data::Report &dimEvolutionReport) const override
DIM evolution report.
map< string, vector< Real > > nettingSetSimpleDIMh_
Array regressorArray(string nettingSet, Size dateIndex, Size sampleIndex)
Compile the array of DIM regressors for the specified netting set, date and sample index.
const vector< vector< Real > > & localRegressionResults(const string &nettingSet)
RegressionDynamicInitialMarginCalculator(const QuantLib::ext::shared_ptr< InputParameters > &inputs, const QuantLib::ext::shared_ptr< Portfolio > &portfolio, const QuantLib::ext::shared_ptr< NPVCube > &cube, const QuantLib::ext::shared_ptr< CubeInterpretation > &cubeInterpretation, const QuantLib::ext::shared_ptr< AggregationScenarioData > &scenarioData, Real quantile, Size horizonCalendarDays, Size regressionOrder, vector< string > regressors, Size localRegressionEvaluations=0, Real localRegressionBandWidth=0, const std::map< std::string, Real > ¤tIM=std::map< std::string, Real >())
void build() override
Compute dynamic initial margin along all paths and fill result structures.
const vector< Real > & simpleResultsLower(const string &nettingSet)
const vector< Real > & simpleResultsUpper(const string &nettingSet)
map< string, vector< Real > > nettingSetZeroOrderDIM_
Size localRegressionEvaluations_
map< string, Real > unscaledCurrentDIM() override
Model implied t0 DIM by netting set, does not need a call to build() before.
map< string, vector< Real > > nettingSetSimpleDIMp_
virtual Report & add(const ReportType &rt)=0
virtual Report & next()=0
virtual Report & addColumn(const string &name, const ReportType &, Size precision=0)=0
SafeStack< ValueType > value
Dynamic Initial Margin calculator by regression.
std::vector< T > apply_permutation(const std::vector< T > &vec, const std::vector< std::size_t > &p)
std::vector< std::size_t > sort_permutation(const std::vector< T > &vec, Compare &compare)
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Size size(const ValueType &v)
bool lessThan(const string &s1, const string &s2)