Coverage Report

Created: 2025-12-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/credit/distribution.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008 Roland Lichters
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <https://www.quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
/*! \file distribution.cpp
21
    \brief Discretized probability density and cumulative probability
22
*/
23
24
#include <ql/experimental/credit/distribution.hpp>
25
#include <ql/math/comparison.hpp>
26
#include <ql/errors.hpp>
27
#include <algorithm>
28
#include <functional>
29
30
namespace QuantLib {
31
32
    //-------------------------------------------------------------------------
33
    Distribution::Distribution (int nBuckets, Real xmin, Real xmax)
34
    //-------------------------------------------------------------------------
35
0
        : size_(nBuckets),
36
0
          xmin_(xmin), xmax_(xmax), count_(nBuckets),
37
0
          x_(nBuckets,0), dx_(nBuckets,0),
38
0
          density_(nBuckets,0),
39
0
          cumulativeDensity_(nBuckets,0),
40
0
          excessProbability_(nBuckets,0),
41
0
          cumulativeExcessProbability_(nBuckets,0),
42
0
          average_(nBuckets,0),
43
0
          overFlow_(0), underFlow_(0),
44
0
          isNormalized_(false) {
45
0
        for (int i = 0; i < nBuckets; i++) {
46
0
            dx_[i] = (xmax - xmin) / nBuckets;
47
0
            x_[i] = (i == 0 ? xmin : x_[i-1] + dx_[i-1]);
48
0
        }
49
        // ensure we match exactly the domain, otherwise we might fail the
50
        //   locate test because of precission mismatches
51
0
        dx_.back() = xmax - x_.back();
52
0
    }
53
54
    //-------------------------------------------------------------------------
55
0
    int Distribution::locate (Real x) {
56
    //-------------------------------------------------------------------------
57
0
        QL_REQUIRE ((x >= x_.front() || close(x, x_.front())) &&
58
0
                    (x <= x_.back() + dx_.back()
59
0
                     || close(x, x_.back() + dx_.back())),
60
0
                    "coordinate " << x
61
0
                    << " out of range [" << x_.front() << "; "
62
0
                    << x_.back() + dx_.back() << "]");
63
0
        for (Size i = 0; i < x_.size(); i++) {
64
0
            if (x_[i] > x)
65
0
                return i - 1;
66
0
        }
67
0
        return x_.size() - 1;
68
0
    }
69
70
    //-------------------------------------------------------------------------
71
0
    Real Distribution::dx (Real x) {
72
    //-------------------------------------------------------------------------
73
0
        int i = locate (x);
74
0
        return dx_[i];
75
0
    }
76
77
    //-------------------------------------------------------------------------
78
0
    void Distribution::add (Real value) {
79
    //-------------------------------------------------------------------------
80
0
        isNormalized_ = false;
81
0
        if (value < x_.front()) underFlow_++;
82
0
        else {
83
0
            for (Size i = 0; i < count_.size(); i++) {
84
0
                if (x_[i] + dx_[i] > value) {
85
0
                    count_[i]++;
86
0
                    average_[i] += value;
87
0
                    return;
88
0
                }
89
0
            }
90
0
            overFlow_++;
91
0
        }
92
0
    }
93
94
    //-------------------------------------------------------------------------
95
0
    void Distribution::addDensity (int bucket, Real value) {
96
    //-------------------------------------------------------------------------
97
0
        QL_REQUIRE (bucket >= 0 && bucket < size_, "bucket out of range");
98
0
        isNormalized_ = false;
99
0
        density_[bucket] += value;
100
0
    }
101
102
    //-------------------------------------------------------------------------
103
0
    void Distribution::addAverage (int bucket, Real value) {
104
    //-------------------------------------------------------------------------
105
0
        QL_REQUIRE (bucket >= 0 && bucket < size_, "bucket out of range");
106
0
        isNormalized_ = false;
107
0
        average_[bucket] += value;
108
0
    }
109
110
    //-------------------------------------------------------------------------
111
0
    void Distribution::normalize () {
112
    //-------------------------------------------------------------------------
113
0
        if (isNormalized_)
114
0
            return;
115
116
0
        int count = underFlow_ + overFlow_;
117
0
        for (int i = 0; i < size_; i++)
118
0
            count += count_[i];
119
120
0
        excessProbability_[0] = 1.0;
121
0
        cumulativeExcessProbability_[0] = 0.0;
122
0
        for (int i = 0; i < size_; i++) {
123
0
            if (count > 0) {
124
0
                density_[i] = 1.0 / dx_[i] * count_[i] / count;
125
0
                if (count_[i] > 0)
126
0
                    average_[i] /= count_[i];
127
0
            }
128
0
            if (density_[i] == 0.0)
129
0
                average_[i] = x_[i] + dx_[i]/2;
130
131
0
            cumulativeDensity_[i] = density_[i] * dx_[i];
132
0
            if (i > 0) {
133
0
                cumulativeDensity_[i] += cumulativeDensity_[i-1];
134
0
                excessProbability_[i] = 1.0 - cumulativeDensity_[i-1];
135
//                     excessProbability_[i] = excessProbability_[i-1]
136
//                         - density_[i-1] * dx_[i-1];
137
//                     cumulativeExcessProbability_[i]
138
//                         = (excessProbability_[i-1] +
139
//                            excessProbability_[i]) / 2 * dx_[i-1]
140
//                         + cumulativeExcessProbability_[i-1];
141
0
                cumulativeExcessProbability_[i]
142
0
                    = excessProbability_[i-1] * dx_[i-1]
143
0
                    + cumulativeExcessProbability_[i-1];
144
0
            }
145
0
        }
146
147
0
        isNormalized_ = true;
148
0
    }
149
150
    //-------------------------------------------------------------------------
151
0
    Real Distribution::confidenceLevel (Real quantil) {
152
    //-------------------------------------------------------------------------
153
0
        normalize();
154
0
        for (int i = 0; i < size_; i++) {
155
0
            if (cumulativeDensity_[i] > quantil)
156
0
                return x_[i] + dx_[i];
157
0
        }
158
0
        return x_.back() + dx_.back();
159
0
    }
160
161
    //-------------------------------------------------------------------------
162
0
    Real Distribution::expectedValue () {
163
    //-------------------------------------------------------------------------
164
0
        normalize();
165
0
        Real expected = 0;
166
0
        for (int i = 0; i < size_; i++) {
167
0
            Real x = x_[i] + dx_[i]/2;
168
0
            expected += x * dx_[i] * density_[i];
169
0
        }
170
0
        return expected;
171
0
    }
172
173
    //-------------------------------------------------------------------------
174
0
    Real Distribution::trancheExpectedValue (Real a, Real d) {
175
    //-------------------------------------------------------------------------
176
0
        normalize();
177
0
        Real expected = 0;
178
0
        for (int i = 0; i < size_; i++) {
179
0
            Real x = x_[i] + dx_[i]/2;
180
0
            if (x < a)
181
0
                continue;
182
0
            if (x > d)
183
0
                break;
184
0
            expected += (x - a) * dx_[i] * density_[i];
185
0
        }
186
187
0
        expected += (d - a) * (1.0 - cumulativeDensity (d));
188
189
0
        return expected;
190
0
    }
191
192
//     Real Distribution::cumulativeExcessProbability (Real a, Real b) {
193
//         //normalize();
194
//         Real integral = 0.0;
195
//         for (int i = 0; i < size_; i++) {
196
//             if (x_[i] >= b) break;
197
//             if (x_[i] >= a)
198
//                 integral += dx_[i] * excessProbability_[i];
199
//         }
200
//         return integral;
201
//     }
202
203
    //-------------------------------------------------------------------------
204
0
    Real Distribution::cumulativeExcessProbability (Real a, Real b) {
205
    //-------------------------------------------------------------------------
206
0
        normalize();
207
0
        QL_REQUIRE (b <= xmax_,
208
0
                 "end of interval " << b << " out of range ["
209
0
                 << xmin_ << ", " << xmax_ << "]");
210
0
        QL_REQUIRE (a >= xmin_,
211
0
                 "start of interval " << a << " out of range ["
212
0
                 << xmin_ << ", " << xmax_ << "]");
213
214
0
        int i = locate (a);
215
0
        int j = locate (b);
216
0
        return cumulativeExcessProbability_[j]-cumulativeExcessProbability_[i];
217
0
    }
218
219
    //-------------------------------------------------------------------------
220
0
    Real Distribution::cumulativeDensity (Real x) {
221
    //-------------------------------------------------------------------------
222
0
        Real tiny = dx_.back() * 1e-3;
223
0
        QL_REQUIRE (x > 0, "x must be positive");
224
0
        normalize();
225
0
        for (int i = 0; i < size_; i++) {
226
0
            if (x_[i] + dx_[i] + tiny >= x)
227
0
                return ((x - x_[i]) * cumulativeDensity_[i]
228
0
                     + (x_[i] + dx_[i] - x) * cumulativeDensity_[i-1]) / dx_[i];
229
0
        }
230
0
        QL_FAIL ("x = " << x << " beyond distribution cutoff "
231
0
                 << x_.back() + dx_.back());
232
0
    }
233
234
    //-------------------------------------------------------------------------
235
    // Dangerous to perform calls to members after this; transform and clone?
236
0
    void Distribution::tranche (Real attachmentPoint, Real detachmentPoint) {
237
    //-------------------------------------------------------------------------
238
0
        QL_REQUIRE (attachmentPoint < detachmentPoint,
239
0
                 "attachment >= detachment point");
240
0
        QL_REQUIRE (x_.back() > attachmentPoint && 
241
0
                    x_.back()+dx_.back() >= detachmentPoint,
242
0
                 "attachment or detachment too large");
243
244
0
        normalize();
245
246
        // shift
247
0
        while (x_[0] < attachmentPoint) {
248
0
            x_.erase(x_.begin());
249
0
            dx_.erase(dx_.begin());
250
0
            count_.erase(count_.begin());
251
0
            density_.erase(density_.begin());
252
0
            cumulativeDensity_.erase(cumulativeDensity_.begin());
253
0
            excessProbability_.erase(excessProbability_.begin());
254
0
        }
255
256
        // remove losses over detachment point:
257
0
        auto detachPosit = std::find_if(x_.begin(), x_.end(), [=](Real x){ return x > detachmentPoint; });
258
0
        if(detachPosit != x_.end())
259
0
            x_.erase(detachPosit + 1, x_.end());
260
261
0
        size_ = x_.size();
262
0
        cumulativeDensity_.erase(cumulativeDensity_.begin() + size_, 
263
0
            cumulativeDensity_.end());
264
0
        cumulativeDensity_.back() = 1.; 
265
0
        count_.erase(count_.begin() + size_, count_.end());
266
0
        dx_.erase(dx_.begin() + size_, dx_.end());
267
268
        // truncate
269
0
        for (Real& i : x_) {
270
0
            i = std::min(std::max(i - attachmentPoint, 0.), detachmentPoint - attachmentPoint);
271
0
        }
272
273
0
        density_.clear(); 
274
0
        excessProbability_.clear();
275
0
        cumulativeExcessProbability_.clear(); //? reuse?
276
0
        density_.push_back((cumulativeDensity_[0]-0.)/dx_[0]);
277
0
        excessProbability_.push_back(1.);
278
0
        for(Integer i=1; i<size_-1; i++) {
279
0
            excessProbability_.push_back(1.-cumulativeDensity_[i-1]);
280
0
            density_.push_back((cumulativeDensity_[i]-
281
0
                cumulativeDensity_[i-1])/dx_[i]);
282
0
        }
283
0
        excessProbability_.push_back(1.-cumulativeDensity_.back());
284
0
        density_.push_back((1.-cumulativeDensity_.back())/dx_.back());
285
0
    }
286
287
    //-------------------------------------------------------------------------
288
    Distribution ManipulateDistribution::convolve (const Distribution& d1,
289
0
                                                   const Distribution& d2) {
290
    //-------------------------------------------------------------------------
291
        // force equal constant bucket sizes
292
0
        QL_REQUIRE (d1.dx_[0] == d2.dx_[0], "bucket sizes differ in d1 and d2");
293
0
        for (Size i = 1; i < d1.size(); i++)
294
0
            QL_REQUIRE (d1.dx_[i] == d1.dx_[i-1], "bucket size varies in d1");
295
0
        for (Size i = 1; i < d2.size(); i++)
296
0
            QL_REQUIRE (d2.dx_[i] == d2.dx_[i-1], "bucket size varies in d2");
297
298
        // force offset 0
299
0
        QL_REQUIRE (d1.xmin_ == 0.0 && d2.xmin_ == 0.0,
300
0
                 "distributions offset larger than 0");
301
302
0
        Distribution dist(d1.size() + d2.size() - 1,
303
0
                          0.0, // assuming both distributions have xmin = 0
304
0
                          d1.xmax_ + d2.xmax_);
305
306
0
        for (Size i1 = 0; i1 < d1.size(); i1++) {
307
0
            Real dx = d1.dx_[i1];
308
0
            for (Size i2 = 0; i2 < d2.size(); i2++)
309
0
                dist.density_[i1+i2] = d1.density_[i1] * d2.density_[i2] * dx;
310
0
        }
311
312
        // update cumulated and excess
313
0
        dist.excessProbability_[0] = 1.0;
314
0
        for (Size i = 0; i < dist.size(); i++) {
315
0
            dist.cumulativeDensity_[i] = dist.density_[i] * dist.dx_[i];
316
0
            if (i > 0) {
317
0
                dist.cumulativeDensity_[i] += dist.cumulativeDensity_[i-1];
318
0
                dist.excessProbability_[i] = dist.excessProbability_[i-1]
319
0
                    - dist.density_[i-1] * dist.dx_[i-1];
320
0
            }
321
0
        }
322
323
0
        return dist;
324
0
    }
325
326
327
    //-------------------------------------------------------------------------
328
0
    Real Distribution::expectedShortfall (Real percValue) {
329
    //-------------------------------------------------------------------------
330
0
        QL_REQUIRE(percValue >= 0. && percValue <= 1., 
331
0
            "Incorrect percentile");
332
0
        normalize();
333
0
        Real expected = 0;
334
0
        Integer iVal = locate(confidenceLevel(percValue));
335
336
0
        if(iVal == size_-1) return x_.back();
337
338
0
        for (int i = iVal; i < size_; i++)
339
0
            expected += x_[i] * 
340
0
                (cumulativeDensity_[i] - cumulativeDensity_[i-1]);
341
0
        return expected/(1.-cumulativeDensity_.at(iVal));
342
0
    }
343
344
}