Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
riskparticipationagreementbaseengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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
21#include <ql/cashflows/floatingratecoupon.hpp>
22#include <ql/quotes/simplequote.hpp>
23
24// #include <iostream> // just for debugging
25
26namespace ore {
27namespace data {
28
30 const std::string& baseCcy, const std::map<std::string, Handle<YieldTermStructure>>& discountCurves,
31 const std::map<std::string, Handle<Quote>>& fxSpots, const Handle<DefaultProbabilityTermStructure>& defaultCurve,
32 const Handle<Quote>& recoveryRate, const Size maxGapDays, const Size maxDiscretisationPoints)
33 : baseCcy_(baseCcy), discountCurves_(discountCurves), fxSpots_(fxSpots), defaultCurve_(defaultCurve),
34 recoveryRate_(recoveryRate), maxGapDays_(maxGapDays), maxDiscretisationPoints_(maxDiscretisationPoints) {
35 QL_REQUIRE(maxGapDays == Null<Size>() || maxGapDays >= 1,
36 "invalid maxGapDays (" << maxGapDays << "), must be >= 1");
37 QL_REQUIRE(maxDiscretisationPoints_ == Null<Size>() || maxDiscretisationPoints_ >= 1,
38 "invalid maxDiscretisationPoints (" << maxDiscretisationPoints << "), must be >= 3");
39 for (auto const& d : discountCurves_)
40 registerWith(d.second);
41 for (auto const& s : fxSpots_)
42 registerWith(s.second);
43 registerWith(defaultCurve_);
44 registerWith(recoveryRate_);
45 fxSpots_[baseCcy_] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.0));
46}
47
48// just for debugging
49// namespace {
50// void logGrid(const std::vector<Date>& original, const std::vector<Date>& now) {
51// for (Size i = 0; i < original.size(); ++i) {
52// if (std::find(now.begin(), now.end(), original[i]) != now.end())
53// std::clog << "| ";
54// else
55// std::clog << " ";
56// }
57// std::clog << " (" << (now.size() - 1) << ")"
58// << "\n";
59// }
60// } // namespace
61
63 const Date& referenceDate, const Date& protectionStart, const Date& protectionEnd,
64 const std::vector<Leg>& underlying, const Size maxGapDays, const Size maxDiscretisationPoints) {
65
66 QL_REQUIRE(protectionEnd > referenceDate,
67 "protection end (" << protectionEnd << ") must be > reference date (" << referenceDate << ")");
68
69 // collect the accrual end dates of the float coupons
70
71 std::vector<Date> accrualDates;
72 for (auto const& l : underlying) {
73 for (auto const& c : l) {
74 if (auto f = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(c)) {
75 accrualDates.push_back(f->accrualEndDate());
76 }
77 }
78 }
79
80 // build the discretisation grid
81
82 accrualDates.push_back(std::max(protectionStart, referenceDate));
83 accrualDates.push_back(protectionEnd);
84
85 std::sort(accrualDates.begin(), accrualDates.end());
86 auto it = std::unique(accrualDates.begin(), accrualDates.end());
87 accrualDates.resize(it - accrualDates.begin());
88
89 auto itStart = std::lower_bound(accrualDates.begin(), accrualDates.end(), referenceDate);
90 auto itEnd = std::upper_bound(accrualDates.begin(), accrualDates.end(), protectionEnd);
91
92 QL_REQUIRE(std::distance(itStart, itEnd) >= 2, "got invalid discretisationGrid for RPA, this is unexpected");
93
94 std::vector<Date> gridDates(itStart, itEnd);
95
96 // add additional dates on mid points of intervals that are exceeding the max gap given
97
98 if (maxGapDays != Null<Size>()) {
99 bool refining;
100 // just to really make sure we exit the loop below!
101 Size refiningIterations = 0;
102 do {
103 refining = false;
104 for (auto it = gridDates.begin(); it < gridDates.end() - 1; ++it) {
105 if (*(it + 1) - *it > std::max<int>(1, maxGapDays)) {
106 it = gridDates.insert(it + 1, *it + (*(it + 1) - *it) / 2);
107 refining = true;
108 }
109 }
110 } while (refining && ++refiningIterations < 100);
111 QL_REQUIRE(refiningIterations < 100, "discretisationGrid refinement failed, this is unexpected");
112 }
113
114 // if the number of intervals exceeds the max allowed number, remove points at the beginning and end like this
115 //
116 // (1) | x | x | x | x | x | x | x | x | x | =>
117 // (2) | x | x | x | x | x | x | x | =>
118 // (3) | x | x | x | x | x | etc.
119 //
120 // where each '|' marks an entry in gridDates and 'x' marks the midpoint of the intervals,
121 // until we reach the max allowed number of discretisation points.
122
123 // just for debugging
124 // std::vector<Date> originalGridDates(gridDates);
125 // logGrid(originalGridDates, gridDates);
126
127 Size currentNumberOfDiscretisationPoints = gridDates.size() - 1,
128 previousNumberOfDiscretisationPoints = QL_MAX_INTEGER;
129
130 while (maxDiscretisationPoints != Null<Size>() && currentNumberOfDiscretisationPoints > maxDiscretisationPoints &&
131 currentNumberOfDiscretisationPoints < previousNumberOfDiscretisationPoints) {
132
133 previousNumberOfDiscretisationPoints = currentNumberOfDiscretisationPoints;
134 Size currentLeftIndex = 0, currentRightIndex = gridDates.size() - 1;
135 bool nextErasureOnLeft = true, canRemoveFurtherPointsInPass = true;
136
137 while (currentNumberOfDiscretisationPoints > maxDiscretisationPoints && canRemoveFurtherPointsInPass) {
138 if (nextErasureOnLeft) {
139 if (gridDates.size() >= currentLeftIndex + 4 && currentLeftIndex + 3 <= currentRightIndex) {
140 gridDates.erase(std::next(gridDates.begin(), currentLeftIndex + 1),
141 std::next(gridDates.begin(), currentLeftIndex + 3));
142 currentNumberOfDiscretisationPoints -= 2;
143 currentRightIndex -= 2;
144 currentLeftIndex++;
145 nextErasureOnLeft = false;
146 } else {
147 canRemoveFurtherPointsInPass = false;
148 }
149 } else {
150 if (currentRightIndex >= 3 && currentLeftIndex + 3 <= currentRightIndex) {
151 gridDates.erase(std::next(gridDates.begin(), currentRightIndex - 2),
152 std::next(gridDates.begin(), currentRightIndex));
153 currentNumberOfDiscretisationPoints -= 2;
154 currentRightIndex -= 3;
155 nextErasureOnLeft = true;
156 } else {
157 canRemoveFurtherPointsInPass = false;
158 }
159 }
160 // just for debugging
161 // logGrid(originalGridDates, gridDates);
162 }
163 }
164
165 return gridDates;
166}
167
169
170 QL_REQUIRE(!discountCurves_[baseCcy_].empty(),
171 "RiskParticipationAgreementBaseEngine::calculate(): empty discount curve for ccy" << baseCcy_);
172 QL_REQUIRE(!defaultCurve_.empty(), "RiskParticipationAgreementBaseEngine::calculate(): empty default curve");
173 QL_REQUIRE(arguments_.fixedRecoveryRate != Null<Real>() || !recoveryRate_.empty(),
174 "RiskParticipationAgreementBaseEngine::calculate(): empty recovery and trade does not specify "
175 "fixed recovery");
176
177 // asof date for valuation
178
179 referenceDate_ = discountCurves_[baseCcy_]->referenceDate();
180
181 // effective recovery rate to use
182
184 arguments_.fixedRecoveryRate == Null<Real>() ? recoveryRate_->value() : arguments_.fixedRecoveryRate;
185
186 // compute the fee leg NPV
187
188 Real fee = 0.0;
189 Size idx = 0;
190 std::vector<std::vector<Date>> feeStartDates;
191 std::vector<std::vector<Date>> feeEndDates;
192 std::vector<std::vector<Date>> feePayDates;
193 std::vector<std::vector<Date>> feeMidDates;
194 std::vector<std::vector<double>> feeAmounts;
195 std::vector<std::vector<double>> feeMidAccrueds;
196 std::vector<std::vector<double>> feeMidDiscounts;
197 std::vector<std::vector<double>> feeDiscounts;
198 std::vector<std::vector<double>> feeSurvivalProbs;
199 std::vector<std::vector<double>> feePeriodPDs;
200 std::vector<double> feeFXSpot;
201 for (auto const& l : arguments_.protectionFee) {
202 QL_REQUIRE(!discountCurves_[arguments_.protectionFeeCcys[idx]].empty(),
203 "RiskParticipationAgreementBaseEngine::calculate(): empty discount curve for ccy "
204 << arguments_.protectionFeeCcys[idx]);
205 QL_REQUIRE(!fxSpots_[arguments_.protectionFeeCcys[idx]].empty(),
206 "RiskParticipationAgreementBaseEngine::calculate(): empty fx spot for ccy "
207 << arguments_.protectionFeeCcys[idx] + baseCcy_);
208 feeFXSpot.push_back(fxSpots_[arguments_.protectionFeeCcys[idx]]->value());
209 std::vector<Date> thisFeeStartDates;
210 std::vector<Date> thisFeeEndDates;
211 std::vector<Date> thisFeePayDates;
212 std::vector<Date> thisFeeMidDates;
213 std::vector<double> thisFeeAmounts;
214 std::vector<double> thisFeeMidAccrueds;
215 std::vector<double> thisFeeMidDiscounts;
216 std::vector<double> thisFeeDiscounts;
217 std::vector<double> thisFeeSurvivalProbs;
218 std::vector<double> thisFeePeriodPDs;
219 for (auto const& c : l) {
220 if (c->date() <= referenceDate_)
221 continue;
222 thisFeePayDates.push_back(c->date());
223 thisFeeAmounts.push_back(c->amount());
224 thisFeeDiscounts.push_back(discountCurves_[arguments_.protectionFeeCcys[idx]]->discount(c->date()));
225 thisFeeSurvivalProbs.push_back(defaultCurve_->survivalProbability(c->date()));
226 // the fee is only paid if the reference entity is still alive at the payment date
227 fee += thisFeeAmounts.back() * thisFeeDiscounts.back() * feeFXSpot.back() * thisFeeSurvivalProbs.back();
228 // accrual settlement using the mid of the coupon periods
229 auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c);
230 if (cpn && arguments_.settlesAccrual) {
231 Date start = std::max(cpn->accrualStartDate(), referenceDate_);
232 Date end = cpn->accrualEndDate();
233 thisFeeStartDates.push_back(start);
234 thisFeeEndDates.push_back(end);
235 if (start < end) {
236 Date mid = start + (end - start) / 2;
237 thisFeeMidDates.push_back(mid);
238 thisFeeMidAccrueds.push_back(cpn->accruedAmount(mid));
239 thisFeeMidDiscounts.push_back(discountCurves_[arguments_.protectionFeeCcys[idx]]->discount(mid));
240 thisFeePeriodPDs.push_back(defaultCurve_->defaultProbability(start, end));
241 fee += thisFeeMidAccrueds.back() * thisFeeMidDiscounts.back() * feeFXSpot.back() *
242 thisFeePeriodPDs.back();
243 }
244 }
245 }
246 feeStartDates.push_back(thisFeeStartDates);
247 feeEndDates.push_back(thisFeeEndDates);
248 feePayDates.push_back(thisFeePayDates);
249 feeMidDates.push_back(thisFeeMidDates);
250 feeAmounts.push_back(thisFeeAmounts);
251 feeMidAccrueds.push_back(thisFeeMidAccrueds);
252 feeMidDiscounts.push_back(thisFeeMidDiscounts);
253 feeDiscounts.push_back(thisFeeDiscounts);
254 feeSurvivalProbs.push_back(thisFeeSurvivalProbs);
255 feePeriodPDs.push_back(thisFeePeriodPDs);
256 ++idx;
257 }
258
259 // if we are past the protection end date, the protection leg NPV is zero, otherwise we call into the
260 // derived engine to compute this
261
262 Real protection = 0.0;
263 if (arguments_.protectionEnd > referenceDate_) {
266 protection = protectionLegNpv();
267 }
268
269 // compute the total NPV, we buy the protection if we pay the fee
270
271 results_.value = (arguments_.protectionFeePayer ? 1.0 : -1.0) * (protection - fee);
272
273 // set additional results
274
275 std::vector<Real> gridPeriodPds;
276 for (Size i = 0; i < gridDates_.size() - 1; ++i)
277 gridPeriodPds.push_back(defaultCurve_->defaultProbability(gridDates_[i], gridDates_[i + 1]));
278
279 results_.additionalResults["GridDates"] = gridDates_;
280 results_.additionalResults["ProtectionLegNpv"] = (arguments_.protectionFeePayer ? 1.0 : -1.0) * protection;
281 results_.additionalResults["FeeLegNpv"] = (arguments_.protectionFeePayer ? 1.0 : -1.0) * fee;
282 results_.additionalResults["RecoveryRate"] = effectiveRecoveryRate_;
283 results_.additionalResults["GridPeriodPDs"] = gridPeriodPds;
284 results_.additionalResults["ParticipationRate"] = arguments_.participationRate;
285
286 for (Size l = 0; l < arguments_.protectionFee.size(); ++l) {
287 results_.additionalResults["FeeStartDates"] = feeStartDates[l];
288 results_.additionalResults["FeeEndDates"] = feeEndDates[l];
289 results_.additionalResults["FeePayDates"] = feePayDates[l];
290 results_.additionalResults["FeeMidDates"] = feeMidDates[l];
291 results_.additionalResults["FeeAmounts"] = feeAmounts[l];
292 results_.additionalResults["FeeMidAccrueds"] = feeMidAccrueds[l];
293 results_.additionalResults["FeeMidDiscounts"] = feeMidDiscounts[l];
294 results_.additionalResults["FeeDiscounts"] = feeDiscounts[l];
295 results_.additionalResults["FeeSurvivalProbs"] = feeSurvivalProbs[l];
296 results_.additionalResults["FeePeriodPDs"] = feePeriodPDs[l];
297 results_.additionalResults["FeeFXSpot"] = feeFXSpot;
298 results_.additionalResults["FeeCurrency"] = arguments_.protectionFeeCcys[l];
299 }
300}
301
302} // namespace data
303} // namespace ore
const Instrument::results * results_
static std::vector< Date > buildDiscretisationGrid(const Date &referenceDate, const Date &protectionStart, const Date &protectionEnd, const std::vector< Leg > &underlying, const Size maxGapDays=Null< Size >(), const Size maxDiscretisationPoints=Null< Size >())
RiskParticipationAgreementBaseEngine(const std::string &baseCcy, const std::map< std::string, Handle< YieldTermStructure > > &discountCurves, const std::map< std::string, Handle< Quote > > &fxSpots, const Handle< DefaultProbabilityTermStructure > &defaultCurve, const Handle< Quote > &recoveryRate, const Size maxGapDays=Null< Size >(), const Size maxDiscretizsationPoints=Null< Size >())
std::map< std::string, Handle< YieldTermStructure > > discountCurves_
@ data
Definition: log.hpp:77
Date referenceDate
Definition: utilities.cpp:442
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Swap::arguments * arguments_