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 <http://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/event.hpp>
21#include <ql/experimental/credit/cdo.hpp>
22#include <utility>
23
24using namespace std;
25
26namespace QuantLib {
27
28 CDO::CDO(Real attachment,
29 Real detachment,
30 vector<Real> nominals,
31 const vector<Handle<DefaultProbabilityTermStructure> >& basket,
32 Handle<OneFactorCopula> copula,
33 bool protectionSeller,
34 Schedule premiumSchedule,
35 Rate premiumRate,
36 DayCounter dayCounter,
37 Rate recoveryRate,
38 Rate upfrontPremiumRate,
39 Handle<YieldTermStructure> yieldTS,
40 Size nBuckets,
41 const Period& integrationStep)
42 : attachment_(attachment), detachment_(detachment), nominals_(std::move(nominals)),
43 basket_(basket), copula_(std::move(copula)), protectionSeller_(protectionSeller),
44 premiumSchedule_(std::move(premiumSchedule)), premiumRate_(premiumRate),
45 dayCounter_(std::move(dayCounter)), recoveryRate_(recoveryRate),
46 upfrontPremiumRate_(upfrontPremiumRate), yieldTS_(std::move(yieldTS)), nBuckets_(nBuckets),
47 integrationStep_(integrationStep) {
48
49 QL_REQUIRE (!basket.empty(), "basket is empty");
50 QL_REQUIRE (attachment_ >= 0 && attachment_ < detachment_
51 && detachment_ <= 1,
52 "illegal attachment/detachment point");
53
54 registerWith (h: yieldTS_);
55 registerWith (h: copula_);
56 for (auto& i : basket_)
57 registerWith(h: i);
58
59 QL_REQUIRE (nominals_.size() <= basket_.size(),
60 "nominal vector size too large");
61
62 if (nominals_.size() < basket_.size()) {
63 Size n = basket_.size() - nominals_.size();
64 Real back = nominals_.back();
65 for (Size i = 0; i < n; i++)
66 nominals_.push_back(x: back);
67 }
68
69 QL_REQUIRE (nominals_.size() == basket_.size(),
70 "nominal size " << nominals_.size()
71 << " != basket size " << basket_.size());
72
73 nominal_ = 0;
74 for (Size i = 0; i < nominals_.size(); i++) {
75 lgds_.push_back (x: nominals_[i] * (1.0 - recoveryRate_));
76 nominal_ += nominals_[i];
77 lgd_ += lgds_[i];
78 }
79 xMax_ = detachment_ * nominal_;
80 xMin_ = attachment_ * nominal_;
81 }
82
83
84 bool CDO::isExpired () const {
85 return detail::simple_event(premiumSchedule_.dates().back())
86 .hasOccurred(refDate: yieldTS_->referenceDate());
87 }
88
89
90 void CDO::setupExpired() const {
91 Instrument::setupExpired();
92 }
93
94
95 Real CDO::expectedTrancheLoss (Date d) const {
96 if (d <= basket_.front()->referenceDate())
97 return 0;
98
99 vector<Real> defProb (basket_.size());
100 for (Size j = 0; j < basket_.size(); j++)
101 defProb[j] = basket_[j]->defaultProbability (d);
102
103 LossDistBucketing op (nBuckets_, xMax_);
104 Distribution dist = copula_->integral (f: op, nominals: lgds_, probabilities: defProb);
105
106 return dist.trancheExpectedValue (a: xMin_, d: xMax_);
107
108 // The following causes two errors in test against literature values.
109 // FIXME: Investigate accuracy.
110 // return dist.cumulativeExcessProbability (xMin_, xMax_);
111
112 // TranchePayoff func (xMin_, xMax_);
113 // return (dist.expectedValue (func)
114 // + (xMax_ - xMin_) * (1.0 - dist.cumulatedProbability (xMax_)));
115 }
116
117
118 void CDO::performCalculations() const {
119
120 QL_REQUIRE(!yieldTS_.empty(), "no yield term structure set");
121
122 errorEstimate_ = Null<Real>();
123
124 NPV_ = 0.0;
125 premiumValue_ = 0;
126 protectionValue_ = 0;
127 error_ = 0;
128
129 /* Expectations e1 and e2 are portfolio loss given default,
130 i.e. with recovery already "bult in". Multiplication by
131 (1-r) is therefore not necessary, neither in premium nor
132 protection value calculation.
133 */
134
135 Real e1 = 0;
136 Date today = yieldTS_->referenceDate();
137 if (premiumSchedule_[0] > today)
138 e1 = expectedTrancheLoss (d: premiumSchedule_[0]);
139
140 for (Size i = 1; i < premiumSchedule_.size(); i++) {
141 Date d2 = premiumSchedule_[i];
142 if (d2 < today)
143 continue;
144
145 Date d1 = premiumSchedule_[i-1];
146
147 Date d, d0 = d1;
148 do {
149 d = NullCalendar().advance (date: d0 > today ? d0 : today,
150 period: integrationStep_);
151 if (d > d2)
152 d = d2;
153
154 Real e2 = expectedTrancheLoss (d);
155
156 premiumValue_ += (xMax_ - xMin_ - e2)
157 * premiumRate_ * dayCounter_.yearFraction (d1: d0, d2: d)
158 * yieldTS_->discount (d);
159
160 if (e2 < e1) {
161 error_ ++;
162 }
163
164 protectionValue_ -= (e2 - e1) * yieldTS_->discount (d);
165
166 d0 = d;
167 e1 = e2;
168 }
169 while (d < d2);
170 }
171
172 if (premiumSchedule_[0] >= today)
173 upfrontPremiumValue_ = (xMax_ - xMin_) * upfrontPremiumRate_ *
174 yieldTS_->discount(d: premiumSchedule_[0]);
175 else
176 upfrontPremiumValue_ = 0.0;
177
178 if (!protectionSeller_) {
179 premiumValue_ *= -1;
180 upfrontPremiumValue_ *= -1;
181 protectionValue_ *= -1;
182 }
183
184 NPV_ = premiumValue_ + protectionValue_ + upfrontPremiumValue_;
185 }
186
187
188 Rate CDO::premiumValue () const {
189 calculate();
190 return premiumValue_;
191 }
192
193 Rate CDO::protectionValue () const {
194 calculate();
195 return protectionValue_;
196 }
197
198 Size CDO::error () const {
199 calculate();
200 return error_;
201 }
202
203 Rate CDO::fairPremium () const {
204 calculate();
205 return - premiumRate_ * protectionValue_ / premiumValue_;
206 }
207
208}
209

source code of quantlib/ql/experimental/credit/cdo.cpp