• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 14910176578

08 May 2025 03:28PM UTC coverage: 73.315% (+0.02%) from 73.3%
14910176578

Pull #2195

github

web-flow
Merge 3a61f499c into 5d972fb7b
Pull Request #2195: Added `Handle<Quote>` for spread in `OISRateHelper`

32 of 33 new or added lines in 2 files covered. (96.97%)

277 existing lines in 25 files now uncovered.

56277 of 76761 relevant lines covered (73.31%)

8687029.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.88
/ql/experimental/credit/basket.cpp
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2

3
/*
4
 Copyright (C) 2008 Roland Lichters
5
 Copyright (C) 2009, 2014 Jose Aparicio
6

7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9

10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <http://quantlib.org/license.shtml>.
15

16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20

21
#include <ql/experimental/credit/basket.hpp>
22
#include <ql/experimental/credit/defaultlossmodel.hpp>
23
#include <ql/experimental/credit/loss.hpp>
24
#include <ql/time/daycounters/actualactual.hpp>
25
#include <algorithm>
26
#include <numeric>
27
#include <utility>
28

29
using namespace std;
30

31
namespace QuantLib {
32

33
    Basket::Basket(const Date& refDate,
26✔
34
                   const vector<string>& names,
35
                   vector<Real> notionals,
36
                   ext::shared_ptr<Pool> pool,
37
                   Real attachment,
38
                   Real detachment,
39
                   ext::shared_ptr<Claim> claim)
26✔
40
    : notionals_(std::move(notionals)), pool_(std::move(pool)), claim_(std::move(claim)),
26✔
41
      attachmentRatio_(attachment), detachmentRatio_(detachment), basketNotional_(0.0),
26✔
42
      attachmentAmount_(0.0), detachmentAmount_(0.0), trancheNotional_(0.0), refDate_(refDate) {
26✔
43
        QL_REQUIRE(!notionals_.empty(), "notionals empty");
26✔
44
        QL_REQUIRE (attachmentRatio_ >= 0 &&
26✔
45
                    attachmentRatio_ <= detachmentRatio_ &&
46
                    detachmentRatio_ <= 1,
47
                    "invalid attachment/detachment ratio");
48
        QL_REQUIRE(pool_, "Empty pool pointer.");
26✔
49
        QL_REQUIRE(notionals_.size() == pool_->size(), 
26✔
50
                   "unmatched data entry sizes in basket");
51

52
        // registrations relevant to the loss status, not to the expected 
53
        // loss values; those are through models.
54
        registerWith(Settings::instance().evaluationDate());
52✔
55
        registerWith(claim_);
26✔
56

57
        computeBasket();
26✔
58

59
        // At this point Issuers in the pool might or might not have
60
        //   probability term structures for the defultKeys(eventType+
61
        //   currency+seniority) entering in this basket. This is not
62
        //   necessarily a problem.
63
        for (Real notional : notionals_) {
2,079✔
64
            basketNotional_ += notional;
2,053✔
65
            attachmentAmount_ += notional * attachmentRatio_;
2,053✔
66
            detachmentAmount_ += notional * detachmentRatio_;
2,053✔
67
        }
68
        trancheNotional_ = detachmentAmount_ - attachmentAmount_;
26✔
69
    }
26✔
70

71
    /*\todo Alternatively send a relinkable handle so it can be changed from 
72
    the outside. In that case reconsider the observability chain.
73
    */
74
    void Basket::setLossModel(
85✔
75
        const ext::shared_ptr<DefaultLossModel>& lossModel) {
76

77
        if (lossModel_ != nullptr)
85✔
78
            unregisterWith(lossModel_);
118✔
79
        lossModel_ = lossModel;
85✔
80
        if (lossModel_ != nullptr) {
85✔
81
            //recovery quotes, defaults(once Issuer is observable)etc might 
82
            //  trigger us:
83
            registerWith(lossModel_);
170✔
84
        }
85
        LazyObject::update(); //<- just set calc=false
85✔
86
    }
85✔
87

88
    void Basket::performCalculations() const {
85✔
89
        // Calculations for status
90
        computeBasket();// or we might be called from an statistic member 
85✔
91
                        // without being initialized yet (first called)
92
        QL_REQUIRE(lossModel_, "Basket has no default loss model assigned.");
85✔
93

94
        /* The model must notify us if the another basket calls it for 
95
        reasignment. The basket works as an argument to the deafult loss models 
96
        so, even if the models dont cache anything, they will be using the wrong
97
        default TS. \todo: This has a possible optimization: the basket 
98
        incorporates trancheability and many models do their compuations 
99
        independently of that (some do but do it inefficiently when asked for 
100
        two tranches on the same basket; e,g, recursive model) so it might be 
101
        more efficient sending the pool only; however the modtionals and other 
102
        basket info are still used.*/
103
        lossModel_->setBasket(const_cast<Basket*>(this));
85✔
104
    }
