/src/quantlib/ql/experimental/callablebonds/discretizedcallablefixedratebond.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2008 Allen Kuo |
5 | | Copyright (C) 2021, 2022 Ralf Konrad Eckel |
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/experimental/callablebonds/discretizedcallablefixedratebond.hpp> |
22 | | |
23 | | namespace QuantLib { |
24 | | |
25 | | namespace { |
26 | | |
27 | 0 | bool withinNextWeek(Time t1, Time t2) { |
28 | 0 | static const Time dt = 1.0 / 52; |
29 | 0 | return t1 <= t2 && t2 <= t1 + dt; |
30 | 0 | } |
31 | | |
32 | | } |
33 | | |
34 | | |
35 | | DiscretizedCallableFixedRateBond::DiscretizedCallableFixedRateBond( |
36 | | const CallableBond::arguments& args, const Handle<YieldTermStructure>& termStructure) |
37 | 0 | : arguments_(args), adjustedCallabilityPrices_(args.callabilityPrices) { |
38 | |
|
39 | 0 | auto dayCounter = termStructure->dayCounter(); |
40 | 0 | auto referenceDate = termStructure->referenceDate(); |
41 | |
|
42 | 0 | redemptionTime_ = dayCounter.yearFraction(referenceDate, args.redemptionDate); |
43 | | |
44 | | /* By default the coupon adjustment should take place in |
45 | | * DiscretizedCallableFixedRateBond::postAdjustValuesImpl(). */ |
46 | 0 | couponAdjustments_ = |
47 | 0 | std::vector<CouponAdjustment>(args.couponDates.size(), CouponAdjustment::post); |
48 | |
|
49 | 0 | couponTimes_.resize(args.couponDates.size()); |
50 | 0 | for (Size i = 0; i < couponTimes_.size(); ++i) { |
51 | 0 | couponTimes_[i] = dayCounter.yearFraction(referenceDate, args.couponDates[i]); |
52 | 0 | } |
53 | |
|
54 | 0 | callabilityTimes_.resize(args.callabilityDates.size()); |
55 | 0 | for (Size i = 0; i < callabilityTimes_.size(); ++i) { |
56 | 0 | const Date callabilityDate = args.callabilityDates[i]; |
57 | 0 | Time callabilityTime = dayCounter.yearFraction(referenceDate, args.callabilityDates[i]); |
58 | | |
59 | | // To avoid mispricing, we snap exercise dates to the closest coupon date. |
60 | 0 | for (Size j = 0; j < couponTimes_.size(); j++) { |
61 | 0 | const Time couponTime = couponTimes_[j]; |
62 | 0 | const Date couponDate = args.couponDates[j]; |
63 | |
|
64 | 0 | if (withinNextWeek(callabilityTime, couponTime) && callabilityDate < couponDate) { |
65 | | // Snap the exercise date. |
66 | 0 | callabilityTime = couponTime; |
67 | | |
68 | | /* The order of events must be changed here. In |
69 | | * DiscretizedCallableFixedRateBond::postAdjustValuesImpl() the callability is |
70 | | * done before adding of the coupon. However from the |
71 | | * DiscretizedAsset::rollback(Time to) perspective the coupon must be added |
72 | | * before the callability as it is later in time. */ |
73 | 0 | couponAdjustments_[j] = CouponAdjustment::pre; |
74 | | |
75 | | /* We snapped the callabilityTime so we need to take into account the missing |
76 | | * discount factor including any possible spread e.g. set in the OAS |
77 | | * calculation. */ |
78 | 0 | auto spread = arguments_.spread; |
79 | 0 | auto calcDiscountFactorInclSpread = [&termStructure, spread](Date date) { |
80 | 0 | auto time = termStructure->timeFromReference(date); |
81 | 0 | auto zeroRateInclSpread = |
82 | 0 | termStructure->zeroRate(date, termStructure->dayCounter(), Continuous, |
83 | 0 | NoFrequency) + |
84 | 0 | spread; |
85 | 0 | auto df = std::exp(-zeroRateInclSpread * time); |
86 | 0 | return df; |
87 | 0 | }; |
88 | |
|
89 | 0 | auto dfTillCallDate = calcDiscountFactorInclSpread(callabilityDate); |
90 | 0 | auto dfTillCouponDate = calcDiscountFactorInclSpread(couponDate); |
91 | 0 | adjustedCallabilityPrices_[i] *= dfTillCallDate / dfTillCouponDate; |
92 | |
|
93 | 0 | break; |
94 | 0 | } |
95 | 0 | } |
96 | |
|
97 | 0 | adjustedCallabilityPrices_[i] *= arguments_.faceAmount / 100.0; |
98 | 0 | callabilityTimes_[i] = callabilityTime; |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | |
103 | 0 | void DiscretizedCallableFixedRateBond::reset(Size size) { |
104 | 0 | values_ = Array(size, arguments_.redemption); |
105 | 0 | adjustValues(); |
106 | 0 | } |
107 | | |
108 | | |
109 | 0 | std::vector<Time> DiscretizedCallableFixedRateBond::mandatoryTimes() const { |
110 | 0 | std::vector<Time> times; |
111 | 0 | Time t; |
112 | 0 | Size i; |
113 | |
|
114 | 0 | t = redemptionTime_; |
115 | 0 | if (t >= 0.0) { |
116 | 0 | times.push_back(t); |
117 | 0 | } |
118 | |
|
119 | 0 | for (i = 0; i < couponTimes_.size(); i++) { |
120 | 0 | t = couponTimes_[i]; |
121 | 0 | if (t >= 0.0) { |
122 | 0 | times.push_back(t); |
123 | 0 | } |
124 | 0 | } |
125 | |
|
126 | 0 | for (i = 0; i < callabilityTimes_.size(); i++) { |
127 | 0 | t = callabilityTimes_[i]; |
128 | 0 | if (t >= 0.0) { |
129 | 0 | times.push_back(t); |
130 | 0 | } |
131 | 0 | } |
132 | |
|
133 | 0 | return times; |
134 | 0 | } |
135 | | |
136 | | |
137 | 0 | void DiscretizedCallableFixedRateBond::preAdjustValuesImpl() { |
138 | 0 | for (Size i = 0; i < couponTimes_.size(); i++) { |
139 | 0 | if (couponAdjustments_[i] == CouponAdjustment::pre) { |
140 | 0 | Time t = couponTimes_[i]; |
141 | 0 | if (t >= 0.0 && isOnTime(t)) { |
142 | 0 | addCoupon(i); |
143 | 0 | } |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | |
149 | 0 | void DiscretizedCallableFixedRateBond::postAdjustValuesImpl() { |
150 | 0 | for (Size i = 0; i < callabilityTimes_.size(); i++) { |
151 | 0 | Time t = callabilityTimes_[i]; |
152 | 0 | if (t >= 0.0 && isOnTime(t)) { |
153 | 0 | applyCallability(i); |
154 | 0 | } |
155 | 0 | } |
156 | 0 | for (Size i = 0; i < couponTimes_.size(); i++) { |
157 | 0 | if (couponAdjustments_[i] == CouponAdjustment::post) { |
158 | 0 | Time t = couponTimes_[i]; |
159 | 0 | if (t >= 0.0 && isOnTime(t)) { |
160 | | /* Exercise and coupon date matches. */ |
161 | 0 | addCoupon(i); |
162 | 0 | } |
163 | 0 | } |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | | |
168 | 0 | void DiscretizedCallableFixedRateBond::applyCallability(Size i) { |
169 | 0 | Size j; |
170 | 0 | switch (arguments_.putCallSchedule[i]->type()) { |
171 | 0 | case Callability::Call: |
172 | 0 | for (j = 0; j < values_.size(); j++) { |
173 | 0 | values_[j] = std::min(adjustedCallabilityPrices_[i], values_[j]); |
174 | 0 | } |
175 | 0 | break; |
176 | 0 | case Callability::Put: |
177 | 0 | for (j = 0; j < values_.size(); j++) { |
178 | 0 | values_[j] = std::max(values_[j], adjustedCallabilityPrices_[i]); |
179 | 0 | } |
180 | 0 | break; |
181 | 0 | default: |
182 | 0 | QL_FAIL("unknown callability type"); |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | |
187 | 0 | void DiscretizedCallableFixedRateBond::addCoupon(Size i) { |
188 | 0 | values_ += arguments_.couponAmounts[i]; |
189 | 0 | } |
190 | | |
191 | | } |