Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
makenonstandardlegs.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2023 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
20
21#include <ql/cashflows/fixedratecoupon.hpp>
22#include <ql/cashflows/iborcoupon.hpp>
23
24using namespace QuantLib;
25
26namespace ore::data {
27
28Leg makeNonStandardIborLeg(const QuantLib::ext::shared_ptr<IborIndex>& index, const std::vector<Date>& calcDates,
29 const std::vector<Date>& payDatesInput, const std::vector<Date>& fixingDatesInput,
30 const std::vector<Date>& resetDatesInput, const Size fixingDays,
31 const std::vector<Real>& notionals, const std::vector<Date>& notionalDatesInput,
32 const std::vector<Real>& spreadsInput, const std::vector<Date>& spreadDatesInput,
33 const std::vector<Real>& gearingsInput, const std::vector<Date>& gearingDatesInput,
34 const bool strictNotionalDates, const DayCounter& dayCounter, const Calendar& payCalendar,
35 const BusinessDayConvention payConv, const Period& payLag, const bool isInArrears) {
36
37 // add spread and gearing if none is given
38
39 std::vector<Real> spreads = spreadsInput, gearings = gearingsInput;
40
41 if (spreads.empty()) {
42 spreads.push_back(0.0);
43 }
44
45 if (gearings.empty()) {
46 gearings.push_back(1.0);
47 }
48
49 // checks
50
51 QL_REQUIRE(calcDates.size() >= 2,
52 "makeNonStandardIborLeg(): calc dates size (" << calcDates.size() << ") >= 2 required");
53 QL_REQUIRE(!notionals.empty(), "makeNonStandardIborLeg(): empty notinoals");
54 QL_REQUIRE(notionalDatesInput.empty() || notionalDatesInput.size() == notionals.size() - 1,
55 "makeNonStandardIborLeg(): notional dates (" << notionalDatesInput.size() << ") must match notional ("
56 << notionals.size() << ") minus 1");
57 QL_REQUIRE(spreadDatesInput.empty() || spreadDatesInput.size() == spreads.size() - 1,
58 "makeNonStandardIborLeg(): spread dates (" << spreadDatesInput.size() << ") must match spread ("
59 << spreads.size() << ") minus 1");
60 QL_REQUIRE(gearingDatesInput.empty() || gearingDatesInput.size() == gearings.size() - 1,
61 "makeNonStandardIborLeg(): gearing dates (" << gearingDatesInput.size() << ") must match gearing ("
62 << gearings.size() << ") minus 1");
63
64 // populate pay dates, reset dates, fixing dates, notional dates if not explicitly given
65
66 std::vector<Date> payDates(payDatesInput);
67 std::vector<Date> resetDates(resetDatesInput);
68 std::vector<Date> fixingDates(fixingDatesInput);
69 std::vector<Date> notionalDates(notionalDatesInput);
70 std::vector<Date> spreadDates(spreadDatesInput);
71 std::vector<Date> gearingDates(gearingDatesInput);
72
73 if (payDates.empty()) {
74 for (Size i = 1; i < calcDates.size(); ++i) {
75 payDates.push_back(payCalendar.advance(calcDates[i], payLag, payConv));
76 }
77 }
78
79 if (resetDates.empty() && fixingDates.empty()) {
80 for (Size i = 0; i < calcDates.size() - 1; ++i) {
81 resetDates.push_back(calcDates[i]);
82 fixingDates.push_back(index->fixingCalendar().advance(isInArrears ? calcDates[i + 1] : calcDates[i],
83 -static_cast<int>(fixingDays) * Days, Preceding));
84 }
85 } else if (resetDates.empty()) {
86 for (Size i = 0; i < fixingDates.size(); ++i) {
87 resetDates.push_back(index->fixingCalendar().advance(fixingDates[i], fixingDays * Days, Following));
88 }
89 } else if (fixingDates.empty()) {
90 for (Size i = 0; i < resetDates.size(); ++i) {
91 fixingDates.push_back(
92 index->fixingCalendar().advance(resetDates[i], -static_cast<int>(fixingDays) * Days, Preceding));
93 }
94 }
95
96 if (notionalDates.empty()) {
97 for (Size i = 1; i < notionals.size(); ++i)
98 notionalDates.push_back(calcDates[i]);
99 }
100
101 if (spreadDates.empty()) {
102 for (Size i = 1; i < spreads.size(); ++i)
103 spreadDates.push_back(calcDates[i]);
104 }
105
106 if (gearingDates.empty()) {
107 for (Size i = 1; i < gearings.size(); ++i)
108 gearingDates.push_back(calcDates[i]);
109 }
110
111 // more checks
112
113 QL_REQUIRE(payDates.size() == calcDates.size() - 1, "makeNonStandardIborLeg(): pay dates size ("
114 << payDates.size() << ") = calc dates size ("
115 << calcDates.size() << ") minus 1 required");
116 QL_REQUIRE(fixingDates.size() == resetDates.size(), "makeNonStandardIborLeg(): fixing dates ("
117 << fixingDates.size() << ") must match reset dates ("
118 << resetDates.size() << ")");
119
120 for (Size i = 0; i < fixingDates.size(); ++i) {
121 QL_REQUIRE(resetDates[i] <= calcDates.back(), "makeNonStandardIborLeg(): reset date at "
122 << i << " (" << resetDates[i]
123 << ") must be less or equal last calculation date ("
124 << calcDates.back());
125 }
126
127 for (Size i = 0; i < calcDates.size() - 1; ++i) {
128 QL_REQUIRE(calcDates[i] <= calcDates[i + 1], "makeNonStandardIborLeg(): calc date at "
129 << i << " (" << calcDates[i]
130 << ") must be less or equal calc date at " << (i + 1) << " ("
131 << calcDates[i + 1]);
132 }
133
134 for (Size i = 0; i < fixingDates.size() - 1; ++i) {
135 QL_REQUIRE(fixingDates[i] <= fixingDates[i + 1], "makeNonStandardIborLeg(): fixing date at "
136 << i << " (" << fixingDates[i]
137 << ") must be less or equal fixing date at " << (i + 1)
138 << " (" << fixingDates[i + 1] << ")");
139 }
140
141 for (Size i = 0; i < resetDates.size() - 1; ++i) {
142 QL_REQUIRE(resetDates[i] <= resetDates[i + 1], "makeNonStandardIborLeg(): reset date at "
143 << i << " (" << resetDates[i]
144 << ") must be less or equal reset date at " << (i + 1)
145 << " (" << resetDates[i + 1] << ")");
146 }
147
148 // build calculation periods including broken periods due to notional resets or fixing resets
149
150 std::set<Date> effCalcDates;
151
152 for (auto const& d : calcDates)
153 effCalcDates.insert(d);
154
155 for (auto const& d : resetDates) {
156 if (d >= calcDates.front() && d < calcDates.back())
157 effCalcDates.insert(d);
158 }
159
160 if (strictNotionalDates) {
161 for (auto const& d : notionalDates) {
162 if (d >= calcDates.front() && d < calcDates.back())
163 effCalcDates.insert(d);
164 }
165 }
166
167 // build coupons
168
169 Leg leg;
170
171 for (auto startDate = effCalcDates.begin(); startDate != std::next(effCalcDates.end(), -1); ++startDate) {
172 // determine calc end date from start date
173
174 Date endDate = *std::next(startDate, 1);
175
176 if (endDate > calcDates.back())
177 continue;
178
179 // determine pay date
180
181 auto nextCalcDate = std::lower_bound(calcDates.begin(), calcDates.end(), endDate);
182 Date payDate = payDates[std::max<Size>(1, std::distance(calcDates.begin(), nextCalcDate)) - 1];
183
184 // determine reset and thereby fixing date
185
186 auto nextReset = std::upper_bound(resetDates.begin(), resetDates.end(), *startDate);
187 QL_REQUIRE(nextReset != resetDates.begin(),
188 "makeNonStandardIborLeg(): calc start date "
189 << *startDate << " is before first reset date " << *resetDates.begin()
190 << ". Ensure that there is a reset date on or before the calc start date.");
191 Date fixingDate = fixingDates[std::distance(resetDates.begin(), nextReset) - 1];
192
193 // determine notional
194
195 auto notionalDate = std::upper_bound(notionalDates.begin(), notionalDates.end(), *startDate);
196 Real notional =
197 notionals[std::min<Size>(notionals.size() - 1, std::distance(notionalDates.begin(), notionalDate))];
198
199 // determine spread
200
201 auto spreadDate = std::upper_bound(spreadDates.begin(), spreadDates.end(), *startDate);
202 Real spread = spreads[std::min<Size>(spreads.size() - 1, std::distance(spreadDates.begin(), spreadDate))];
203
204 // determine gearing
205
206 auto gearingDate = std::upper_bound(gearingDates.begin(), gearingDates.end(), *startDate);
207 Real gearing = gearings[std::min<Size>(gearings.size() - 1, std::distance(gearingDates.begin(), gearingDate))];
208
209 // build coupon
210
211 leg.push_back(QuantLib::ext::make_shared<IborCoupon>(payDate, notional, *startDate, endDate, fixingDate, index, gearing,
212 spread, Date(), Date(), dayCounter));
213 }
214
215 return leg;
216}
217
218Leg makeNonStandardFixedLeg(const std::vector<Date>& calcDates, const std::vector<Date>& payDatesInput,
219 const std::vector<Real>& notionals, const std::vector<Date>& notionalDatesInput,
220 const std::vector<Real>& rates, const std::vector<Date>& rateDatesInput,
221 const bool strictNotionalDates, const DayCounter& dayCounter, const Calendar& payCalendar,
222 const BusinessDayConvention payConv, const Period& payLag) {
223
224 // checks
225
226 QL_REQUIRE(calcDates.size() >= 2,
227 "makeNonStandardFixedLeg(): calc dates size (" << calcDates.size() << ") >= 2 required");
228 QL_REQUIRE(!notionals.empty(), "makeNonStandardFixedLeg(): empty notinoals");
229 QL_REQUIRE(!rates.empty(), "makeNonStandardFixedLeg(): empty rates");
230 QL_REQUIRE(notionalDatesInput.empty() || notionalDatesInput.size() == notionals.size() - 1,
231 "makeNonStandardFixedLeg(): notional dates (" << notionalDatesInput.size() << ") must match notional ("
232 << notionals.size() << ") minus 1");
233 QL_REQUIRE(rateDatesInput.empty() || rateDatesInput.size() == rates.size() - 1,
234 "makeNonStandardIborLeg(): rate dates (" << rateDatesInput.size() << ") must match rate ("
235 << rates.size() << ") minus 1");
236
237 for (Size i = 0; i < calcDates.size() - 1; ++i) {
238 QL_REQUIRE(calcDates[i] <= calcDates[i + 1], "makeNonStandardFixedLeg(): calc date at "
239 << i << " (" << calcDates[i]
240 << ") must be less or equal calc date at " << (i + 1) << " ("
241 << calcDates[i + 1] << ")");
242 }
243
244 // populate pay dates, notional dates if not given
245
246 std::vector<Date> payDates(payDatesInput);
247 std::vector<Date> notionalDates(notionalDatesInput);
248 std::vector<Date> rateDates(rateDatesInput);
249
250 if (payDates.empty()) {
251 for (Size i = 1; i < calcDates.size(); ++i) {
252 payDates.push_back(payCalendar.advance(calcDates[i], payLag, payConv));
253 }
254 }
255
256 if (notionalDates.empty()) {
257 for (Size i = 1; i < notionals.size(); ++i)
258 notionalDates.push_back(calcDates[i]);
259 }
260
261 if (rateDates.empty()) {
262 for (Size i = 1; i < rates.size(); ++i)
263 rateDates.push_back(calcDates[i]);
264 }
265
266 // more checks
267
268 QL_REQUIRE(payDates.size() == calcDates.size() - 1, "makeNonStandardFixedLeg(): pay dates size ("
269 << payDates.size() << ") = calc dates size ("
270 << calcDates.size() << ") minus 1 required");
271
272 // build calculation periods including broken periods due to notional resets or fixing resets
273
274 std::set<Date> effCalcDates;
275
276 for (auto const& d : calcDates)
277 effCalcDates.insert(d);
278
279 if (strictNotionalDates)
280 for (auto const& d : notionalDates) {
281 if (d >= calcDates.front() && d < calcDates.back())
282 effCalcDates.insert(d);
283 }
284
285 // build coupons
286
287 Leg leg;
288
289 for (auto startDate = effCalcDates.begin(); startDate != std::next(effCalcDates.end(), -1); ++startDate) {
290
291 // determine calc end date from start date
292
293 Date endDate = *std::next(startDate, 1);
294
295 if (endDate >= calcDates.back())
296 continue;
297
298 // determine pay date
299
300 auto nextCalcDate = std::lower_bound(calcDates.begin(), calcDates.end(), endDate);
301 Date payDate = payDates[std::max<Size>(1, std::distance(calcDates.begin(), nextCalcDate)) - 1];
302
303 // determine notional
304
305 auto notionalDate = std::upper_bound(notionalDates.begin(), notionalDates.end(), *startDate);
306 Real notional =
307 notionals[std::min<Size>(notionals.size() - 1, std::distance(notionalDates.begin(), notionalDate))];
308
309 // determine rate
310
311 auto rateDate = std::upper_bound(rateDates.begin(), rateDates.end(), *startDate);
312 Real rate = rates[std::min<Size>(rates.size() - 1, std::distance(rateDates.begin(), rateDate))];
313
314 // build coupon
315
316 leg.push_back(QuantLib::ext::make_shared<FixedRateCoupon>(payDate, notional, rate, dayCounter, *startDate, endDate,
317 Date(), Date()));
318 }
319
320 return leg;
321}
322
323} // namespace ore::data
make functions for non-standard ibor and fixed legs
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Leg makeNonStandardIborLeg(const QuantLib::ext::shared_ptr< IborIndex > &index, const std::vector< Date > &calcDates, const std::vector< Date > &payDatesInput, const std::vector< Date > &fixingDatesInput, const std::vector< Date > &resetDatesInput, const Size fixingDays, const std::vector< Real > &notionals, const std::vector< Date > &notionalDatesInput, const std::vector< Real > &spreadsInput, const std::vector< Date > &spreadDatesInput, const std::vector< Real > &gearingsInput, const std::vector< Date > &gearingDatesInput, const bool strictNotionalDates, const DayCounter &dayCounter, const Calendar &payCalendar, const BusinessDayConvention payConv, const Period &payLag, const bool isInArrears)
Leg makeNonStandardFixedLeg(const std::vector< Date > &calcDates, const std::vector< Date > &payDatesInput, const std::vector< Real > &notionals, const std::vector< Date > &notionalDatesInput, const std::vector< Real > &rates, const std::vector< Date > &rateDatesInput, const bool strictNotionalDates, const DayCounter &dayCounter, const Calendar &payCalendar, const BusinessDayConvention payConv, const Period &payLag)