/src/quantlib/ql/time/calendars/unitedstates.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2004, 2005 Ferdinando Ametrano |
5 | | Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl |
6 | | Copyright (C) 2003, 2004, 2005, 2006 StatPro Italia srl |
7 | | Copyright (C) 2017 Peter Caspers |
8 | | Copyright (C) 2017 Oleg Kulkov |
9 | | Copyright (C) 2023 Skandinaviska Enskilda Banken AB (publ) |
10 | | Copyright (C) 2024 Dirk Eddelbuettel |
11 | | |
12 | | This file is part of QuantLib, a free-software/open-source library |
13 | | for financial quantitative analysts and developers - http://quantlib.org/ |
14 | | |
15 | | QuantLib is free software: you can redistribute it and/or modify it |
16 | | under the terms of the QuantLib license. You should have received a |
17 | | copy of the license along with this program; if not, please email |
18 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
19 | | <https://www.quantlib.org/license.shtml>. |
20 | | |
21 | | This program is distributed in the hope that it will be useful, but WITHOUT |
22 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
23 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
24 | | */ |
25 | | |
26 | | #include <ql/time/calendars/unitedstates.hpp> |
27 | | #include <ql/errors.hpp> |
28 | | |
29 | | namespace QuantLib { |
30 | | |
31 | | namespace { |
32 | | |
33 | | // a few rules used by multiple calendars |
34 | | |
35 | 20.3M | bool isWashingtonBirthday(Day d, Month m, Year y, Weekday w) { |
36 | 20.3M | if (y >= 1971) { |
37 | | // third Monday in February |
38 | 17.6M | return (d >= 15 && d <= 21) && w == Monday && m == February; |
39 | 17.6M | } else { |
40 | | // February 22nd, possibly adjusted |
41 | 2.67M | return (d == 22 || (d == 23 && w == Monday) |
42 | 2.56M | || (d == 21 && w == Friday)) && m == February; |
43 | 2.67M | } |
44 | 20.3M | } |
45 | | |
46 | 20.2M | bool isMemorialDay(Day d, Month m, Year y, Weekday w) { |
47 | 20.2M | if (y >= 1971) { |
48 | | // last Monday in May |
49 | 17.5M | return d >= 25 && w == Monday && m == May; |
50 | 17.5M | } else { |
51 | | // May 30th, possibly adjusted |
52 | 2.65M | return (d == 30 || (d == 31 && w == Monday) |
53 | 2.56M | || (d == 29 && w == Friday)) && m == May; |
54 | 2.65M | } |
55 | 20.2M | } |
56 | | |
57 | 20.0M | bool isLaborDay(Day d, Month m, Year y, Weekday w) { |
58 | | // first Monday in September |
59 | 20.0M | return d <= 7 && w == Monday && m == September; |
60 | 20.0M | } |
61 | | |
62 | 10.1M | bool isColumbusDay(Day d, Month m, Year y, Weekday w) { |
63 | | // second Monday in October |
64 | 10.1M | return (d >= 8 && d <= 14) && w == Monday && m == October |
65 | 39.8k | && y >= 1971; |
66 | 10.1M | } |
67 | | |
68 | 0 | bool isVeteransDay(Day d, Month m, Year y, Weekday w) { |
69 | 0 | if (y <= 1970 || y >= 1978) { |
70 | | // November 11th, adjusted |
71 | 0 | return (d == 11 || (d == 12 && w == Monday) || |
72 | 0 | (d == 10 && w == Friday)) && m == November; |
73 | 0 | } else { |
74 | | // fourth Monday in October |
75 | 0 | return (d >= 22 && d <= 28) && w == Monday && m == October; |
76 | 0 | } |
77 | 0 | } |
78 | | |
79 | 10.1M | bool isVeteransDayNoSaturday(Day d, Month m, Year y, Weekday w) { |
80 | 10.1M | if (y <= 1970 || y >= 1978) { |
81 | | // November 11th, adjusted, but no Saturday to Friday |
82 | 9.85M | return (d == 11 || (d == 12 && w == Monday)) && m == November; |
83 | 9.85M | } else { |
84 | | // fourth Monday in October |
85 | 301k | return (d >= 22 && d <= 28) && w == Monday && m == October; |
86 | 301k | } |
87 | 10.1M | } |
88 | | |
89 | 20.1M | bool isJuneteenth(Day d, Month m, Year y, Weekday w, bool moveToFriday = true) { |
90 | | // declared in 2021, but only observed by exchanges since 2022 |
91 | 20.1M | return (d == 19 || (d == 20 && w == Monday) || ((d == 18 && w == Friday) && moveToFriday)) |
92 | 881k | && m == June && y >= 2022; |
93 | 20.1M | } |
94 | | } |
95 | | |
96 | 18.5k | UnitedStates::UnitedStates(UnitedStates::Market market) { |
97 | | // all calendar instances on the same market share the same implementation instance |
98 | 18.5k | static auto settlementImpl = ext::make_shared<UnitedStates::SettlementImpl>(); |
99 | 18.5k | static auto liborImpactImpl = ext::make_shared<UnitedStates::LiborImpactImpl>(); |
100 | 18.5k | static auto nyseImpl = ext::make_shared<UnitedStates::NyseImpl>(); |
101 | 18.5k | static auto governmentImpl = ext::make_shared<UnitedStates::GovernmentBondImpl>(); |
102 | 18.5k | static auto nercImpl = ext::make_shared<UnitedStates::NercImpl>(); |
103 | 18.5k | static auto federalReserveImpl = ext::make_shared<UnitedStates::FederalReserveImpl>(); |
104 | 18.5k | static auto sofrImpl = ext::make_shared<UnitedStates::SofrImpl>(); |
105 | | |
106 | 18.5k | switch (market) { |
107 | 0 | case Settlement: |
108 | 0 | impl_ = settlementImpl; |
109 | 0 | break; |
110 | 0 | case LiborImpact: |
111 | 0 | impl_ = liborImpactImpl; |
112 | 0 | break; |
113 | 6.18k | case NYSE: |
114 | 6.18k | impl_ = nyseImpl; |
115 | 6.18k | break; |
116 | 6.18k | case GovernmentBond: |
117 | 6.18k | impl_ = governmentImpl; |
118 | 6.18k | break; |
119 | 0 | case SOFR: |
120 | 0 | impl_ = sofrImpl; |
121 | 0 | break; |
122 | 0 | case NERC: |
123 | 0 | impl_ = nercImpl; |
124 | 0 | break; |
125 | 6.18k | case FederalReserve: |
126 | 6.18k | impl_ = federalReserveImpl; |
127 | 6.18k | break; |
128 | 0 | default: |
129 | 0 | QL_FAIL("unknown market"); |
130 | 18.5k | } |
131 | 18.5k | } |
132 | | |
133 | | |
134 | 0 | bool UnitedStates::SettlementImpl::isBusinessDay(const Date& date) const { |
135 | 0 | Weekday w = date.weekday(); |
136 | 0 | Day d = date.dayOfMonth(); |
137 | 0 | Month m = date.month(); |
138 | 0 | Year y = date.year(); |
139 | 0 | if (isWeekend(w) |
140 | | // New Year's Day (possibly moved to Monday if on Sunday) |
141 | 0 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
142 | | // (or to Friday if on Saturday) |
143 | 0 | || (d == 31 && w == Friday && m == December) |
144 | | // Martin Luther King's birthday (third Monday in January) |
145 | 0 | || ((d >= 15 && d <= 21) && w == Monday && m == January |
146 | 0 | && y >= 1983) |
147 | | // Washington's birthday (third Monday in February) |
148 | 0 | || isWashingtonBirthday(d, m, y, w) |
149 | | // Memorial Day (last Monday in May) |
150 | 0 | || isMemorialDay(d, m, y, w) |
151 | | // Juneteenth (Monday if Sunday or Friday if Saturday) |
152 | 0 | || isJuneteenth(d, m, y, w) |
153 | | // Independence Day (Monday if Sunday or Friday if Saturday) |
154 | 0 | || ((d == 4 || (d == 5 && w == Monday) || |
155 | 0 | (d == 3 && w == Friday)) && m == July) |
156 | | // Labor Day (first Monday in September) |
157 | 0 | || isLaborDay(d, m, y, w) |
158 | | // Columbus Day (second Monday in October) |
159 | 0 | || isColumbusDay(d, m, y, w) |
160 | | // Veteran's Day (Monday if Sunday or Friday if Saturday) |
161 | 0 | || isVeteransDay(d, m, y, w) |
162 | | // Thanksgiving Day (fourth Thursday in November) |
163 | 0 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
164 | | // Christmas (Monday if Sunday or Friday if Saturday) |
165 | 0 | || ((d == 25 || (d == 26 && w == Monday) || |
166 | 0 | (d == 24 && w == Friday)) && m == December)) |
167 | 0 | return false; // NOLINT(readability-simplify-boolean-expr) |
168 | 0 | return true; |
169 | 0 | } |
170 | | |
171 | 0 | bool UnitedStates::LiborImpactImpl::isBusinessDay(const Date& date) const { |
172 | | // Since 2015 Independence Day only impacts Libor if it falls |
173 | | // on a weekday |
174 | 0 | Weekday w = date.weekday(); |
175 | 0 | Day d = date.dayOfMonth(); |
176 | 0 | Month m = date.month(); |
177 | 0 | Year y = date.year(); |
178 | 0 | if (((d == 5 && w == Monday) || |
179 | 0 | (d == 3 && w == Friday)) && m == July && y >= 2015) |
180 | 0 | return true; |
181 | 0 | return SettlementImpl::isBusinessDay(date); |
182 | 0 | } |
183 | | |
184 | 13.9M | bool UnitedStates::NyseImpl::isBusinessDay(const Date& date) const { |
185 | 13.9M | Weekday w = date.weekday(); |
186 | 13.9M | Day d = date.dayOfMonth(), dd = date.dayOfYear(); |
187 | 13.9M | Month m = date.month(); |
188 | 13.9M | Year y = date.year(); |
189 | 13.9M | Day em = easterMonday(y); |
190 | 13.9M | if (isWeekend(w) |
191 | | // New Year's Day (possibly moved to Monday if on Sunday) |
192 | 10.0M | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
193 | | // Washington's birthday (third Monday in February) |
194 | 9.97M | || isWashingtonBirthday(d, m, y, w) |
195 | | // Good Friday |
196 | 9.94M | || (dd == em-3) |
197 | | // Memorial Day (last Monday in May) |
198 | 9.90M | || isMemorialDay(d, m, y, w) |
199 | | // Juneteenth (Monday if Sunday or Friday if Saturday) |
200 | 9.86M | || isJuneteenth(d, m, y, w) |
201 | | // Independence Day (Monday if Sunday or Friday if Saturday) |
202 | 9.84M | || ((d == 4 || (d == 5 && w == Monday) || |
203 | 9.44M | (d == 3 && w == Friday)) && m == July) |
204 | | // Labor Day (first Monday in September) |
205 | 9.80M | || isLaborDay(d, m, y, w) |
206 | | // Thanksgiving Day (fourth Thursday in November) |
207 | 9.76M | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
208 | | // Christmas (Monday if Sunday or Friday if Saturday) |
209 | 9.72M | || ((d == 25 || (d == 26 && w == Monday) || |
210 | 9.34M | (d == 24 && w == Friday)) && m == December) |
211 | 13.9M | ) return false; |
212 | | |
213 | 9.68M | if (y >= 1998 && (d >= 15 && d <= 21) && w == Monday && m == January) |
214 | | // Martin Luther King's birthday (third Monday in January) |
215 | 28.2k | return false; |
216 | | |
217 | 9.65M | if ((y <= 1968 || (y <= 1980 && y % 4 == 0)) && m == November |
218 | 96.9k | && d <= 7 && w == Tuesday) |
219 | | // Presidential election days |
220 | 4.62k | return false; |
221 | | |
222 | | // Special closings |
223 | 9.65M | if (// President Carter's Funeral |
224 | 9.65M | (y == 2025 && m == January && d == 9) |
225 | | // President Bush's Funeral |
226 | 9.65M | || (y == 2018 && m == December && d == 5) |
227 | | // Hurricane Sandy |
228 | 9.65M | || (y == 2012 && m == October && (d == 29 || d == 30)) |
229 | | // President Ford's funeral |
230 | 9.65M | || (y == 2007 && m == January && d == 2) |
231 | | // President Reagan's funeral |
232 | 9.65M | || (y == 2004 && m == June && d == 11) |
233 | | // September 11-14, 2001 |
234 | 9.65M | || (y == 2001 && m == September && (11 <= d && d <= 14)) |
235 | | // President Nixon's funeral |
236 | 9.65M | || (y == 1994 && m == April && d == 27) |
237 | | // Hurricane Gloria |
238 | 9.65M | || (y == 1985 && m == September && d == 27) |
239 | | // 1977 Blackout |
240 | 9.65M | || (y == 1977 && m == July && d == 14) |
241 | | // Funeral of former President Lyndon B. Johnson. |
242 | 9.65M | || (y == 1973 && m == January && d == 25) |
243 | | // Funeral of former President Harry S. Truman |
244 | 9.65M | || (y == 1972 && m == December && d == 28) |
245 | | // National Day of Participation for the lunar exploration. |
246 | 9.65M | || (y == 1969 && m == July && d == 21) |
247 | | // Funeral of former President Eisenhower. |
248 | 9.65M | || (y == 1969 && m == March && d == 31) |
249 | | // Closed all day - heavy snow. |
250 | 9.65M | || (y == 1969 && m == February && d == 10) |
251 | | // Day after Independence Day. |
252 | 9.65M | || (y == 1968 && m == July && d == 5) |
253 | | // June 12-Dec. 31, 1968 |
254 | | // Four day week (closed on Wednesdays) - Paperwork Crisis |
255 | 9.64M | || (y == 1968 && dd >= 163 && w == Wednesday) |
256 | | // Day of mourning for Martin Luther King Jr. |
257 | 9.64M | || (y == 1968 && m == April && d == 9) |
258 | | // Funeral of President Kennedy |
259 | 9.64M | || (y == 1963 && m == November && d == 25) |
260 | | // Day before Decoration Day |
261 | 9.64M | || (y == 1961 && m == May && d == 29) |
262 | | // Day after Christmas |
263 | 9.64M | || (y == 1958 && m == December && d == 26) |
264 | | // Christmas Eve |
265 | 9.64M | || ((y == 1954 || y == 1956 || y == 1965) |
266 | 82.1k | && m == December && d == 24) |
267 | 9.65M | ) return false; |
268 | | |
269 | 9.64M | return true; |
270 | 9.65M | } |
271 | | |
272 | | |
273 | 8.37M | bool UnitedStates::GovernmentBondImpl::isBusinessDay(const Date& date) const { |
274 | 8.37M | Weekday w = date.weekday(); |
275 | 8.37M | Day d = date.dayOfMonth(), dd = date.dayOfYear(); |
276 | 8.37M | Month m = date.month(); |
277 | 8.37M | Year y = date.year(); |
278 | 8.37M | Day em = easterMonday(y); |
279 | 8.37M | if (isWeekend(w) |
280 | | // New Year's Day (possibly moved to Monday if on Sunday) |
281 | 5.98M | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
282 | | // Martin Luther King's birthday (third Monday in January) |
283 | 5.95M | || ((d >= 15 && d <= 21) && w == Monday && m == January |
284 | 22.5k | && y >= 1983) |
285 | | // Washington's birthday (third Monday in February) |
286 | 5.94M | || isWashingtonBirthday(d, m, y, w) |
287 | | // Good Friday. Since 1996 it's an early close and not a full market |
288 | | // close when it coincides with the NFP release date, which is the |
289 | | // first Friday of the month(*). |
290 | | // See <https://www.sifma.org/resources/general/holiday-schedule/> |
291 | | // |
292 | | // (*) The full rule is "the third Friday after the conclusion of the |
293 | | // week which includes the 12th of the month". This is usually the |
294 | | // first Friday of the next month, but can be the second Friday if the |
295 | | // month has fewer than 31 days. Since Good Friday is always between |
296 | | // March 20th and April 23rd, it can only coincide with the April NFP, |
297 | | // which is always on the first Friday, because March has 31 days. |
298 | 5.91M | || (dd == em-3 && (y < 1996 || d > 7)) |
299 | | // Memorial Day (last Monday in May) |
300 | 5.89M | || isMemorialDay(d, m, y, w) |
301 | | // Juneteenth (Monday if Sunday or Friday if Saturday) |
302 | 5.87M | || isJuneteenth(d, m, y, w) |
303 | | // Independence Day (Monday if Sunday or Friday if Saturday) |
304 | 5.86M | || ((d == 4 || (d == 5 && w == Monday) || |
305 | 5.62M | (d == 3 && w == Friday)) && m == July) |
306 | | // Labor Day (first Monday in September) |
307 | 5.83M | || isLaborDay(d, m, y, w) |
308 | | // Columbus Day (second Monday in October) |
309 | 5.81M | || isColumbusDay(d, m, y, w) |
310 | | // Veteran's Day (Monday if Sunday) |
311 | 5.79M | || isVeteransDayNoSaturday(d, m, y, w) |
312 | | // Thanksgiving Day (fourth Thursday in November) |
313 | 5.77M | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
314 | | // Christmas (Monday if Sunday or Friday if Saturday) |
315 | 5.75M | || ((d == 25 || (d == 26 && w == Monday) || |
316 | 5.52M | (d == 24 && w == Friday)) && m == December)) |
317 | 2.64M | return false; |
318 | | |
319 | | // Special closings |
320 | 5.73M | if (// President Bush's Funeral |
321 | 5.73M | (y == 2018 && m == December && d == 5) |
322 | | // Hurricane Sandy |
323 | 5.73M | || (y == 2012 && m == October && d == 30) |
324 | | // President Reagan's funeral |
325 | 5.73M | || (y == 2004 && m == June && d == 11) |
326 | 5.73M | ) return false; |
327 | | |
328 | 5.73M | return true; |
329 | 5.73M | } |
330 | | |
331 | | |
332 | 0 | bool UnitedStates::SofrImpl::isBusinessDay(const Date& date) const { |
333 | | // so far (that is, up to 2023 at the time of this change) SOFR never fixed |
334 | | // on Good Friday. We're extrapolating that pattern. This might change if |
335 | | // a fixing on Good Friday occurs in future years. |
336 | 0 | const Day dY = date.dayOfYear(); |
337 | 0 | const Year y = date.year(); |
338 | | |
339 | | // Good Friday |
340 | 0 | if (dY == (easterMonday(y) - 3)) |
341 | 0 | return false; |
342 | | |
343 | 0 | return GovernmentBondImpl::isBusinessDay(date); |
344 | 0 | } |
345 | | |
346 | | |
347 | 0 | bool UnitedStates::NercImpl::isBusinessDay(const Date& date) const { |
348 | 0 | Weekday w = date.weekday(); |
349 | 0 | Day d = date.dayOfMonth(); |
350 | 0 | Month m = date.month(); |
351 | 0 | Year y = date.year(); |
352 | 0 | if (isWeekend(w) |
353 | | // New Year's Day (possibly moved to Monday if on Sunday) |
354 | 0 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
355 | | // Memorial Day (last Monday in May) |
356 | 0 | || isMemorialDay(d, m, y, w) |
357 | | // Independence Day (Monday if Sunday) |
358 | 0 | || ((d == 4 || (d == 5 && w == Monday)) && m == July) |
359 | | // Labor Day (first Monday in September) |
360 | 0 | || isLaborDay(d, m, y, w) |
361 | | // Thanksgiving Day (fourth Thursday in November) |
362 | 0 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
363 | | // Christmas (Monday if Sunday) |
364 | 0 | || ((d == 25 || (d == 26 && w == Monday)) && m == December)) |
365 | 0 | return false; // NOLINT(readability-simplify-boolean-expr) |
366 | 0 | return true; |
367 | 0 | } |
368 | | |
369 | | |
370 | 6.25M | bool UnitedStates::FederalReserveImpl::isBusinessDay(const Date& date) const { |
371 | | // see https://www.frbservices.org/about/holiday-schedules for details |
372 | 6.25M | Weekday w = date.weekday(); |
373 | 6.25M | Day d = date.dayOfMonth(); |
374 | 6.25M | Month m = date.month(); |
375 | 6.25M | Year y = date.year(); |
376 | 6.25M | if (isWeekend(w) |
377 | | // New Year's Day (possibly moved to Monday if on Sunday) |
378 | 4.47M | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
379 | | // Martin Luther King's birthday (third Monday in January) |
380 | 4.45M | || ((d >= 15 && d <= 21) && w == Monday && m == January |
381 | 17.2k | && y >= 1983) |
382 | | // Washington's birthday (third Monday in February) |
383 | 4.44M | || isWashingtonBirthday(d, m, y, w) |
384 | | // Memorial Day (last Monday in May) |
385 | 4.42M | || isMemorialDay(d, m, y, w) |
386 | | // Juneteenth (Monday if Sunday) |
387 | 4.41M | || isJuneteenth(d, m, y, w, false) |
388 | | // Independence Day (Monday if Sunday) |
389 | 4.40M | || ((d == 4 || (d == 5 && w == Monday)) && m == July) |
390 | | // Labor Day (first Monday in September) |
391 | 4.38M | || isLaborDay(d, m, y, w) |
392 | | // Columbus Day (second Monday in October) |
393 | 4.37M | || isColumbusDay(d, m, y, w) |
394 | | // Veteran's Day (Monday if Sunday) |
395 | 4.35M | || isVeteransDayNoSaturday(d, m, y, w) |
396 | | // Thanksgiving Day (fourth Thursday in November) |
397 | 4.34M | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
398 | | // Christmas (Monday if Sunday) |
399 | 4.32M | || ((d == 25 || (d == 26 && w == Monday)) && m == December)) |
400 | 1.94M | return false; // NOLINT(readability-simplify-boolean-expr) |
401 | 4.30M | return true; |
402 | 6.25M | } |
403 | | |
404 | | } |