85✔
105

106
    Real Basket::notional() const {
×
107
        return std::accumulate(notionals_.begin(), notionals_.end(), Real(0.0));
×
108
    }
109

110
    vector<Real> Basket::probabilities(const Date& d) const {
×
111
        vector<Real> prob(size());
×
112
        vector<DefaultProbKey> defKeys = defaultKeys();
×
113
        for (Size j = 0; j < size(); j++)
×
114
            prob[j] = pool_->get(pool_->names()[j]).defaultProbability(
×
115
                defKeys[j])->defaultProbability(d);
×
116
        return prob;
×
117
    }
×
118

119
    Real Basket::cumulatedLoss(const Date& endDate) const {
×
120
        QL_REQUIRE(endDate >= refDate_, 
×
121
            "Target date lies before basket inception");
122
        Real loss = 0.0;
123
        for (Size i = 0; i < size(); i++) {
×
124
            ext::shared_ptr<DefaultEvent> credEvent =
125
                pool_->get(pool_->names()[i]).defaultedBetween(refDate_,
×
126
                    endDate, pool_->defaultKeys()[i]);
×
127
            if (credEvent != nullptr) {
×
128
                /* \todo If the event has not settled one would need to 
129
                introduce some model recovery rate (independently of a loss 
130
                model) This remains to be done.
131
                */  
132
                if(credEvent->hasSettled())
×
133
                    loss += claim_->amount(credEvent->date(),
×
134
                            // notionals_[i],
135
                            exposure(pool_->names()[i], credEvent->date()),
×
136
                            credEvent->settlement().recoveryRate(
×
137
                                pool_->defaultKeys()[i].seniority()));
×
138
            }
139
        }
140
        return loss;
×
141
    }
142

143
    Real Basket::settledLoss(const Date& endDate) const {
222✔
144
        QL_REQUIRE(endDate >= refDate_, 
222✔
145
            "Target date lies before basket inception");
146
        
147
        Real loss = 0.0;
148
        for (Size i = 0; i < size(); i++) {
18,212✔
149
            ext::shared_ptr<DefaultEvent> credEvent =
150
                pool_->get(pool_->names()[i]).defaultedBetween(refDate_,
17,990✔
151
                    endDate, pool_->defaultKeys()[i]);
35,980✔
152
            if (credEvent != nullptr) {
17,990✔
153
                if(credEvent->hasSettled()) {
×
154
                    loss += claim_->amount(credEvent->date(),
×
155
                            //notionals_[i],
156
                            exposure(pool_->names()[i], credEvent->date()),
×
157
                            //NOtice I am requesting an exposure in the past...
158
                            /* also the seniority does not belong to the 
159
                            counterparty anymore but to the position.....*/
160
                            credEvent->settlement().recoveryRate(
×
161
                                pool_->defaultKeys()[i].seniority()));
×
162
                }
163
            }
164
        }
165
        return loss;
222✔
166
    }
167

168
    Real Basket::remainingNotional() const {
164✔
169
        return evalDateRemainingNot_;
164✔
170
    }
171

172
    std::vector<Size> Basket::liveList(const Date& endDate) const {
1,158✔
173
        std::vector<Size> calcBufferLiveList;
174
        for (Size i = 0; i < size(); i++)
107,638✔
175
            if (!pool_->get(pool_->names()[i]).defaultedBetween(
106,480✔
176
                    refDate_,
106,480✔
177
                    endDate,
178
                    pool_->defaultKeys()[i]))
212,960✔
179
                calcBufferLiveList.push_back(i);
106,480✔
180

181
        return calcBufferLiveList;
1,158✔
182
    }
183

184
    Real Basket::remainingNotional(const Date& endDate) const {
823✔
185
        Real notional = 0;
186
        vector<DefaultProbKey> defKeys = defaultKeys();
823✔
187
        for (Size i = 0; i < size(); i++) {
80,298✔
188
            if (!pool_->get(pool_->names()[i]).defaultedBetween(refDate_,
158,950✔
189
                                                        endDate,
190
                                                        defKeys[i]))
191
                notional += notionals_[i];
79,475✔
192
        }
193
        return notional;
823✔
194
    }
