Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discretizedconvertible.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005, 2006 Theo Boafo
3 Copyright (C) 2006, 2007 StatPro Italia srl
4 Copyright (C) 2020 Quaternion Risk Managment Ltd
5
6 This file is part of ORE, a free-software/open-source library
7 for transparent pricing and risk analysis - http://opensourcerisk.org
8
9 ORE is free software: you can redistribute it and/or modify it
10 under the terms of the Modified BSD License. You should have received a
11 copy of the license along with this program.
12 The license is also available online at <http://opensourcerisk.org>
13
14 This program is distributed on the basis that it will form a useful
15 contribution to risk analytics and model standardisation, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
21
22#include <ql/math/comparison.hpp>
23#include <ql/processes/blackscholesprocess.hpp>
24
25namespace QuantExt {
26
28 const ext::shared_ptr<GeneralizedBlackScholesProcess>& process,
29 const Handle<Quote>& creditSpread, const TimeGrid& grid)
30 : arguments_(args), process_(process), creditSpread_(creditSpread) {
31
32 dividendValues_ = Array(arguments_.dividends.size(), 0.0);
33
34 Date settlementDate = process_->riskFreeRate()->referenceDate();
35 for (Size i = 0; i < arguments_.dividends.size(); i++) {
36 if (arguments_.dividends[i]->date() >= settlementDate) {
38 arguments_.dividends[i]->amount() * process_->riskFreeRate()->discount(arguments_.dividends[i]->date());
39 }
40 }
41
42 DayCounter dayCounter = process_->riskFreeRate()->dayCounter();
43 Date bondSettlement = arguments_.settlementDate;
44
45 stoppingTimes_.resize(arguments_.exercise->dates().size());
46 for (Size i = 0; i < stoppingTimes_.size(); ++i)
47 stoppingTimes_[i] = dayCounter.yearFraction(bondSettlement, arguments_.exercise->date(i));
48
50 for (Size i = 0; i < callabilityTimes_.size(); ++i)
51 callabilityTimes_[i] = dayCounter.yearFraction(bondSettlement, arguments_.callabilityDates[i]);
52
54 for (Size i = 0; i < cashflowTimes_.size(); ++i) {
55 cashflowTimes_[i] = dayCounter.yearFraction(bondSettlement, arguments_.cashflowDates[i]);
56 }
57
59 for (Size i = 0; i < dividendTimes_.size(); ++i)
60 dividendTimes_[i] = dayCounter.yearFraction(bondSettlement, arguments_.dividendDates[i]);
61
62 if (!grid.empty()) {
63 // adjust times to grid
64 for (Size i = 0; i < stoppingTimes_.size(); i++)
65 stoppingTimes_[i] = grid.closestTime(stoppingTimes_[i]);
66 for (Size i = 0; i < cashflowTimes_.size(); i++)
67 cashflowTimes_[i] = grid.closestTime(cashflowTimes_[i]);
68 for (Size i = 0; i < callabilityTimes_.size(); i++)
69 callabilityTimes_[i] = grid.closestTime(callabilityTimes_[i]);
70 for (Size i = 0; i < dividendTimes_.size(); i++)
71 dividendTimes_[i] = grid.closestTime(dividendTimes_[i]);
72 }
73
74 notionalTimes_.clear();
75 for (auto const& d : arguments_.notionalDates) {
76 notionalTimes_.push_back(grid.closestTime(dayCounter.yearFraction(bondSettlement, d)));
77 }
78}
79
80std::vector<Time> DiscretizedConvertible::mandatoryTimes() const {
81 std::vector<Time> result;
82 // the stopping times might be negative, if an exercise date lies in the past
83 for (auto const& t : stoppingTimes_) {
84 if (t > 0.0)
85 result.push_back(t);
86 }
87 std::copy(callabilityTimes_.begin(), callabilityTimes_.end(), std::back_inserter(result));
88 std::copy(cashflowTimes_.begin(), cashflowTimes_.end(), std::back_inserter(result));
89 return result;
90}
91
93
94 // Set cashflow on last date
95 values_ = Array(size, 0.0);
96 for (Size i = 0; i < cashflowTimes_.size(); ++i) {
98 values_ += arguments_.cashflowAmounts[i];
99 }
100 }
101
102 conversionProbability_ = Array(size, 0.0);
103 spreadAdjustedRate_ = Array(size, 0.0);
104
105 DayCounter rfdc = process_->riskFreeRate()->dayCounter();
106
107 // this takes care of convertibility and conversion probabilities
108 adjustValues();
109
110 Real creditSpread = creditSpread_->value();
111 Rate riskFreeRate = process_->riskFreeRate()->zeroRate(arguments_.maturityDate, rfdc, Continuous, NoFrequency);
112
113 // Calculate blended discount rate to be used on roll back.
114 for (Size j = 0; j < values_.size(); j++) {
116 conversionProbability_[j] * riskFreeRate + (1 - conversionProbability_[j]) * (riskFreeRate + creditSpread);
117 }
118}
119
121
122 bool convertible = false;
123 switch (arguments_.exercise->type()) {
124 case Exercise::American:
125 if (time() <= stoppingTimes_[1] && time() >= stoppingTimes_[0])
126 convertible = true;
127 break;
128 case Exercise::European:
129 if (isOnTime(stoppingTimes_[0]))
130 convertible = true;
131 break;
132 case Exercise::Bermudan:
133 for (Size i = 0; i < stoppingTimes_.size(); ++i) {
134 if (isOnTime(stoppingTimes_[i]))
135 convertible = true;
136 }
137 break;
138 default:
139 QL_FAIL("invalid option type");
140 }
141
142 for (Size i = 0; i < callabilityTimes_.size(); i++) {
143 if (isOnTime(callabilityTimes_[i]))
144 applyCallability(i, convertible);
145 }
146
147 for (Size i = 0; i < cashflowTimes_.size(); i++) {
148 if (isOnTime(cashflowTimes_[i]) && !close_enough(cashflowTimes_[i], cashflowTimes_.back()))
149 addCashflow(i);
150 }
151
152 if (convertible)
154}
155
157 Size idx = std::distance(notionalTimes_.begin(), std::upper_bound(notionalTimes_.begin(), notionalTimes_.end(), t));
158 if (idx > 0)
159 --idx;
160 return arguments_.notionals[std::min(idx, arguments_.notionals.size() - 1)] / arguments_.notionals.front() *
162}
163
165 Array grid = adjustedGrid();
166 Real conversionRatio = getConversionRatio(time());
167 for (Size j = 0; j < values_.size(); j++) {
168 Real payoff = conversionRatio * grid[j];
169 if (values_[j] <= payoff) {
170 values_[j] = payoff;
171 conversionProbability_[j] = 1.0;
172 }
173 }
174}
175
176void DiscretizedConvertible::applyCallability(Size i, bool convertible) {
177 Size j;
178 Array grid = adjustedGrid();
179 Real conversionRatio = getConversionRatio(time());
180 switch (arguments_.callabilityTypes[i]) {
181 case Callability::Call:
182 if (arguments_.callabilityTriggers[i] != Null<Real>() && arguments_.conversionValue != Null<Real>()) {
184 for (j = 0; j < values_.size(); j++) {
185 // the callability is conditioned by the trigger...
186 if (grid[j] >= trigger) {
187 // ...and might trigger conversion
188 values_[j] =
189 std::min(std::max(arguments_.callabilityPrices[i], conversionRatio * grid[j]), values_[j]);
190 }
191 }
192 } else if (convertible) {
193 for (j = 0; j < values_.size(); j++) {
194 // exercising the callability might trigger conversion
195 values_[j] = std::min(std::max(arguments_.callabilityPrices[i], conversionRatio * grid[j]), values_[j]);
196 }
197 } else {
198 for (j = 0; j < values_.size(); j++) {
199 values_[j] = std::min(arguments_.callabilityPrices[i], values_[j]);
200 }
201 }
202 break;
203 case Callability::Put:
204 for (j = 0; j < values_.size(); j++) {
205 values_[j] = std::max(values_[j], arguments_.callabilityPrices[i]);
206 }
207 break;
208 default:
209 QL_FAIL("unknown callability type");
210 }
211}
212
214
216 Time t = time();
217 Array grid = method()->grid(t);
218 // add back all dividend amounts in the future
219 for (Size i = 0; i < arguments_.dividends.size(); i++) {
220 Time dividendTime = dividendTimes_[i];
221 if (dividendTime >= t || close(dividendTime, t)) {
222 const ext::shared_ptr<Dividend>& d = arguments_.dividends[i];
223 DiscountFactor dividendDiscount =
224 process_->riskFreeRate()->discount(dividendTime) / process_->riskFreeRate()->discount(t);
225 for (Size j = 0; j < grid.size(); j++)
226 grid[j] += d->amount(grid[j]) * dividendDiscount;
227 }
228 }
229 return grid;
230}
231
232} // namespace QuantExt
std::vector< Callability::Type > callabilityTypes
DiscretizedConvertible(const ConvertibleBond::option::arguments &args, const ext::shared_ptr< GeneralizedBlackScholesProcess > &process, const Handle< Quote > &creditSpread, const TimeGrid &grid)
ConvertibleBond::option::arguments arguments_
Real getConversionRatio(const Real t) const
std::vector< Time > mandatoryTimes() const override
QuantLib::ext::shared_ptr< GeneralizedBlackScholesProcess > process_
void applyCallability(Size, bool convertible)
discretized convertible
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Swap::arguments * arguments_