Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
collatexposurehelper.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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#include <ql/errors.hpp>
21
22using namespace std;
23using namespace QuantLib;
24
25#define FLAT_INTERPOLATION 1
26
27namespace ore {
28using namespace data;
29namespace analytics {
30
32 static map<string, CollateralExposureHelper::CalculationType> m = {
37 };
38 auto it = m.find(s);
39 if (it != m.end()) {
40 return it->second;
41 } else {
42 QL_FAIL("Collateral Calculation Type \"" << s << "\" not recognized");
43 }
44}
45
46std::ostream& operator<<(std::ostream& out, CollateralExposureHelper::CalculationType t) {
48 out << "Symmetric";
50 out << "AsymmetricCVA";
52 out << "AsymmetricDVA";
54 out << "NoLag";
55 else
56 QL_FAIL("Collateral calculation type not covered");
57 return out;
58}
59
60Real CollateralExposureHelper::marginRequirementCalc(const QuantLib::ext::shared_ptr<CollateralAccount>& collat,
61 const Real& uncollatValue, const Date& simulationDate) {
62 // first step, make sure collateral balance is up to date.
63 // collat->updateAccountBalance(simulationDate);
64
65 Real collatBalance = collat->accountBalance();
66 Real csa = creditSupportAmount(collat->csaDef(), uncollatValue);
67
68 Real openMargins = collat->outstandingMarginAmount(simulationDate);
69 Real collatShortfall = csa - collatBalance - openMargins;
70
71 Real mta;
72 if (collatShortfall >= 0.0)
73 mta = collat->csaDef()->csaDetails()->mtaRcv();
74 else
75 mta = collat->csaDef()->csaDetails()->mtaPay();
76
77 Real deliveryAmount = fabs(collatShortfall) >= mta ? (collatShortfall) : 0.0;
78
79 return deliveryAmount;
80}
81
83 const QuantLib::ext::shared_ptr<ore::data::NettingSetDefinition>& nettingSet,
84 const Real& uncollatValueCsaCur) {
85
86 Real ia = nettingSet->csaDetails()->independentAmountHeld();
87 Real threshold, csa;
88 if (uncollatValueCsaCur + ia >= 0) {
89 threshold = nettingSet->csaDetails()->thresholdRcv();
90 csa = max(uncollatValueCsaCur + ia - threshold, 0.0);
91 }
92 else {
93 threshold = nettingSet->csaDetails()->thresholdPay();
94 // N.B. the min and change of sign on threshold.
95 csa = min(uncollatValueCsaCur + ia + threshold, 0.0);
96 }
97 return csa;
98}
99
100template <class T>
101Real CollateralExposureHelper::estimateUncollatValue(const Date& simulationDate, const Real& npv_t0,
102 const Date& date_t0, const vector<vector<T>>& scenPvProfiles,
103 const unsigned& scenIndex, const vector<Date>& dateGrid) {
104
105 QL_REQUIRE(simulationDate >= date_t0, "CollatExposureHelper error: simulation date < start date");
106 QL_REQUIRE(dateGrid[0] >= date_t0, "CollatExposureHelper error: cube dateGrid starts before t0");
107
108 if (simulationDate >= dateGrid.back())
109 return scenPvProfiles.back()[scenIndex]; // flat extrapolation
110 if (simulationDate == date_t0)
111 return npv_t0;
112 for (unsigned i = 0; i < dateGrid.size(); i++) {
113 if (dateGrid[i] == simulationDate)
114 // return if collat sim-date is the same as an exposure grid date
115 return scenPvProfiles[i][scenIndex];
116#ifdef FLAT_INTERPOLATION
117 else if (simulationDate < dateGrid.front())
118 return scenPvProfiles.front()[scenIndex];
119 else if (i < dateGrid.size() - 1 && simulationDate > dateGrid[i] && simulationDate < dateGrid[i + 1])
120 return scenPvProfiles[i + 1][scenIndex];
121#endif
122 }
123
124 // if none of above criteria are met, perform interpolation
125 Date t1, t2;
126 Real npv1, npv2;
127 if (simulationDate <= dateGrid[0]) {
128 t1 = date_t0;
129 t2 = dateGrid[0];
130 npv1 = npv_t0;
131 npv2 = scenPvProfiles[0][scenIndex];
132 } else {
133 vector<Date>::const_iterator it = lower_bound(dateGrid.begin(), dateGrid.end(), simulationDate);
134 QL_REQUIRE(it != dateGrid.end(), "CollatExposureHelper error; "
135 << "date interpolation points not found (it.end())");
136 QL_REQUIRE(it != dateGrid.begin(), "CollatExposureHelper error; "
137 << "date interpolation points not found (it.begin())");
138 Size pos1 = (it - 1) - dateGrid.begin();
139 Size pos2 = it - dateGrid.begin();
140 t1 = dateGrid[pos1];
141 t2 = dateGrid[pos2];
142 npv1 = scenPvProfiles[pos1][scenIndex];
143 npv2 = scenPvProfiles[pos2][scenIndex];
144 }
145
146 Real newPv = npv1 + ((npv2 - npv1) * (double(simulationDate - t1) / double(t2 - t1)));
147 QL_REQUIRE((npv1 <= newPv && newPv <= npv2) || (npv1 >= newPv && newPv >= npv2),
148 "CollatExposureHelper error; "
149 << "interpolated Pv value " << newPv << " out of range (" << npv1 << " " << npv2 << ") "
150 << "for simulation date " << simulationDate << " between " << t1 << " and " << t2);
151
152 return newPv;
153}
154
155void CollateralExposureHelper::updateMarginCall(const QuantLib::ext::shared_ptr<CollateralAccount>& collat,
156 const Real& uncollatValue, const Date& simulationDate,
157 const Real& annualisedZeroRate, const CalculationType& calcType,
158 const bool& eligMarginReqDateUs, const bool& eligMarginReqDateCtp) {
159 collat->updateAccountBalance(simulationDate, annualisedZeroRate);
160
161 Real margin = marginRequirementCalc(collat, uncollatValue, simulationDate);
162
163 if (margin != 0.0) {
164 // settle margin call on appropriate date
165 // (dependent upon MPR and collateralised calculation methodology)
166 Date marginPayDate;
167 // RL 2020-07-17
168 // 1) If the calculation type is set to NoLag:
169 // Collateral balances are NOT delayed by the MPoR, but we use the close-out NPV in exposure calculations,
170 // see similar comment and the equivalent change in the post processor.
171 // 2) Otherwise:
172 // Collateral balances are delayed by the MPoR (if possible, i.e. the valuation grid has MPoR spacing),
173 // and we use the default date NPV.
174 // This is the treatment in the ORE releases up to June 2020).
175 Period lag = (calcType == NoLag ? 0*Days : collat->csaDef()->csaDetails()->marginPeriodOfRisk());
176 if (margin > 0.0 && eligMarginReqDateUs) {
177 marginPayDate =
178 (calcType == AsymmetricDVA ? simulationDate : simulationDate + lag);
179 collat->updateMarginCall(margin, marginPayDate, simulationDate);
180 } else if (margin < 0.0 && eligMarginReqDateCtp) {
181 marginPayDate =
182 (calcType == AsymmetricCVA ? simulationDate : simulationDate + lag);
183 collat->updateMarginCall(margin, marginPayDate, simulationDate);
184 } else {
185 }
186 }
187}
188
189QuantLib::ext::shared_ptr<vector<QuantLib::ext::shared_ptr<CollateralAccount>>> CollateralExposureHelper::collateralBalancePaths(
190 const QuantLib::ext::shared_ptr<NettingSetDefinition>& csaDef, const Real& nettingSetPv, const Date& date_t0,
191 const vector<vector<Real>>& nettingSetValues, const Date& nettingSet_maturity, const vector<Date>& dateGrid,
192 const Real& csaFxTodayRate, const vector<vector<Real>>& csaFxScenarioRates, const Real& csaTodayCollatCurve,
193 const vector<vector<Real>>& csaScenCollatCurves, const CalculationType& calcType,
194 const QuantLib::ext::shared_ptr<CollateralBalance>& balance) {
195
196 try {
197 // step 1; build a collateral account object, assuming t0 VM balance from the balance object (zero balance if missing),
198 // and calculate t0 margin requirement
199
200 Real initialBalance = 0.0;
201 if (balance && balance->variationMargin() != Null<Real>()) {
202 initialBalance = balance->variationMargin();
203 DLOG("initial collateral balance: " << initialBalance);
204 }
205 else {
206 DLOG("initial collateral balance not found");
207 }
208
209 QuantLib::ext::shared_ptr<CollateralAccount> tmpAcc(new CollateralAccount(csaDef, initialBalance, date_t0));
210 DLOG("tmp initial collateral balance: " << tmpAcc->balance_t0());
211 DLOG("tmp current collateral balance: " << tmpAcc->accountBalance());
212
213 Real bal_t0 = marginRequirementCalc(tmpAcc, nettingSetPv, date_t0);
214
215 // step 2; build a new collateral account object with t0 balance = bal_t0
216 // a copy of this new object will be used as base for each scenario collateral path
217 CollateralAccount baseAcc(csaDef, bal_t0, date_t0);
218 DLOG("base current collateral balance: " << bal_t0 << ", " << baseAcc.accountBalance());
219
220 // step 3; build an empty container for the return value(s)
221 QuantLib::ext::shared_ptr<vector<QuantLib::ext::shared_ptr<CollateralAccount>>> scenarioCollatPaths(
222 new vector<QuantLib::ext::shared_ptr<CollateralAccount>>());
223
224 // step 4; start loop over scenarios
225 Size numScenarios = nettingSetValues.front().size();
226 QL_REQUIRE(numScenarios == csaFxScenarioRates.front().size(), "netting values -v- scenario FX rate mismatch");
227 Date simEndDate = std::min(nettingSet_maturity, dateGrid.back()) + csaDef->csaDetails()->marginPeriodOfRisk();
228 for (unsigned i = 0; i < numScenarios; i++) {
229 QuantLib::ext::shared_ptr<CollateralAccount> collat(new CollateralAccount(baseAcc));
230 Date tmpDate = date_t0; // the date which gets evolved
231 Date nextMarginReqDateUs = date_t0;
232 Date nextMarginReqDateCtp = date_t0;
233 while (tmpDate <= simEndDate) {
234 QL_REQUIRE(tmpDate <= nextMarginReqDateUs && tmpDate <= nextMarginReqDateCtp &&
235 (tmpDate == nextMarginReqDateUs || tmpDate == nextMarginReqDateCtp),
236 "collateral balance path generation error; invalid time stepping");
237 bool eligMarginReqDateUs = tmpDate == nextMarginReqDateUs ? true : false;
238 bool eligMarginReqDateCtp = tmpDate == nextMarginReqDateCtp ? true : false;
239 Real uncollatVal = estimateUncollatValue(tmpDate, nettingSetPv, date_t0, nettingSetValues, i, dateGrid);
240 Real fxValue = estimateUncollatValue(tmpDate, csaFxTodayRate, date_t0, csaFxScenarioRates, i, dateGrid);
241 Real annualisedZeroRate =
242 estimateUncollatValue(tmpDate, csaTodayCollatCurve, date_t0, csaScenCollatCurves, i, dateGrid);
243 uncollatVal /= fxValue;
244 updateMarginCall(collat, uncollatVal, tmpDate, annualisedZeroRate, calcType, eligMarginReqDateUs,
245 eligMarginReqDateCtp);
246
247 if (nextMarginReqDateUs == tmpDate)
248 nextMarginReqDateUs = tmpDate + collat->csaDef()->csaDetails()->marginCallFrequency();
249 if (nextMarginReqDateCtp == tmpDate)
250 nextMarginReqDateCtp = tmpDate + collat->csaDef()->csaDetails()->marginPostFrequency();
251 tmpDate = std::min(nextMarginReqDateUs, nextMarginReqDateCtp);
252 }
253 QL_REQUIRE(tmpDate > simEndDate,
254 "collateral balance path generation error; while loop terminated too early. ("
255 << tmpDate << ", " << simEndDate << ")");
256 // set account balance to zero after maturity of portfolio
257 collat->closeAccount(simEndDate + Period(1, Days));
258 scenarioCollatPaths->push_back(collat);
259 }
260 return scenarioCollatPaths;
261 } catch (const std::exception& e) {
262 QL_FAIL(e.what());
263 } catch (...) {
264 QL_FAIL("CollateralExposureHelper - unknown error when generating collateralBalancePaths");
265 }
266}
267} // namespace analytics
268} // namespace ore
static void updateMarginCall(const QuantLib::ext::shared_ptr< CollateralAccount > &collat, const Real &uncollatValue, const Date &simulationDate, const Real &accrualFactor, const CalculationType &calcType=Symmetric, const bool &eligMarginReqDateUs=true, const bool &eligMarginReqDateCtp=true)
static Real creditSupportAmount(const QuantLib::ext::shared_ptr< ore::data::NettingSetDefinition > &nettingSet, const Real &uncollatValueCsaCur)
static Real estimateUncollatValue(const Date &simulationDate, const Real &npv_t0, const Date &date_t0, const vector< vector< T > > &scenPvProfiles, const unsigned &scenIndex, const vector< Date > &dateGrid)
static Real marginRequirementCalc(const QuantLib::ext::shared_ptr< CollateralAccount > &collat, const Real &uncollatValue, const Date &simulationDate)
static QuantLib::ext::shared_ptr< vector< QuantLib::ext::shared_ptr< CollateralAccount > > > collateralBalancePaths(const QuantLib::ext::shared_ptr< NettingSetDefinition > &csaDef, const Real &nettingSetPv, const Date &date_t0, const vector< vector< Real > > &nettingSetValues, const Date &nettingSet_maturity, const vector< Date > &dateGrid, const Real &csaFxTodayRate, const vector< vector< Real > > &csaFxScenarioRates, const Real &csaTodayCollatCurve, const vector< vector< Real > > &csaScenCollatCurves, const CalculationType &calcType=Symmetric, const QuantLib::ext::shared_ptr< CollateralBalance > &balance=QuantLib::ext::shared_ptr< CollateralBalance >())
Collateral Exposure Helper Functions (stored in base currency)
data
#define DLOG(text)
RandomVariable max(RandomVariable x, const RandomVariable &y)
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
RandomVariable min(RandomVariable x, const RandomVariable &y)
CollateralExposureHelper::CalculationType parseCollateralCalculationType(const string &s)
Convert text representation to CollateralExposureHelper::CalculationType.