823✔
195

196
    vector<Real> Basket::remainingNotionals(const Date& endDate) const 
825✔
197
    {
198
        QL_REQUIRE(endDate >= refDate_, 
825✔
199
            "Target date lies before basket inception");
200

201
        std::vector<Real> calcBufferNotionals;
202
        const std::vector<Size>& alive = liveList(endDate);
825✔
203
        calcBufferNotionals.reserve(alive.size());
825✔
204
        for(Size i=0; i<alive.size(); i++)
80,320✔
205
            calcBufferNotionals.push_back(
206
                exposure(pool_->names()[i], endDate)
79,495✔
207
                );// some better way to trim it? 
208
        return calcBufferNotionals;
825✔
209
    }
210

211
    std::vector<Probability> Basket::remainingProbabilities(const Date& d) const 
2,473✔
212
    {
213
        QL_REQUIRE(d >= refDate_, "Target date lies before basket inception");
2,473✔
214
        vector<Real> prob;
215
        const std::vector<Size>& alive = liveList();
216

217
        prob.reserve(alive.size());
2,473✔
218
        for(Size i=0; i<alive.size(); i++)
248,963✔
219
            prob.push_back(pool_->get(pool_->names()[i]).defaultProbability(
492,980✔
220
                pool_->defaultKeys()[i])->defaultProbability(d, true));
739,470✔
221
        return prob;
2,473✔
222
    }
223

224
    /* It is supossed to return the addition of ALL notionals from the 
225
    requested ctpty......*/
226
    Real Basket::exposure(const std::string& name, const Date& d) const {
11,536,372✔
227
        //'this->names_' contains duplicates, contrary to 'pool->names'
228
        auto match = std::find(pool_->names().begin(), pool_->names().end(), name);
11,536,372✔
229
        QL_REQUIRE(match != pool_->names().end(), "Name not in basket.");
11,536,372✔
230
        Real totalNotional = 0.;
231
        do{
232
            totalNotional += 
11,536,372✔
233
             // NOT IMPLEMENTED YET:
234
    //positions_[std::distance(names_.begin(), match)]->expectedExposure(d);
235
                notionals_[std::distance(pool_->names().begin(), match)];
11,536,372✔
236
            ++match;
237
            match = std::find(match, pool_->names().end(), name);
11,536,372✔
238
        }while(match != pool_->names().end());
11,536,372✔
239

240
        return totalNotional;
11,536,372✔
241
        //Size position = std::distance(poolNames.begin(), 
242
        //    std::find(poolNames.begin(), poolNames.end(), name));
243
        //QL_REQUIRE(position < pool_->size(), "Name not in pool list");
244

245
        //return positions_[position]->expectedExposure(d);
246
    }
247

248
    std::vector<std::string> Basket::remainingNames(const Date& endDate) const 
111✔
249
    {
250
        // maybe return zero directly instead?:
251
        QL_REQUIRE(endDate >= refDate_, 
111✔
252
            "Target date lies before basket inception");
253

254
        const std::vector<Size>& alive = liveList(endDate);
111✔
255
        std::vector<std::string> calcBufferNames;
256
        calcBufferNames.reserve(alive.size());
111✔
257
        for (unsigned long i : alive)
9,106✔
258
            calcBufferNames.push_back(pool_->names()[i]);
8,995✔
259
        return calcBufferNames;
111✔
UNCOV
260
    }
×
261

262
    vector<DefaultProbKey> Basket::remainingDefaultKeys(const Date& endDate) const 
111✔
263
    {
264
        QL_REQUIRE(endDate >= refDate_,
111✔
265
            "Target date lies before basket inception");
266

267
        const std::vector<Size>& alive = liveList(endDate);
111✔
268
        vector<DefaultProbKey> defKeys;
269
        defKeys.reserve(alive.size());
111✔
270
        for (unsigned long i : alive)
9,106✔
271
            defKeys.push_back(pool_->defaultKeys()[i]);
8,995✔
272
        return defKeys;
111✔
UNCOV
273
    }
×
274

275
    Size Basket::remainingSize() const {
167,322✔
276
        return evalDateLiveList_.size();
167,322✔
277
    }
278

UNCOV
279
    Size Basket::remainingSize(const Date& d) const {
×
UNCOV
280
        return remainingDefaultKeys(d).size();
×
281
    }
282

