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