/src/quantlib/ql/pricingengines/bond/discretizedconvertible.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2005, 2006 Theo Boafo |
5 | | Copyright (C) 2006, 2007 StatPro Italia srl |
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 | | <https://www.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/pricingengines/bond/discretizedconvertible.hpp> |
22 | | #include <ql/math/comparison.hpp> |
23 | | #include <ql/processes/blackscholesprocess.hpp> |
24 | | #include <utility> |
25 | | |
26 | | namespace QuantLib { |
27 | | |
28 | | DiscretizedConvertible::DiscretizedConvertible( |
29 | | ConvertibleBond::arguments args, |
30 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process, |
31 | | DividendSchedule dividends, |
32 | | Handle<Quote> creditSpread, |
33 | | const TimeGrid& grid) |
34 | 0 | : arguments_(std::move(args)), process_(std::move(process)), |
35 | 0 | creditSpread_(std::move(creditSpread)) { |
36 | |
|
37 | 0 | for (const auto& dividend : dividends) { |
38 | 0 | if (!dividend->hasOccurred(arguments_.settlementDate, false)) { |
39 | 0 | dividends_.push_back(dividend); |
40 | 0 | dividendDates_.push_back(dividend->date()); |
41 | 0 | } |
42 | 0 | } |
43 | |
|
44 | 0 | dividendValues_ = Array(dividends_.size(), 0.0); |
45 | |
|
46 | 0 | Date settlementDate = process_->riskFreeRate()->referenceDate(); |
47 | 0 | for (Size i=0; i<dividends.size(); i++) { |
48 | 0 | if (dividends[i]->date() >= settlementDate) { |
49 | 0 | dividendValues_[i] = |
50 | 0 | dividends[i]->amount() * |
51 | 0 | process_->riskFreeRate()->discount( |
52 | 0 | dividends[i]->date()); |
53 | 0 | } |
54 | 0 | } |
55 | |
|
56 | 0 | DayCounter dayCounter = process_->riskFreeRate()->dayCounter(); |
57 | 0 | Date bondSettlement = arguments_.settlementDate; |
58 | |
|
59 | 0 | stoppingTimes_.resize(arguments_.exercise->dates().size()); |
60 | 0 | for (Size i=0; i<stoppingTimes_.size(); ++i) |
61 | 0 | stoppingTimes_[i] = |
62 | 0 | dayCounter.yearFraction(bondSettlement, |
63 | 0 | arguments_.exercise->date(i)); |
64 | |
|
65 | 0 | callabilityTimes_.resize(arguments_.callabilityDates.size()); |
66 | 0 | for (Size i=0; i<callabilityTimes_.size(); ++i) |
67 | 0 | callabilityTimes_[i] = |
68 | 0 | dayCounter.yearFraction(bondSettlement, |
69 | 0 | arguments_.callabilityDates[i]); |
70 | |
|
71 | 0 | couponTimes_.clear(); |
72 | 0 | couponAmounts_.clear(); |
73 | 0 | for (Size i = 0; i < arguments_.cashflows.size() - 1; ++i) { |
74 | 0 | if (!arguments_.cashflows[i]->hasOccurred(bondSettlement, false)) { |
75 | 0 | couponTimes_.push_back(dayCounter.yearFraction(bondSettlement, |
76 | 0 | arguments_.cashflows[i]->date())); |
77 | 0 | couponAmounts_.push_back(arguments_.cashflows[i]->amount()); |
78 | 0 | } |
79 | 0 | } |
80 | |
|
81 | 0 | dividendTimes_.resize(dividendDates_.size()); |
82 | 0 | for (Size i=0; i<dividendTimes_.size(); ++i) |
83 | 0 | dividendTimes_[i] = |
84 | 0 | dayCounter.yearFraction(bondSettlement, |
85 | 0 | dividendDates_[i]); |
86 | |
|
87 | 0 | if (!grid.empty()) { |
88 | | // adjust times to grid |
89 | 0 | for (Real& stoppingTime : stoppingTimes_) |
90 | 0 | stoppingTime = grid.closestTime(stoppingTime); |
91 | 0 | for (Real& couponTime : couponTimes_) |
92 | 0 | couponTime = grid.closestTime(couponTime); |
93 | 0 | for (Real& callabilityTime : callabilityTimes_) |
94 | 0 | callabilityTime = grid.closestTime(callabilityTime); |
95 | 0 | for (Real& dividendTime : dividendTimes_) |
96 | 0 | dividendTime = grid.closestTime(dividendTime); |
97 | 0 | } |
98 | 0 | } |
99 | | |
100 | 0 | void DiscretizedConvertible::reset(Size size) { |
101 | | |
102 | | // Set to bond redemption values |
103 | 0 | values_ = Array(size, arguments_.redemption); |
104 | | |
105 | | // coupon amounts should be added when adjusting |
106 | | // values_ = Array(size, arguments_.cashFlows.back()->amount()); |
107 | |
|
108 | 0 | conversionProbability_ = Array(size, 0.0); |
109 | 0 | spreadAdjustedRate_ = Array(size, 0.0); |
110 | |
|
111 | 0 | DayCounter rfdc = process_->riskFreeRate()->dayCounter(); |
112 | | |
113 | | // this takes care of convertibility and conversion probabilities |
114 | 0 | adjustValues(); |
115 | |
|
116 | 0 | Real creditSpread = creditSpread_->value(); |
117 | |
|
118 | 0 | Date exercise = arguments_.exercise->lastDate(); |
119 | |
|
120 | 0 | Rate riskFreeRate = |
121 | 0 | process_->riskFreeRate()->zeroRate(exercise, rfdc, |
122 | 0 | Continuous, NoFrequency); |
123 | | |
124 | | // Calculate blended discount rate to be used on roll back. |
125 | 0 | for (Size j=0; j<values_.size(); j++) { |
126 | 0 | spreadAdjustedRate_[j] = |
127 | 0 | conversionProbability_[j] * riskFreeRate + |
128 | 0 | (1-conversionProbability_[j])*(riskFreeRate + creditSpread); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | 0 | void DiscretizedConvertible::postAdjustValuesImpl() { |
133 | |
|
134 | 0 | bool convertible = false; |
135 | 0 | switch (arguments_.exercise->type()) { |
136 | 0 | case Exercise::American: |
137 | 0 | if (time() <= stoppingTimes_[1] && time() >= stoppingTimes_[0]) |
138 | 0 | convertible = true; |
139 | 0 | break; |
140 | 0 | case Exercise::European: |
141 | 0 | if (isOnTime(stoppingTimes_[0])) |
142 | 0 | convertible = true; |
143 | 0 | break; |
144 | 0 | case Exercise::Bermudan: |
145 | 0 | for (Real stoppingTime : stoppingTimes_) { |
146 | 0 | if (isOnTime(stoppingTime)) |
147 | 0 | convertible = true; |
148 | 0 | } |
149 | 0 | break; |
150 | 0 | default: |
151 | 0 | QL_FAIL("invalid option type"); |
152 | 0 | } |
153 | | |
154 | 0 | for (Size i=0; i<callabilityTimes_.size(); i++) { |
155 | 0 | if (isOnTime(callabilityTimes_[i])) |
156 | 0 | applyCallability(i,convertible); |
157 | 0 | } |
158 | |
|
159 | 0 | for (Size i=0; i<couponTimes_.size(); i++) { |
160 | 0 | if (isOnTime(couponTimes_[i])) |
161 | 0 | addCoupon(i); |
162 | 0 | } |
163 | |
|
164 | 0 | if (convertible) |
165 | 0 | applyConvertibility(); |
166 | 0 | } |
167 | | |
168 | 0 | void DiscretizedConvertible::applyConvertibility() { |
169 | 0 | Array grid = adjustedGrid(); |
170 | 0 | for (Size j=0; j<values_.size(); j++) { |
171 | 0 | Real payoff = arguments_.conversionRatio*grid[j]; |
172 | 0 | if (values_[j] <= payoff) { |
173 | 0 | values_[j] = payoff; |
174 | 0 | conversionProbability_[j] = 1.0; |
175 | 0 | } |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | 0 | void DiscretizedConvertible::applyCallability(Size i, bool convertible) { |
180 | 0 | Size j; |
181 | 0 | Array grid = adjustedGrid(); |
182 | 0 | switch (arguments_.callabilityTypes[i]) { |
183 | 0 | case Callability::Call: |
184 | 0 | if (arguments_.callabilityTriggers[i] != Null<Real>()) { |
185 | 0 | Real conversionValue = |
186 | 0 | arguments_.redemption/arguments_.conversionRatio; |
187 | 0 | Real trigger = |
188 | 0 | conversionValue*arguments_.callabilityTriggers[i]; |
189 | 0 | for (j=0; j<values_.size(); j++) { |
190 | | // the callability is conditioned by the trigger... |
191 | 0 | if (grid[j] >= trigger) { |
192 | | // ...and might trigger conversion |
193 | 0 | values_[j] = |
194 | 0 | std::min(std::max( |
195 | 0 | arguments_.callabilityPrices[i], |
196 | 0 | arguments_.conversionRatio*grid[j]), |
197 | 0 | values_[j]); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | } else if (convertible) { |
201 | 0 | for (j=0; j<values_.size(); j++) { |
202 | | // exercising the callability might trigger conversion |
203 | 0 | values_[j] = |
204 | 0 | std::min(std::max(arguments_.callabilityPrices[i], |
205 | 0 | arguments_.conversionRatio*grid[j]), |
206 | 0 | values_[j]); |
207 | 0 | } |
208 | 0 | } else { |
209 | 0 | for (j=0; j<values_.size(); j++) { |
210 | 0 | values_[j] = std::min(arguments_.callabilityPrices[i], |
211 | 0 | values_[j]); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | break; |
215 | 0 | case Callability::Put: |
216 | 0 | for (j=0; j<values_.size(); j++) { |
217 | 0 | values_[j] = std::max(values_[j], |
218 | 0 | arguments_.callabilityPrices[i]); |
219 | 0 | } |
220 | 0 | break; |
221 | 0 | default: |
222 | 0 | QL_FAIL("unknown callability type"); |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | 0 | void DiscretizedConvertible::addCoupon(Size i) { |
227 | 0 | values_ += couponAmounts_[i]; |
228 | 0 | } |
229 | | |
230 | 0 | Array DiscretizedConvertible::adjustedGrid() const { |
231 | 0 | Time t = time(); |
232 | 0 | Array grid = method()->grid(t); |
233 | | // add back all dividend amounts in the future |
234 | 0 | for (Size i=0; i<dividends_.size(); i++) { |
235 | 0 | Time dividendTime = dividendTimes_[i]; |
236 | 0 | if (dividendTime >= t || close(dividendTime,t)) { |
237 | 0 | const ext::shared_ptr<Dividend>& d = dividends_[i]; |
238 | 0 | DiscountFactor dividendDiscount = |
239 | 0 | process_->riskFreeRate()->discount(dividendTime) / |
240 | 0 | process_->riskFreeRate()->discount(t); |
241 | 0 | for (Real& j : grid) |
242 | 0 | j += d->amount(j) * dividendDiscount; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | return grid; |
246 | 0 | } |
247 | | |
248 | | } |
249 | | |