Coverage Report

Created: 2025-10-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/credit/integralntdengine.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
#include <ql/experimental/credit/integralntdengine.hpp>
21
#include <ql/cashflows/fixedratecoupon.hpp>
22
#include <ql/termstructures/yieldtermstructure.hpp>
23
#include <ql/experimental/credit/basket.hpp>
24
#include <numeric>
25
26
namespace QuantLib {
27
28
0
    void IntegralNtdEngine::calculate() const {
29
0
        Date today = Settings::instance().evaluationDate();
30
31
0
        results_.errorEstimate = Null<Real>();
32
0
        results_.value = 0.0;
33
0
        results_.premiumValue = 0.0;
34
0
        results_.upfrontPremiumValue = 0.;
35
0
        Real accrualValue = 0.0;
36
0
        Real claimValue = 0.0;
37
0
        Date d0;
38
        /* Given the expense of probsBeingNthEvent both in integrable and 
39
        monte carlo algorithms this engine tests who to call.
40
        Warning: This is not entirely a basket property but of the model too.
41
        The basket has to have all notionals equal but it is the model which
42
        determines the recovery; having all the market recoveries equal is not
43
        enough since we might be using a loss model which is stochastic in the
44
        recovery rates.
45
        */
46
0
        bool basketIsHomogeneous = true;// hardcoded by now
47
48
0
        for (auto& i : arguments_.premiumLeg) {
49
0
            ext::shared_ptr<FixedRateCoupon> coupon = ext::dynamic_pointer_cast<FixedRateCoupon>(i);
50
0
            Date d = i->date();
51
0
            if (d > discountCurve_->referenceDate()) {
52
                /*
53
                std::vector<Probability> probsTriggering =
54
                    arguments_.basket->probsBeingNthEvent(arguments_.ntdOrder, 
55
                        d);
56
                Probability defaultProb = 
57
                    std::accumulate(probsTriggering.begin(), 
58
                    probsTriggering.end(), Real(0.));
59
                // OVERKILL???? 1-probAtLeastNEvents is enough
60
61
*/
62
                // prob of contract not having been triggered by date of payment
63
0
                Probability probNonTriggered = 
64
0
                    1. - arguments_.basket->probAtLeastNEvents(
65
0
                        arguments_.ntdOrder, d);
66
67
0
                results_.premiumValue +=
68
0
                    i->amount() * discountCurve_->discount(d) * probNonTriggered;
69
                ////   * (1.0 - defaultProb);
70
71
0
                if (coupon->accrualStartDate() >= 
72
0
                    discountCurve_->referenceDate())
73
0
                    d = coupon->accrualStartDate();
74
0
                else
75
0
                    d = discountCurve_->referenceDate();
76
77
                // do steps of specified size
78
0
                d0 = d;
79
0
                Period stepSize = integrationStepSize_;
80
/*
81
                probsTriggering =
82
                    arguments_.basket->probsBeingNthEvent(arguments_.ntdOrder, 
83
                    ///////REDUNDANT?
84
                        d0);
85
                Probability defProb0 = std::accumulate(probsTriggering.begin(), 
86
                ///OVERKILL????
87
                    probsTriggering.end(), Real(0.));
88
*/
89
0
                Probability defProb0 = arguments_.basket->probAtLeastNEvents(
90
0
                        arguments_.ntdOrder, d0);
91
0
                std::vector<Probability> probsTriggering, probsTriggering1;
92
0
                do {
93
0
                    DiscountFactor disc = discountCurve_->discount(d);
94
95
0
                    Probability defProb1;
96
0
                    if(basketIsHomogeneous) {//take test out of the while loop
97
0
                        defProb1 = arguments_.basket->probAtLeastNEvents(
98
0
                            arguments_.ntdOrder, d);
99
0
                        claimValue -= (defProb1-defProb0)
100
0
                            * arguments_.basket->claim()->amount(d, 
101
0
                                arguments_.notional, 
102
0
                                arguments_.basket->recoveryRate(d, 0))
103
0
                            * disc;
104
105
0
                    }else{
106
0
                        probsTriggering1 =
107
0
                            arguments_.basket->probsBeingNthEvent(
108
0
                                arguments_.ntdOrder, d);
109
0
                        defProb1 = std::accumulate(probsTriggering1.begin(), 
110
0
                            probsTriggering1.end(), Real(0.));
111
                        /*Recoveries might differ along names, depending on 
112
                        which name is triggering the contract the loss will be 
113
                        different  
114
                        There is an issue here; MC engines can still be used 
115
                        since the prob of triggering the contract can be 
116
                        extracted from the simulation from the 
117
                        probsBeingNthEvent statistic. Yet, when the RR is 
118
                        stochastic the realized value of the RR is the expected 
119
                        one subject/conditional to the contract being triggered;
120
                        not simply the expected value. For this reason the MC 
121
                        can not be used through the statistic but has to consume
122
                        the simulations directly.
123
                        */
124
0
                        for(Size iName=0; 
125
0
                            iName<arguments_.basket->remainingSize(); 
126
0
                            iName++) 
127
0
                        {
128
0
                            claimValue -= (probsTriggering1[iName]-
129
0
                                probsTriggering[iName])
130
0
                                * arguments_.basket->claim()->amount(d, 
131
0
                                    arguments_.notional,// [iName]! 
132
0
                                    arguments_.basket->recoveryRate(d, iName))
133
0
                                * disc;
134
0
                        }
135
0
                        probsTriggering = probsTriggering1;
136
0
                    }
137
138
0
                    Probability dcfdd = defProb1 - defProb0;
139
0
                    defProb0 = defProb1;
140
141
0
                    if (arguments_.settlePremiumAccrual)
142
0
                        accrualValue += coupon->accruedAmount(d)*disc*dcfdd;
143
144
0
                    d0 = d;
145
0
                    d = d0 + stepSize;
146
                    // reduce step size ?
147
0
                    if (stepSize != 1*Days && d > coupon->accrualEndDate()) {
148
0
                        stepSize = 1*Days;
149
0
                        d = d0 + stepSize;
150
0
                    }
151
0
                }
152
0
                while (d <= coupon->accrualEndDate());
153
0
            }
154
0
        }
155
156
        // The upfront might be due before the curve ref date...
157
0
        if (!arguments_.premiumLeg[0]->hasOccurred(today))
158
0
            results_.upfrontPremiumValue =
159
0
                arguments_.basket->remainingNotional() 
160
0
                    * arguments_.upfrontRate
161
0
                    * discountCurve_->discount(
162
0
                        ext::dynamic_pointer_cast<FixedRateCoupon>(
163
0
                            arguments_.premiumLeg[0])->accrualStartDate());
164
0
        if (arguments_.side == Protection::Buyer) {
165
0
            results_.premiumValue *= -1;
166
0
            accrualValue *= -1;
167
0
            claimValue *= -1;
168
0
            results_.upfrontPremiumValue *= -1;
169
0
        }
170
171
0
        results_.value = results_.premiumValue + accrualValue + claimValue + 
172
0
            results_.upfrontPremiumValue;
173
174
0
        results_.fairPremium = -arguments_.premiumRate * claimValue 
175
0
            / (results_.premiumValue + accrualValue);
176
        // alternatively use results buffers and omit locals.
177
0
        results_.protectionValue = claimValue;
178
179
0
        results_.additionalResults["fairPremium"] = results_.fairPremium;
180
0
        results_.additionalResults["premiumLegNPV"] = 
181
0
            Real(results_.premiumValue + results_.upfrontPremiumValue);
182
0
        results_.additionalResults["protectionLegNPV"] = 
183
0
            results_.protectionValue;
184
0
    }
185
186
}