/src/quantlib/ql/cashflows/cpicoupon.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2009 Chris Kenyon |
5 | | Copyright (C) 2022 Quaternion Risk Management Ltd |
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 | | <http://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 | | |
22 | | #include <ql/cashflows/cashflowvectors.hpp> |
23 | | #include <ql/cashflows/cpicoupon.hpp> |
24 | | #include <ql/cashflows/cpicouponpricer.hpp> |
25 | | #include <ql/cashflows/inflationcoupon.hpp> |
26 | | #include <ql/time/daycounters/thirty360.hpp> |
27 | | #include <utility> |
28 | | |
29 | | |
30 | | namespace QuantLib { |
31 | | |
32 | | CPICoupon::CPICoupon(Real baseCPI, |
33 | | const Date& paymentDate, |
34 | | Real nominal, |
35 | | const Date& startDate, |
36 | | const Date& endDate, |
37 | | const ext::shared_ptr<ZeroInflationIndex>& index, |
38 | | const Period& observationLag, |
39 | | CPI::InterpolationType observationInterpolation, |
40 | | const DayCounter& dayCounter, |
41 | | Real fixedRate, |
42 | | const Date& refPeriodStart, |
43 | | const Date& refPeriodEnd, |
44 | | const Date& exCouponDate) |
45 | 0 | : CPICoupon(baseCPI, Date(), paymentDate, nominal, startDate, endDate, |
46 | 0 | index, observationLag, observationInterpolation, dayCounter, |
47 | 0 | fixedRate, refPeriodStart, refPeriodEnd, exCouponDate) {} Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) |
48 | | |
49 | | CPICoupon::CPICoupon(const Date& baseDate, |
50 | | const Date& paymentDate, |
51 | | Real nominal, |
52 | | const Date& startDate, |
53 | | const Date& endDate, |
54 | | const ext::shared_ptr<ZeroInflationIndex>& index, |
55 | | const Period& observationLag, |
56 | | CPI::InterpolationType observationInterpolation, |
57 | | const DayCounter& dayCounter, |
58 | | Real fixedRate, |
59 | | const Date& refPeriodStart, |
60 | | const Date& refPeriodEnd, |
61 | | const Date& exCouponDate) |
62 | 0 | : CPICoupon(Null<Real>(), baseDate, paymentDate, nominal, startDate, endDate, |
63 | 0 | index, observationLag, observationInterpolation, dayCounter, |
64 | 0 | fixedRate, refPeriodStart, refPeriodEnd, exCouponDate) {} Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) |
65 | | |
66 | | CPICoupon::CPICoupon(Real baseCPI, |
67 | | const Date& baseDate, |
68 | | const Date& paymentDate, |
69 | | Real nominal, |
70 | | const Date& startDate, |
71 | | const Date& endDate, |
72 | | const ext::shared_ptr<ZeroInflationIndex>& index, |
73 | | const Period& observationLag, |
74 | | CPI::InterpolationType observationInterpolation, |
75 | | const DayCounter& dayCounter, |
76 | | Real fixedRate, |
77 | | const Date& refPeriodStart, |
78 | | const Date& refPeriodEnd, |
79 | | const Date& exCouponDate) |
80 | 0 | : InflationCoupon(paymentDate, nominal, startDate, endDate, 0, |
81 | 0 | index, observationLag, dayCounter, |
82 | 0 | refPeriodStart, refPeriodEnd, exCouponDate), |
83 | 0 | baseCPI_(baseCPI), fixedRate_(fixedRate), |
84 | 0 | observationInterpolation_(observationInterpolation), baseDate_(baseDate) { |
85 | |
|
86 | 0 | QL_REQUIRE(index_, "no index provided"); |
87 | 0 | QL_REQUIRE(baseCPI_ != Null<Rate>() || baseDate != Date(), |
88 | 0 | "baseCPI and baseDate can not be both null, provide a valid baseCPI or baseDate"); |
89 | 0 | QL_REQUIRE(baseCPI_ == Null<Rate>() || std::fabs(baseCPI_) > 1e-16, |
90 | 0 | "|baseCPI_| < 1e-16, future divide-by-zero problem"); |
91 | 0 | } Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&) |
92 | | |
93 | 0 | void CPICoupon::accept(AcyclicVisitor& v) { |
94 | 0 | auto* v1 = dynamic_cast<Visitor<CPICoupon>*>(&v); |
95 | 0 | if (v1 != nullptr) |
96 | 0 | v1->visit(*this); |
97 | 0 | else |
98 | 0 | InflationCoupon::accept(v); |
99 | 0 | } |
100 | | |
101 | 0 | Real CPICoupon::accruedAmount(const Date& d) const { |
102 | 0 | if (d <= accrualStartDate_ || d > paymentDate_) { |
103 | 0 | return 0.0; |
104 | 0 | } else { |
105 | 0 | auto pricer = ext::dynamic_pointer_cast<CPICouponPricer>(pricer_); |
106 | 0 | QL_REQUIRE(pricer, "pricer not set or of wrong type"); |
107 | 0 | pricer->initialize(*this); |
108 | 0 | return nominal() * pricer->accruedRate(d) * accruedPeriod(d); |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | 0 | Rate CPICoupon::indexRatio(Date d) const { |
113 | |
|
114 | 0 | Rate I0 = baseCPI(); |
115 | |
|
116 | 0 | if (I0 == Null<Rate>()) { |
117 | 0 | I0 = CPI::laggedFixing(cpiIndex(), |
118 | 0 | baseDate() + observationLag(), |
119 | 0 | observationLag(), |
120 | 0 | observationInterpolation()); |
121 | 0 | } |
122 | |
|
123 | 0 | Rate I1 = CPI::laggedFixing(cpiIndex(), |
124 | 0 | d, |
125 | 0 | observationLag(), |
126 | 0 | observationInterpolation()); |
127 | |
|
128 | 0 | return I1 / I0; |
129 | 0 | } |
130 | | |
131 | | bool CPICoupon::checkPricerImpl( |
132 | 0 | const ext::shared_ptr<InflationCouponPricer>&pricer) const { |
133 | 0 | return static_cast<bool>( |
134 | 0 | ext::dynamic_pointer_cast<CPICouponPricer>(pricer)); |
135 | 0 | } |
136 | | |
137 | | |
138 | | |
139 | | CPICashFlow::CPICashFlow(Real notional, |
140 | | const ext::shared_ptr<ZeroInflationIndex>& index, |
141 | | const Date& baseDate, |
142 | | Real baseFixing, |
143 | | const Date& observationDate, |
144 | | const Period& observationLag, |
145 | | CPI::InterpolationType interpolation, |
146 | | const Date& paymentDate, |
147 | | bool growthOnly) |
148 | 0 | : IndexedCashFlow(notional, index, baseDate, observationDate - observationLag, paymentDate, growthOnly), |
149 | 0 | baseFixing_(baseFixing), observationDate_(observationDate), observationLag_(observationLag), |
150 | 0 | interpolation_(interpolation), frequency_(index ? index->frequency() : NoFrequency) { |
151 | 0 | QL_REQUIRE(index, "no index provided"); |
152 | 0 | QL_REQUIRE( |
153 | 0 | baseFixing_ != Null<Rate>() || baseDate != Date(), |
154 | 0 | "baseCPI and baseDate can not be both null, provide a valid baseCPI or baseDate"); |
155 | 0 | QL_REQUIRE(baseFixing_ == Null<Rate>() || std::fabs(baseFixing_) > 1e-16, |
156 | 0 | "|baseCPI_| < 1e-16, future divide-by-zero problem"); |
157 | 0 | } Unexecuted instantiation: QuantLib::CPICashFlow::CPICashFlow(double, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::Date const&, bool) Unexecuted instantiation: QuantLib::CPICashFlow::CPICashFlow(double, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::Date const&, bool) |
158 | | |
159 | 0 | Date CPICashFlow::baseDate() const { |
160 | 0 | Date base = IndexedCashFlow::baseDate(); |
161 | 0 | if (base != Date()) { |
162 | 0 | return base; |
163 | 0 | } else { |
164 | 0 | QL_FAIL("no base date specified"); |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | 0 | Real CPICashFlow::baseFixing() const { |
169 | 0 | if (baseFixing_ != Null<Rate>()) |
170 | 0 | return baseFixing_; |
171 | 0 | else |
172 | 0 | return CPI::laggedFixing(cpiIndex(), baseDate(), 0 * Months, interpolation_); |
173 | 0 | } |
174 | | |
175 | 0 | Real CPICashFlow::indexFixing() const { |
176 | 0 | return CPI::laggedFixing(cpiIndex(), observationDate_, observationLag_, interpolation_); |
177 | 0 | } |
178 | | |
179 | | CPILeg::CPILeg(Schedule schedule, |
180 | | ext::shared_ptr<ZeroInflationIndex> index, |
181 | | const Real baseCPI, |
182 | | const Period& observationLag) |
183 | 0 | : schedule_(std::move(schedule)), index_(std::move(index)), baseCPI_(baseCPI), |
184 | 0 | observationLag_(observationLag), paymentDayCounter_(Thirty360(Thirty360::BondBasis)), |
185 | 0 | paymentCalendar_(schedule_.calendar()) {} |
186 | | |
187 | | |
188 | 0 | CPILeg& CPILeg::withObservationInterpolation(CPI::InterpolationType interp) { |
189 | 0 | observationInterpolation_ = interp; |
190 | 0 | return *this; |
191 | 0 | } |
192 | | |
193 | | |
194 | 0 | CPILeg& CPILeg::withFixedRates(Real fixedRate) { |
195 | 0 | fixedRates_ = std::vector<Real>(1,fixedRate); |
196 | 0 | return *this; |
197 | 0 | } |
198 | | |
199 | 0 | CPILeg& CPILeg::withFixedRates(const std::vector<Real>& fixedRates) { |
200 | 0 | fixedRates_ = fixedRates; |
201 | 0 | return *this; |
202 | 0 | } |
203 | | |
204 | 0 | CPILeg& CPILeg::withNotionals(Real notional) { |
205 | 0 | notionals_ = std::vector<Real>(1,notional); |
206 | 0 | return *this; |
207 | 0 | } |
208 | | |
209 | 0 | CPILeg& CPILeg::withNotionals(const std::vector<Real>& notionals) { |
210 | 0 | notionals_ = notionals; |
211 | 0 | return *this; |
212 | 0 | } |
213 | | |
214 | 0 | CPILeg& CPILeg::withSubtractInflationNominal(bool growthOnly) { |
215 | 0 | subtractInflationNominal_ = growthOnly; |
216 | 0 | return *this; |
217 | 0 | } |
218 | | |
219 | 0 | CPILeg& CPILeg::withPaymentDayCounter(const DayCounter& dayCounter) { |
220 | 0 | paymentDayCounter_ = dayCounter; |
221 | 0 | return *this; |
222 | 0 | } |
223 | | |
224 | 0 | CPILeg& CPILeg::withPaymentAdjustment(BusinessDayConvention convention) { |
225 | 0 | paymentAdjustment_ = convention; |
226 | 0 | return *this; |
227 | 0 | } |
228 | | |
229 | 0 | CPILeg& CPILeg::withPaymentCalendar(const Calendar& cal) { |
230 | 0 | paymentCalendar_ = cal; |
231 | 0 | return *this; |
232 | 0 | } |
233 | | |
234 | 0 | CPILeg& CPILeg::withCaps(Rate cap) { |
235 | 0 | caps_ = std::vector<Rate>(1,cap); |
236 | 0 | return *this; |
237 | 0 | } |
238 | | |
239 | 0 | CPILeg& CPILeg::withCaps(const std::vector<Rate>& caps) { |
240 | 0 | caps_ = caps; |
241 | 0 | return *this; |
242 | 0 | } |
243 | | |
244 | 0 | CPILeg& CPILeg::withFloors(Rate floor) { |
245 | 0 | floors_ = std::vector<Rate>(1,floor); |
246 | 0 | return *this; |
247 | 0 | } |
248 | | |
249 | 0 | CPILeg& CPILeg::withFloors(const std::vector<Rate>& floors) { |
250 | 0 | floors_ = floors; |
251 | 0 | return *this; |
252 | 0 | } |
253 | | |
254 | | CPILeg& CPILeg::withExCouponPeriod( |
255 | | const Period& period, |
256 | | const Calendar& cal, |
257 | | BusinessDayConvention convention, |
258 | 0 | bool endOfMonth) { |
259 | 0 | exCouponPeriod_ = period; |
260 | 0 | exCouponCalendar_ = cal; |
261 | 0 | exCouponAdjustment_ = convention; |
262 | 0 | exCouponEndOfMonth_ = endOfMonth; |
263 | 0 | return *this; |
264 | 0 | } |
265 | | |
266 | 0 | CPILeg& CPILeg::withBaseDate(const Date& baseDate) { |
267 | 0 | baseDate_ = baseDate; |
268 | 0 | return *this; |
269 | 0 | } |
270 | | |
271 | | |
272 | 0 | CPILeg::operator Leg() const { |
273 | |
|
274 | 0 | QL_REQUIRE(!notionals_.empty(), "no notional given"); |
275 | 0 | Size n = schedule_.size()-1; |
276 | 0 | Leg leg; |
277 | 0 | leg.reserve(n+1); // +1 for notional, we always have some sort ... |
278 | |
|
279 | 0 | Date baseDate = baseDate_; |
280 | | // BaseDate and baseCPI are not given, use the first date as startDate and the baseFixingg |
281 | | // should be at startDate - observationLag |
282 | |
|
283 | 0 | if (n>0) { |
284 | 0 | QL_REQUIRE(!fixedRates_.empty(), "no fixedRates given"); |
285 | | |
286 | 0 | if (baseDate_ == Date() && baseCPI_ == Null<Real>()) { |
287 | 0 | baseDate = schedule_.date(0) - observationLag_; |
288 | 0 | } |
289 | |
|
290 | 0 | Date refStart, start, refEnd, end; |
291 | |
|
292 | 0 | for (Size i=0; i<n; ++i) { |
293 | 0 | refStart = start = schedule_.date(i); |
294 | 0 | refEnd = end = schedule_.date(i+1); |
295 | 0 | Date paymentDate = paymentCalendar_.adjust(end, paymentAdjustment_); |
296 | |
|
297 | 0 | Date exCouponDate; |
298 | 0 | if (exCouponPeriod_ != Period()) |
299 | 0 | { |
300 | 0 | exCouponDate = exCouponCalendar_.advance(paymentDate, |
301 | 0 | -exCouponPeriod_, |
302 | 0 | exCouponAdjustment_, |
303 | 0 | exCouponEndOfMonth_); |
304 | 0 | } |
305 | |
|
306 | 0 | if (i==0 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) { |
307 | 0 | BusinessDayConvention bdc = schedule_.businessDayConvention(); |
308 | 0 | refStart = schedule_.calendar().adjust(end - schedule_.tenor(), bdc); |
309 | 0 | } |
310 | 0 | if (i==n-1 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) { |
311 | 0 | BusinessDayConvention bdc = schedule_.businessDayConvention(); |
312 | 0 | refEnd = schedule_.calendar().adjust(start + schedule_.tenor(), bdc); |
313 | 0 | } |
314 | 0 | if (detail::get(fixedRates_, i, 1.0) == 0.0) { // fixed coupon |
315 | | // this looks like an optimization but I'm not sure it's worth it? |
316 | 0 | leg.push_back(ext::make_shared<FixedRateCoupon> |
317 | 0 | (paymentDate, detail::get(notionals_, i, 0.0), |
318 | 0 | detail::effectiveFixedRate({},caps_,floors_,i), |
319 | 0 | paymentDayCounter_, start, end, refStart, refEnd, exCouponDate)); |
320 | 0 | } else { // zero inflation coupon |
321 | 0 | if (detail::noOption(caps_, floors_, i)) { // just swaplet |
322 | 0 | leg.push_back(ext::make_shared<CPICoupon> |
323 | 0 | (baseCPI_, // all have same base for ratio |
324 | 0 | baseDate, |
325 | 0 | paymentDate, |
326 | 0 | detail::get(notionals_, i, 0.0), |
327 | 0 | start, end, |
328 | 0 | index_, observationLag_, |
329 | 0 | observationInterpolation_, |
330 | 0 | paymentDayCounter_, |
331 | 0 | detail::get(fixedRates_, i, 0.0), |
332 | 0 | refStart, refEnd, exCouponDate)); |
333 | 0 | } else { // cap/floorlet |
334 | 0 | QL_FAIL("caps/floors on CPI coupons not implemented."); |
335 | 0 | } |
336 | 0 | } |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | // in CPI legs you always have a notional flow of some sort |
341 | 0 | Date paymentDate = paymentCalendar_.adjust(schedule_.date(n), paymentAdjustment_); |
342 | 0 | leg.push_back(ext::make_shared<CPICashFlow> |
343 | 0 | (detail::get(notionals_, n, 0.0), index_, |
344 | 0 | baseDate, baseCPI_, |
345 | 0 | schedule_.date(n), observationLag_, observationInterpolation_, |
346 | 0 | paymentDate, subtractInflationNominal_)); |
347 | | |
348 | | // no caps and floors here, so this is enough |
349 | 0 | setCouponPricer(leg, ext::make_shared<CPICouponPricer>()); |
350 | |
|
351 | 0 | return leg; |
352 | 0 | } |
353 | | |
354 | | } |