Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
exposurecalculator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 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
21
23
24#include <ql/time/date.hpp>
25#include <ql/time/calendars/weekendsonly.hpp>
26
27using namespace std;
28using namespace QuantLib;
29
30namespace ore {
31namespace analytics {
32
34 const QuantLib::ext::shared_ptr<Portfolio>& portfolio, const QuantLib::ext::shared_ptr<NPVCube>& cube,
35 const QuantLib::ext::shared_ptr<CubeInterpretation> cubeInterpretation,
36 const QuantLib::ext::shared_ptr<Market>& market,
37 bool exerciseNextBreak, const string& baseCurrency, const string& configuration,
38 const Real quantile, const CollateralExposureHelper::CalculationType calcType, const bool multiPath,
39 const bool flipViewXVA)
40 : portfolio_(portfolio), cube_(cube), cubeInterpretation_(cubeInterpretation),
41 market_(market), exerciseNextBreak_(exerciseNextBreak),
42 baseCurrency_(baseCurrency), configuration_(configuration),
43 quantile_(quantile), calcType_(calcType),
44 multiPath_(multiPath), dates_(cube->dates()),
45 today_(market_->asofDate()), dc_(ActualActual(ActualActual::ISDA)), flipViewXVA_(flipViewXVA) {
46
47 QL_REQUIRE(portfolio_, "portfolio is null");
48
49 if (multiPath) {
50 exposureCube_ = QuantLib::ext::make_shared<SinglePrecisionInMemoryCubeN>(
51 market->asofDate(), portfolio_->ids(), dates_,
52 cube_->samples(), EXPOSURE_CUBE_DEPTH);// EPE, ENE, allocatedEPE, allocatedENE
53 } else {
54 exposureCube_ = QuantLib::ext::make_shared<DoublePrecisionInMemoryCubeN>(
55 market->asofDate(), portfolio_->ids(), dates_,
56 1, EXPOSURE_CUBE_DEPTH);// EPE, ENE, allocatedEPE, allocatedENE
57 }
58
59 set<string> nettingSetIdsSet;
60 for (const auto& [tradeId, trade] : portfolio->trades())
61 nettingSetIdsSet.insert(trade->envelope().nettingSetId());
62 nettingSetIds_= vector<string>(nettingSetIdsSet.begin(), nettingSetIdsSet.end());
63
64 times_ = vector<Real>(dates_.size(), 0.0);
65 for (Size i = 0; i < dates_.size(); i++)
66 times_[i] = dc_.yearFraction(today_, cube_->dates()[i]);
67
68 isRegularCubeStorage_ = !cubeInterpretation_->withCloseOutLag();
69}
70
72 LOG("Compute trade exposure profiles, " << (flipViewXVA_ ? "inverted (flipViewXVA = Y)" : "regular (flipViewXVA = N)"));
73 size_t i = 0;
74 for (auto tradeIt = portfolio_->trades().begin(); tradeIt != portfolio_->trades().end(); ++tradeIt, ++i) {
75 auto trade = tradeIt->second;
76 string tradeId = tradeIt->first;
77 string nettingSetId = trade->envelope().nettingSetId();
78 LOG("Aggregate exposure for trade " << tradeId);
79 if (nettingSetDefaultValue_.find(nettingSetId) == nettingSetDefaultValue_.end()) {
80 nettingSetDefaultValue_[nettingSetId] = vector<vector<Real>>(dates_.size(), vector<Real>(cube_->samples(), 0.0));
81 nettingSetCloseOutValue_[nettingSetId] = vector<vector<Real>>(dates_.size(), vector<Real>(cube_->samples(), 0.0));
82 nettingSetMporPositiveFlow_[nettingSetId] = vector<vector<Real>>(dates_.size(), vector<Real>(cube_->samples(), 0.0));
83 nettingSetMporNegativeFlow_[nettingSetId] = vector<vector<Real>>(dates_.size(), vector<Real>(cube_->samples(), 0.0));
84 }
85
86 // Identify the next break date if provided, default is trade maturity.
87 Date nextBreakDate = trade->maturity();
88 TradeActions ta = trade->tradeActions();
89 if (exerciseNextBreak_ && !ta.empty()) {
90 // loop over actions and pick next mutual break, if available
91 vector<TradeAction> actions = ta.actions();
92 for (Size j = 0; j < actions.size(); ++j) {
93 DLOG("TradeAction for " << tradeId << ", actionType " << actions[j].type() << ", actionOwner "
94 << actions[j].owner());
95 // FIXME: Introduce enumeration and parse text when building trade
96 if (actions[j].type() == "Break" && actions[j].owner() == "Mutual") {
97 QuantLib::Schedule schedule = ore::data::makeSchedule(actions[j].schedule());
98 vector<Date> dates = schedule.dates();
99 std::sort(dates.begin(), dates.end());
100 Date today = Settings::instance().evaluationDate();
101 for (Size k = 0; k < dates.size(); ++k) {
102 if (dates[k] > today && dates[k] < nextBreakDate) {
103 nextBreakDate = dates[k];
104 DLOG("Next break date for trade " << tradeId << ": "
105 << QuantLib::io::iso_date(nextBreakDate));
106 break;
107 }
108 }
109 }
110 }
111 }
112
113 Handle<YieldTermStructure> curve = market_->discountCurve(baseCurrency_, configuration_);
114 Real npv0;
115 if (flipViewXVA_) {
116 npv0 = -cube_->getT0(i);
117 } else {
118 npv0 = cube_->getT0(i);
119 }
120 vector<Real> epe(dates_.size() + 1, 0.0);
121 vector<Real> ene(dates_.size() + 1, 0.0);
122 vector<Real> ee_b(dates_.size() + 1, 0.0);
123 vector<Real> eee_b(dates_.size() + 1, 0.0);
124 vector<Real> pfe(dates_.size() + 1, 0.0);
125 epe[0] = std::max(npv0, 0.0);
126 ene[0] = std::max(-npv0, 0.0);
127 ee_b[0] = epe[0];
128 eee_b[0] = ee_b[0];
129 pfe[0] = std::max(npv0, 0.0);
130 exposureCube_->setT0(epe[0], tradeId, ExposureIndex::EPE);
131 exposureCube_->setT0(ene[0], tradeId, ExposureIndex::ENE);
132 for (Size j = 0; j < dates_.size(); ++j) {
133 Date d = cube_->dates()[j];
134 vector<Real> distribution(cube_->samples(), 0.0);
135 for (Size k = 0; k < cube_->samples(); ++k) {
136 // RL 2020-07-17
137 // 1) If the calculation type is set to NoLag:
138 // Collateral balances are NOT delayed by the MPoR, but we use the close-out NPV.
139 // 2) Otherwise:
140 // Collateral balances are delayed by the MPoR (if possible, i.e. the valuation
141 // grid has MPoR spacing), and we use the default date NPV.
142 // This is the treatment in the ORE releases up to June 2020).
143 Real defaultValue =
144 d > nextBreakDate && exerciseNextBreak_ ? 0.0 : cubeInterpretation_->getDefaultNpv(cube_, i, j, k);
145 Real closeOutValue;
146 if (isRegularCubeStorage_ && j == dates_.size() - 1)
147 closeOutValue = defaultValue;
148 else
149 closeOutValue = d > nextBreakDate && exerciseNextBreak_
150 ? 0.0
151 : cubeInterpretation_->getCloseOutNpv(cube_, i, j, k);
152
153 Real positiveCashFlow = cubeInterpretation_->getMporPositiveFlows(cube_, i, j, k);
154 Real negativeCashFlow = cubeInterpretation_->getMporNegativeFlows(cube_, i, j, k);
155 //for single trade exposures, always default value is relevant
156 Real npv = defaultValue;
157 epe[j + 1] += max(npv, 0.0) / cube_->samples();
158 ene[j + 1] += max(-npv, 0.0) / cube_->samples();
159 nettingSetDefaultValue_[nettingSetId][j][k] += defaultValue;
160 nettingSetCloseOutValue_[nettingSetId][j][k] += closeOutValue;
161 nettingSetMporPositiveFlow_[nettingSetId][j][k] += positiveCashFlow;
162 nettingSetMporNegativeFlow_[nettingSetId][j][k] += negativeCashFlow;
163 distribution[k] = npv;
164 if (multiPath_) {
165 exposureCube_->set(max(npv, 0.0), tradeId, d, k, ExposureIndex::EPE);
166 exposureCube_->set(max(-npv, 0.0), tradeId, d, k, ExposureIndex::ENE);
167 }
168 }
169 if (!multiPath_) {
170 exposureCube_->set(epe[j + 1], tradeId, d, 0, ExposureIndex::EPE);
171 exposureCube_->set(ene[j + 1], tradeId, d, 0, ExposureIndex::ENE);
172 }
173 ee_b[j + 1] = epe[j + 1] / curve->discount(cube_->dates()[j]);
174 eee_b[j + 1] = std::max(eee_b[j], ee_b[j + 1]);
175 std::sort(distribution.begin(), distribution.end());
176 Size index = Size(floor(quantile_ * (cube_->samples() - 1) + 0.5));
177 pfe[j + 1] = std::max(distribution[index], 0.0);
178 }
179 ee_b_[tradeId] = ee_b;
180 eee_b_[tradeId] = eee_b;
181 pfe_[tradeId] = pfe;
182
183 Real epe_b = 0.0;
184 Real eepe_b = 0.0;
185
186 Size t = 0;
187 Calendar cal = WeekendsOnly();
188 /*The time average in the EEPE calculation is taken over the first year of the exposure evolution
189 (or until maturity if all positions of the netting set mature before one year).
190 This one year point is actually taken to be today+1Y+4D, so that the 1Y point on the dateGrid is always
191 included.
192 This may effect DateGrids with daily data points*/
193 Date maturity = std::min(cal.adjust(today_ + 1 * Years + 4 * Days), trade->maturity());
194 QuantLib::Real maturityTime = dc_.yearFraction(today_, maturity);
195
196 while (t < dates_.size() && times_[t] <= maturityTime)
197 ++t;
198
199 if (t > 0) {
200 vector<double> weights(t);
201 weights[0] = times_[0];
202 for (Size k = 1; k < t; k++)
203 weights[k] = times_[k] - times_[k - 1];
204 double totalWeights = std::accumulate(weights.begin(), weights.end(), 0.0);
205 for (Size k = 0; k < t; k++)
206 weights[k] /= totalWeights;
207
208 for (Size k = 0; k < t; k++) {
209 epe_b += ee_b[k] * weights[k];
210 eepe_b += eee_b[k] * weights[k];
211 }
212 }
213 epe_b_[tradeId] = epe_b;
214 eepe_b_[tradeId] = eepe_b;
215 }
216}
217
218vector<Real> ExposureCalculator::getMeanExposure(const string& tid, ExposureIndex index) {
219 vector<Real> exp(dates_.size() + 1, 0.0);
220 exp[0] = exposureCube_->getT0(tid, index);
221 for (Size i = 0; i < dates_.size(); i++) {
222 for (Size k = 0; k < exposureCube_->samples(); k++) {
223 exp[i + 1] += exposureCube_->get(tid, dates_[i], k, index);
224 }
225 exp[i + 1] /= exposureCube_->samples();
226 }
227 return exp;
228}
229
230
231} // namespace analytics
232} // namespace ore
map< string, std::vector< Real > > pfe_
ExposureCalculator(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< Market > &market, const bool exerciseNextBreak, const string &baseCurrency, const string &configuration, const Real quantile, const CollateralExposureHelper::CalculationType calcType, const bool multiPath, const bool flipViewXVA)
const QuantLib::ext::shared_ptr< Portfolio > portfolio_
map< string, vector< vector< Real > > > nettingSetDefaultValue_
map< string, vector< vector< Real > > > nettingSetMporPositiveFlow_
const QuantLib::ext::shared_ptr< NPVCube > cube_
vector< Real > epe(const string &tid)
vector< Real > getMeanExposure(const string &tid, ExposureIndex index)
QuantLib::ext::shared_ptr< Market > market()
QuantLib::ext::shared_ptr< NPVCube > exposureCube_
virtual void build()
Compute exposures along all paths and fill result structures.
vector< Real > & pfe(const string &tid)
map< string, vector< vector< Real > > > nettingSetMporNegativeFlow_
vector< Real > & eee_b(const string &tid)
map< string, std::vector< Real > > eee_b_
map< string, vector< vector< Real > > > nettingSetCloseOutValue_
QuantLib::ext::shared_ptr< Portfolio > portfolio()
const QuantLib::ext::shared_ptr< Market > market_
map< string, std::vector< Real > > ee_b_
vector< Real > & ee_b(const string &tid)
const QuantLib::ext::shared_ptr< CubeInterpretation > cubeInterpretation_
vector< Real > ene(const string &tid)
const vector< TradeAction > & actions() const
Exposure calculator.
A cube implementation that stores the cube in memory.
#define LOG(text)
#define DLOG(text)
Time maturity
RandomVariable max(RandomVariable x, const RandomVariable &y)
RandomVariable exp(RandomVariable x)
Schedule makeSchedule(const ScheduleDates &data)