283
    /* computed on the inception values, notice the positions might have 
284
    amortized or changed in value and the total outstanding notional might 
285
    differ from the inception one.*/
286
    Real Basket::remainingDetachmentAmount(const Date& endDate) const {
111✔
287
        QL_REQUIRE(endDate >= refDate_, 
111✔
288
            "Target date lies before basket inception");
289
        return detachmentAmount_;
111✔
290
    }
291

292
    Real Basket::remainingAttachmentAmount(const Date& endDate) const {
111✔
293
        // maybe return zero directly instead?:
294
        QL_REQUIRE(endDate >= refDate_, 
111✔
295
            "Target date lies before basket inception");
296
        Real loss = settledLoss(endDate);
111✔
297
        return std::min(detachmentAmount_, attachmentAmount_ + 
333✔
298
            std::max(0.0, loss - attachmentAmount_));
196✔
299
    }
300

301
    Probability Basket::probOverLoss(const Date& d, Real lossFraction) const {
×
302
        // convert initial basket fraction to remaining basket fraction
UNCOV
303
        calculate();
×
304
        // if eaten up all the tranche the prob of losing any amount is 1 
305
        //  (we have already lost it)
UNCOV
306
        if(evalDateRemainingNot_ == 0.) return 1.;
×
307

308
        // Turn to live (remaining) tranche units to feed into the model request
309
        Real xPtfl = attachmentAmount_ + 
×
310
            (detachmentAmount_-attachmentAmount_)*lossFraction;
×
UNCOV
311
        Real xPrim = (xPtfl- evalDateAttachAmount_)/
×
UNCOV
312
            (detachmentAmount_-evalDateAttachAmount_);
×
313
        // in live tranche fractional units
314
        // if the level falls within realized losses the prob is 1.
315
        if(xPtfl < 0.) return 1.;
×
316

UNCOV
317
        return lossModel_->probOverLoss(d, xPrim);
×
318
    }
319

320
    Real Basket::percentile(const Date& d, Probability prob) const {
×
UNCOV
321
        calculate();
×
UNCOV
322
        return lossModel_->percentile(d, prob);
×
323
    }
324

325
    Real Basket::expectedTrancheLoss(const Date& d) const {
3,003✔
326
        calculate();
3,003✔
327
        return cumulatedLoss() + lossModel_->expectedTrancheLoss(d);
3,003✔
328
    }
329

330
    std::vector<Real> Basket::splitVaRLevel(const Date& date, Real loss) const {
×
UNCOV
331
        calculate();
×
UNCOV
332
        return lossModel_->splitVaRLevel(date, loss);
×
333
    }
334

335
    Real Basket::expectedShortfall(const Date& d, Probability prob) const {
×
UNCOV
336
        calculate();
×
UNCOV
337
        return lossModel_->expectedShortfall(d, prob);
×
338
    }
339

340
    std::map<Real, Probability> Basket::lossDistribution(const Date& d) const {
×
UNCOV
341
        calculate();
×
UNCOV
342
        return lossModel_->lossDistribution(d);
×
343
    }
344

345
    std::vector<Probability> 
346
        Basket::probsBeingNthEvent(Size n, const Date& d) const {
×
347

348
        Size alreadyDefaulted = pool_->size() - remainingNames().size();
×
UNCOV
349
        if(alreadyDefaulted >=n) 
×
350
            return std::vector<Probability>(remainingNames().size(), 0.);
×
351

UNCOV
352
        calculate();
×
UNCOV
353
        return lossModel_->probsBeingNthEvent(n-alreadyDefaulted, d);
×
354
    }
355

356
    Real Basket::defaultCorrelation(const Date& d, Size iName, Size jName) const{
18✔
357
        calculate();
18✔
358
        return lossModel_->defaultCorrelation(d, iName, jName);
18✔
359

360
    }
361

362
    /*! Returns the probaility of having a given or larger number of 
363
    defaults in the basket portfolio at a given time.
364
    */
365
    Probability Basket::probAtLeastNEvents(Size n, const Date& d) const{
13,768✔
366
        calculate();
13,768✔
367
        return lossModel_->probAtLeastNEvents(n, d);
13,768✔
368

369
    }
370

371
    Real Basket::recoveryRate(const Date& d, Size iName) const {
12,160✔
372
        calculate();
12,160✔
373
        return 
374
            lossModel_->expectedRecovery(d, iName, pool_->defaultKeys()[iName]);
12,160✔
375
    }
376

377
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc