Coverage Report

Created: 2026-01-25 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/credit/syntheticcdo.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/syntheticcdo.hpp>
21
22
#ifndef QL_PATCH_SOLARIS
23
24
#include <ql/cashflows/fixedratecoupon.hpp>
25
#include <ql/event.hpp>
26
#include <ql/math/solvers1d/brent.hpp>
27
#include <ql/termstructures/yieldtermstructure.hpp>
28
#include <ql/experimental/credit/gaussianlhplossmodel.hpp>
29
#include <ql/experimental/credit/midpointcdoengine.hpp>
30
#include <ql/optional.hpp>
31
32
using namespace std;
33
34
namespace QuantLib {
35
36
    SyntheticCDO::SyntheticCDO(const ext::shared_ptr<Basket>& basket,
37
                               Protection::Side side,
38
                               Schedule schedule,
39
                               Rate upfrontRate,
40
                               Rate runningRate,
41
                               const DayCounter& dayCounter,
42
                               BusinessDayConvention paymentConvention,
43
                               ext::optional<Real> notional)
44
0
    : basket_(basket), side_(side), upfrontRate_(upfrontRate), runningRate_(runningRate),
45
0
      leverageFactor_(notional ? *notional / basket->trancheNotional() : Real(1.)), // NOLINT(readability-implicit-bool-conversion)
46
0
      dayCounter_(dayCounter), paymentConvention_(paymentConvention) {
47
0
        QL_REQUIRE(!basket->names().empty(), "basket is empty");
48
        // Basket inception must lie before contract protection start.
49
0
        QL_REQUIRE(basket->refDate() <= schedule.startDate(),
50
        //using the start date of the schedule might be wrong, think of the
51
        //  CDS rule
52
0
            "Basket did not exist before contract start.");
53
54
        // Notice the notional is that of the basket at basket inception, some
55
        //   names might have defaulted in between
56
0
        normalizedLeg_ = FixedRateLeg(std::move(schedule))
57
0
            .withNotionals(basket_->trancheNotional() * leverageFactor_)
58
0
            .withCouponRates(runningRate, dayCounter)
59
0
            .withPaymentAdjustment(paymentConvention);
60
61
        // Date today = Settings::instance().evaluationDate();
62
63
        // register with probabilities if the corresponding issuer is, baskets
64
        //   are not registered with the DTS
65
0
        for (Size i = 0; i < basket->names().size(); i++) {
66
            /* This turns out to be a problem: depends on today but I am not
67
            modifying the registrations, if we go back in time in the
68
            calculations this would left me unregistered to some. Not impossible
69
            to de-register and register when updating but i am dropping it.
70
71
            if(!basket->pool()->get(basket->names()[i]).
72
                defaultedBetween(schedule.dates()[0], today,
73
                                     basket->pool()->defaultKeys()[i]))
74
            */
75
            // registers with the associated curve (issuer and event type)
76
            // \todo make it possible to access them by name instead of index
77
0
            registerWith(basket->pool()->get(basket->names()[i]).
78
0
                defaultProbability(basket->pool()->defaultKeys()[i]));
79
            /* \todo Issuers should be observables/obsrvr and they would in turn
80
            regiter with the DTS; only we might get updates from curves we do
81
            not use.
82
            */
83
0
        }
84
0
        registerWith(basket_);
85
0
    }
Unexecuted instantiation: QuantLib::SyntheticCDO::SyntheticCDO(boost::shared_ptr<QuantLib::Basket> const&, QuantLib::Protection::Side, QuantLib::Schedule, double, double, QuantLib::DayCounter const&, QuantLib::BusinessDayConvention, std::__1::optional<double>)
Unexecuted instantiation: QuantLib::SyntheticCDO::SyntheticCDO(boost::shared_ptr<QuantLib::Basket> const&, QuantLib::Protection::Side, QuantLib::Schedule, double, double, QuantLib::DayCounter const&, QuantLib::BusinessDayConvention, std::__1::optional<double>)
86
87
0
    Rate SyntheticCDO::premiumValue () const {
88
0
        calculate();
89
0
        return premiumValue_;
90
0
    }
91
92
0
    Rate SyntheticCDO::protectionValue () const {
93
0
        calculate();
94
0
        return protectionValue_;
95
0
    }
96
97
0
    Real SyntheticCDO::premiumLegNPV() const {
98
0
        calculate();
99
0
        if(side_ == Protection::Buyer) return premiumValue_;
100
0
        return -premiumValue_;
101
0
    }
102
103
0
    Real SyntheticCDO::protectionLegNPV() const {
104
0
        calculate();
105
0
        if(side_ == Protection::Buyer) return -protectionValue_;
106
0
        return protectionValue_;
107
0
    }
108
109
0
    Rate SyntheticCDO::fairPremium () const {
110
0
        calculate();
111
0
        QL_REQUIRE(premiumValue_ != 0, "Attempted divide by zero while calculating syntheticCDO premium.");
112
0
        return runningRate_
113
0
            * (protectionValue_ - upfrontPremiumValue_) / premiumValue_;
114
0
    }
115
116
0
    Rate SyntheticCDO::fairUpfrontPremium () const {
117
0
        calculate();
118
0
        return (protectionValue_ - premiumValue_) / remainingNotional_;
119
0
    }
120
121
0
    std::vector<Real> SyntheticCDO::expectedTrancheLoss() const {
122
0
        calculate();
123
0
        return expectedTrancheLoss_;
124
0
    }
125
126
0
    Size SyntheticCDO::error () const {
127
0
        calculate();
128
0
        return error_;
129
0
    }
130
131
0
    bool SyntheticCDO::isExpired () const {
132
        // FIXME: it could have also expired (knocked out) because theres
133
        //   no remaining tranche notional.
134
0
        return detail::simple_event(normalizedLeg_.back()->date())
135
0
               .hasOccurred();
136
0
    }
137
138
0
    Real SyntheticCDO::remainingNotional() const {
139
0
        calculate();
140
0
        return remainingNotional_;
141
0
    }
142
143
0
    void SyntheticCDO::setupArguments(PricingEngine::arguments* args) const {
144
0
        auto* arguments = dynamic_cast<SyntheticCDO::arguments*>(args);
145
0
        QL_REQUIRE(arguments != nullptr, "wrong argument type");
146
0
        arguments->basket = basket_;
147
0
        arguments->side = side_;
148
0
        arguments->normalizedLeg = normalizedLeg_;
149
150
0
        arguments->upfrontRate = upfrontRate_;
151
0
        arguments->runningRate = runningRate_;
152
0
        arguments->dayCounter = dayCounter_;
153
0
        arguments->paymentConvention = paymentConvention_;
154
0
        arguments->leverageFactor = leverageFactor_;
155
0
    }
156
157
0
    void SyntheticCDO::fetchResults(const PricingEngine::results* r) const {
158
0
        Instrument::fetchResults(r);
159
160
0
        const auto* results = dynamic_cast<const SyntheticCDO::results*>(r);
161
0
        QL_REQUIRE(results != nullptr, "wrong result type");
162
163
0
        premiumValue_ = results->premiumValue;
164
0
        protectionValue_ = results->protectionValue;
165
0
        upfrontPremiumValue_ = results->upfrontPremiumValue;
166
0
        remainingNotional_ = results->remainingNotional;
167
0
        error_ = results->error;
168
0
        expectedTrancheLoss_ = results->expectedTrancheLoss;
169
0
    }
170
171
0
    void SyntheticCDO::setupExpired() const {
172
0
        Instrument::setupExpired();
173
0
        premiumValue_ = 0.0;
174
0
        protectionValue_ = 0.0;
175
0
        upfrontPremiumValue_ = 0.0;
176
0
        remainingNotional_ = 1.0;
177
0
        expectedTrancheLoss_.clear();
178
0
    }
179
180
0
    void SyntheticCDO::arguments::validate() const {
181
0
        QL_REQUIRE(side != Protection::Side(-1), "side not set");
182
0
        QL_REQUIRE(basket && !basket->names().empty(), "no basket given");
183
0
        QL_REQUIRE(runningRate != Null<Real>(), "no premium rate given");
184
0
        QL_REQUIRE(upfrontRate != Null<Real>(), "no upfront rate given");
185
0
        QL_REQUIRE(!dayCounter.empty(), "no day counter given");
186
0
    }
187
188
0
    void SyntheticCDO::results::reset() {
189
0
        Instrument::results::reset();
190
0
        premiumValue = Null<Real>();
191
0
        protectionValue = Null<Real>();
192
0
        upfrontPremiumValue = Null<Real>();
193
0
        remainingNotional = Null<Real>();
194
0
        error = 0;
195
0
        expectedTrancheLoss.clear();
196
0
    }
197
198
199
200
201
202
    namespace {
203
204
        class ObjectiveFunction {
205
          public:
206
            ObjectiveFunction(Real target,
207
                              SimpleQuote& quote,
208
                              PricingEngine& engine,
209
                              const SyntheticCDO::results* results)
210
0
            : target_(target), quote_(quote),
211
0
              engine_(engine), results_(results) {}
212
213
0
            Real operator()(Real guess) const {
214
0
                quote_.setValue(guess);
215
0
                engine_.calculate();
216
0
                return results_->value - target_;
217
0
            }
218
          private:
219
            Real target_;
220
            SimpleQuote& quote_;
221
            PricingEngine& engine_;
222
            const SyntheticCDO::results* results_;
223
        };
224
225
    }
226
227
    // untested, not sure this is not messing up, once it comes out of this
228
    //   the basket model is different.....
229
    Real SyntheticCDO::implicitCorrelation(const std::vector<Real>& recoveries,
230
        const Handle<YieldTermStructure>& discountCurve,
231
        Real targetNPV,
232
        Real accuracy) const
233
0
    {
234
0
        ext::shared_ptr<SimpleQuote> correl(new SimpleQuote(0.0));
235
236
0
        ext::shared_ptr<GaussianLHPLossModel> lhp(new
237
0
            GaussianLHPLossModel(Handle<Quote>(correl), recoveries));
238
239
        // lock
240
0
        basket_->setLossModel(lhp);
241
242
0
        MidPointCDOEngine engineIC(discountCurve);
243
0
        setupArguments(engineIC.getArguments());
244
0
        const auto* results = dynamic_cast<const SyntheticCDO::results*>(engineIC.getResults());
245
246
        // aviod recal of the basket on engine updates through the quote
247
0
        basket_->recalculate();
248
0
        basket_->freeze();
249
250
0
        ObjectiveFunction f(targetNPV, *correl, engineIC, results);
251
0
        Rate guess = 0.001;
252
        //  Rate step = guess*0.1;
253
254
        // wrap/catch to be able to unfreeze the basket:
255
0
        Real solution = Brent().solve(f, accuracy, guess, QL_EPSILON, 1.-QL_EPSILON);
256
0
        basket_->unfreeze();
257
0
        return solution;
258
0
    }
259
260
}
261
262
#endif