| 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 | |
| 10 | This file is part of QuantLib, a free-software/open-source library |
| 11 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 12 | |
| 13 | QuantLib is free software: you can redistribute it and/or modify it |
| 14 | under the terms of the QuantLib license. You should have received a |
| 15 | copy of the license along with this program; if not, please email |
| 16 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 17 | <http://quantlib.org/license.shtml>. |
| 18 | |
| 19 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 20 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 21 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 22 | */ |
| 23 | |
| 24 | #include <ql/time/calendars/unitedstates.hpp> |
| 25 | #include <ql/errors.hpp> |
| 26 | |
| 27 | namespace QuantLib { |
| 28 | |
| 29 | namespace { |
| 30 | |
| 31 | // a few rules used by multiple calendars |
| 32 | |
| 33 | bool isWashingtonBirthday(Day d, Month m, Year y, Weekday w) { |
| 34 | if (y >= 1971) { |
| 35 | // third Monday in February |
| 36 | return (d >= 15 && d <= 21) && w == Monday && m == February; |
| 37 | } else { |
| 38 | // February 22nd, possily adjusted |
| 39 | return (d == 22 || (d == 23 && w == Monday) |
| 40 | || (d == 21 && w == Friday)) && m == February; |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | bool isMemorialDay(Day d, Month m, Year y, Weekday w) { |
| 45 | if (y >= 1971) { |
| 46 | // last Monday in May |
| 47 | return d >= 25 && w == Monday && m == May; |
| 48 | } else { |
| 49 | // May 30th, possibly adjusted |
| 50 | return (d == 30 || (d == 31 && w == Monday) |
| 51 | || (d == 29 && w == Friday)) && m == May; |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | bool isLaborDay(Day d, Month m, Year y, Weekday w) { |
| 56 | // first Monday in September |
| 57 | return d <= 7 && w == Monday && m == September; |
| 58 | } |
| 59 | |
| 60 | bool isColumbusDay(Day d, Month m, Year y, Weekday w) { |
| 61 | // second Monday in October |
| 62 | return (d >= 8 && d <= 14) && w == Monday && m == October |
| 63 | && y >= 1971; |
| 64 | } |
| 65 | |
| 66 | bool isVeteransDay(Day d, Month m, Year y, Weekday w) { |
| 67 | if (y <= 1970 || y >= 1978) { |
| 68 | // November 11th, adjusted |
| 69 | return (d == 11 || (d == 12 && w == Monday) || |
| 70 | (d == 10 && w == Friday)) && m == November; |
| 71 | } else { |
| 72 | // fourth Monday in October |
| 73 | return (d >= 22 && d <= 28) && w == Monday && m == October; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | bool isVeteransDayNoSaturday(Day d, Month m, Year y, Weekday w) { |
| 78 | if (y <= 1970 || y >= 1978) { |
| 79 | // November 11th, adjusted, but no Saturday to Friday |
| 80 | return (d == 11 || (d == 12 && w == Monday)) && m == November; |
| 81 | } else { |
| 82 | // fourth Monday in October |
| 83 | return (d >= 22 && d <= 28) && w == Monday && m == October; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | bool isJuneteenth(Day d, Month m, Year y, Weekday w) { |
| 88 | // declared in 2021, but only observed by exchanges since 2022 |
| 89 | return (d == 19 || (d == 20 && w == Monday) || (d == 18 && w == Friday)) |
| 90 | && m == June && y >= 2022; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | UnitedStates::UnitedStates(UnitedStates::Market market) { |
| 95 | // all calendar instances on the same market share the same implementation instance |
| 96 | static auto settlementImpl = ext::make_shared<UnitedStates::SettlementImpl>(); |
| 97 | static auto liborImpactImpl = ext::make_shared<UnitedStates::LiborImpactImpl>(); |
| 98 | static auto nyseImpl = ext::make_shared<UnitedStates::NyseImpl>(); |
| 99 | static auto governmentImpl = ext::make_shared<UnitedStates::GovernmentBondImpl>(); |
| 100 | static auto nercImpl = ext::make_shared<UnitedStates::NercImpl>(); |
| 101 | static auto federalReserveImpl = ext::make_shared<UnitedStates::FederalReserveImpl>(); |
| 102 | static auto sofrImpl = ext::make_shared<UnitedStates::SofrImpl>(); |
| 103 | |
| 104 | switch (market) { |
| 105 | case Settlement: |
| 106 | impl_ = settlementImpl; |
| 107 | break; |
| 108 | case LiborImpact: |
| 109 | impl_ = liborImpactImpl; |
| 110 | break; |
| 111 | case NYSE: |
| 112 | impl_ = nyseImpl; |
| 113 | break; |
| 114 | case GovernmentBond: |
| 115 | impl_ = governmentImpl; |
| 116 | break; |
| 117 | case SOFR: |
| 118 | impl_ = sofrImpl; |
| 119 | break; |
| 120 | case NERC: |
| 121 | impl_ = nercImpl; |
| 122 | break; |
| 123 | case FederalReserve: |
| 124 | impl_ = federalReserveImpl; |
| 125 | break; |
| 126 | default: |
| 127 | QL_FAIL("unknown market" ); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | |
| 132 | bool UnitedStates::SettlementImpl::isBusinessDay(const Date& date) const { |
| 133 | Weekday w = date.weekday(); |
| 134 | Day d = date.dayOfMonth(); |
| 135 | Month m = date.month(); |
| 136 | Year y = date.year(); |
| 137 | if (isWeekend(w) |
| 138 | // New Year's Day (possibly moved to Monday if on Sunday) |
| 139 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
| 140 | // (or to Friday if on Saturday) |
| 141 | || (d == 31 && w == Friday && m == December) |
| 142 | // Martin Luther King's birthday (third Monday in January) |
| 143 | || ((d >= 15 && d <= 21) && w == Monday && m == January |
| 144 | && y >= 1983) |
| 145 | // Washington's birthday (third Monday in February) |
| 146 | || isWashingtonBirthday(d, m, y, w) |
| 147 | // Memorial Day (last Monday in May) |
| 148 | || isMemorialDay(d, m, y, w) |
| 149 | // Juneteenth (Monday if Sunday or Friday if Saturday) |
| 150 | || isJuneteenth(d, m, y, w) |
| 151 | // Independence Day (Monday if Sunday or Friday if Saturday) |
| 152 | || ((d == 4 || (d == 5 && w == Monday) || |
| 153 | (d == 3 && w == Friday)) && m == July) |
| 154 | // Labor Day (first Monday in September) |
| 155 | || isLaborDay(d, m, y, w) |
| 156 | // Columbus Day (second Monday in October) |
| 157 | || isColumbusDay(d, m, y, w) |
| 158 | // Veteran's Day (Monday if Sunday or Friday if Saturday) |
| 159 | || isVeteransDay(d, m, y, w) |
| 160 | // Thanksgiving Day (fourth Thursday in November) |
| 161 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
| 162 | // Christmas (Monday if Sunday or Friday if Saturday) |
| 163 | || ((d == 25 || (d == 26 && w == Monday) || |
| 164 | (d == 24 && w == Friday)) && m == December)) |
| 165 | return false; // NOLINT(readability-simplify-boolean-expr) |
| 166 | return true; |
| 167 | } |
| 168 | |
| 169 | bool UnitedStates::LiborImpactImpl::isBusinessDay(const Date& date) const { |
| 170 | // Since 2015 Independence Day only impacts Libor if it falls |
| 171 | // on a weekday |
| 172 | Weekday w = date.weekday(); |
| 173 | Day d = date.dayOfMonth(); |
| 174 | Month m = date.month(); |
| 175 | Year y = date.year(); |
| 176 | if (((d == 5 && w == Monday) || |
| 177 | (d == 3 && w == Friday)) && m == July && y >= 2015) |
| 178 | return true; |
| 179 | return SettlementImpl::isBusinessDay(date); |
| 180 | } |
| 181 | |
| 182 | bool UnitedStates::NyseImpl::isBusinessDay(const Date& date) const { |
| 183 | Weekday w = date.weekday(); |
| 184 | Day d = date.dayOfMonth(), dd = date.dayOfYear(); |
| 185 | Month m = date.month(); |
| 186 | Year y = date.year(); |
| 187 | Day em = easterMonday(y); |
| 188 | if (isWeekend(w) |
| 189 | // New Year's Day (possibly moved to Monday if on Sunday) |
| 190 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
| 191 | // Washington's birthday (third Monday in February) |
| 192 | || isWashingtonBirthday(d, m, y, w) |
| 193 | // Good Friday |
| 194 | || (dd == em-3) |
| 195 | // Memorial Day (last Monday in May) |
| 196 | || isMemorialDay(d, m, y, w) |
| 197 | // Juneteenth (Monday if Sunday or Friday if Saturday) |
| 198 | || isJuneteenth(d, m, y, w) |
| 199 | // Independence Day (Monday if Sunday or Friday if Saturday) |
| 200 | || ((d == 4 || (d == 5 && w == Monday) || |
| 201 | (d == 3 && w == Friday)) && m == July) |
| 202 | // Labor Day (first Monday in September) |
| 203 | || isLaborDay(d, m, y, w) |
| 204 | // Thanksgiving Day (fourth Thursday in November) |
| 205 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
| 206 | // Christmas (Monday if Sunday or Friday if Saturday) |
| 207 | || ((d == 25 || (d == 26 && w == Monday) || |
| 208 | (d == 24 && w == Friday)) && m == December) |
| 209 | ) return false; |
| 210 | |
| 211 | if (y >= 1998 && (d >= 15 && d <= 21) && w == Monday && m == January) |
| 212 | // Martin Luther King's birthday (third Monday in January) |
| 213 | return false; |
| 214 | |
| 215 | if ((y <= 1968 || (y <= 1980 && y % 4 == 0)) && m == November |
| 216 | && d <= 7 && w == Tuesday) |
| 217 | // Presidential election days |
| 218 | return false; |
| 219 | |
| 220 | // Special closings |
| 221 | if (// President Bush's Funeral |
| 222 | (y == 2018 && m == December && d == 5) |
| 223 | // Hurricane Sandy |
| 224 | || (y == 2012 && m == October && (d == 29 || d == 30)) |
| 225 | // President Ford's funeral |
| 226 | || (y == 2007 && m == January && d == 2) |
| 227 | // President Reagan's funeral |
| 228 | || (y == 2004 && m == June && d == 11) |
| 229 | // September 11-14, 2001 |
| 230 | || (y == 2001 && m == September && (11 <= d && d <= 14)) |
| 231 | // President Nixon's funeral |
| 232 | || (y == 1994 && m == April && d == 27) |
| 233 | // Hurricane Gloria |
| 234 | || (y == 1985 && m == September && d == 27) |
| 235 | // 1977 Blackout |
| 236 | || (y == 1977 && m == July && d == 14) |
| 237 | // Funeral of former President Lyndon B. Johnson. |
| 238 | || (y == 1973 && m == January && d == 25) |
| 239 | // Funeral of former President Harry S. Truman |
| 240 | || (y == 1972 && m == December && d == 28) |
| 241 | // National Day of Participation for the lunar exploration. |
| 242 | || (y == 1969 && m == July && d == 21) |
| 243 | // Funeral of former President Eisenhower. |
| 244 | || (y == 1969 && m == March && d == 31) |
| 245 | // Closed all day - heavy snow. |
| 246 | || (y == 1969 && m == February && d == 10) |
| 247 | // Day after Independence Day. |
| 248 | || (y == 1968 && m == July && d == 5) |
| 249 | // June 12-Dec. 31, 1968 |
| 250 | // Four day week (closed on Wednesdays) - Paperwork Crisis |
| 251 | || (y == 1968 && dd >= 163 && w == Wednesday) |
| 252 | // Day of mourning for Martin Luther King Jr. |
| 253 | || (y == 1968 && m == April && d == 9) |
| 254 | // Funeral of President Kennedy |
| 255 | || (y == 1963 && m == November && d == 25) |
| 256 | // Day before Decoration Day |
| 257 | || (y == 1961 && m == May && d == 29) |
| 258 | // Day after Christmas |
| 259 | || (y == 1958 && m == December && d == 26) |
| 260 | // Christmas Eve |
| 261 | || ((y == 1954 || y == 1956 || y == 1965) |
| 262 | && m == December && d == 24) |
| 263 | ) return false; |
| 264 | |
| 265 | return true; |
| 266 | } |
| 267 | |
| 268 | |
| 269 | bool UnitedStates::GovernmentBondImpl::isBusinessDay(const Date& date) const { |
| 270 | Weekday w = date.weekday(); |
| 271 | Day d = date.dayOfMonth(), dd = date.dayOfYear(); |
| 272 | Month m = date.month(); |
| 273 | Year y = date.year(); |
| 274 | Day em = easterMonday(y); |
| 275 | if (isWeekend(w) |
| 276 | // New Year's Day (possibly moved to Monday if on Sunday) |
| 277 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
| 278 | // Martin Luther King's birthday (third Monday in January) |
| 279 | || ((d >= 15 && d <= 21) && w == Monday && m == January |
| 280 | && y >= 1983) |
| 281 | // Washington's birthday (third Monday in February) |
| 282 | || isWashingtonBirthday(d, m, y, w) |
| 283 | // Good Friday (2015, 2021, 2023 are half day due to NFP/SIFMA; |
| 284 | // see <https://www.sifma.org/resources/general/holiday-schedule/>) |
| 285 | || (dd == em-3 && y != 2015 && y != 2021 && y != 2023) |
| 286 | // Memorial Day (last Monday in May) |
| 287 | || isMemorialDay(d, m, y, w) |
| 288 | // Juneteenth (Monday if Sunday or Friday if Saturday) |
| 289 | || isJuneteenth(d, m, y, w) |
| 290 | // Independence Day (Monday if Sunday or Friday if Saturday) |
| 291 | || ((d == 4 || (d == 5 && w == Monday) || |
| 292 | (d == 3 && w == Friday)) && m == July) |
| 293 | // Labor Day (first Monday in September) |
| 294 | || isLaborDay(d, m, y, w) |
| 295 | // Columbus Day (second Monday in October) |
| 296 | || isColumbusDay(d, m, y, w) |
| 297 | // Veteran's Day (Monday if Sunday) |
| 298 | || isVeteransDayNoSaturday(d, m, y, w) |
| 299 | // Thanksgiving Day (fourth Thursday in November) |
| 300 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
| 301 | // Christmas (Monday if Sunday or Friday if Saturday) |
| 302 | || ((d == 25 || (d == 26 && w == Monday) || |
| 303 | (d == 24 && w == Friday)) && m == December)) |
| 304 | return false; |
| 305 | |
| 306 | // Special closings |
| 307 | if (// President Bush's Funeral |
| 308 | (y == 2018 && m == December && d == 5) |
| 309 | // Hurricane Sandy |
| 310 | || (y == 2012 && m == October && (d == 30)) |
| 311 | // President Reagan's funeral |
| 312 | || (y == 2004 && m == June && d == 11) |
| 313 | ) return false; |
| 314 | |
| 315 | return true; |
| 316 | } |
| 317 | |
| 318 | |
| 319 | bool UnitedStates::SofrImpl::isBusinessDay(const Date& date) const { |
| 320 | // Good Friday 2023 was only a half close for SIFMA but SOFR didn't fix |
| 321 | if (date == Date(7, April, 2023)) |
| 322 | return false; |
| 323 | return GovernmentBondImpl::isBusinessDay(date); |
| 324 | } |
| 325 | |
| 326 | |
| 327 | bool UnitedStates::NercImpl::isBusinessDay(const Date& date) const { |
| 328 | Weekday w = date.weekday(); |
| 329 | Day d = date.dayOfMonth(); |
| 330 | Month m = date.month(); |
| 331 | Year y = date.year(); |
| 332 | if (isWeekend(w) |
| 333 | // New Year's Day (possibly moved to Monday if on Sunday) |
| 334 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
| 335 | // Memorial Day (last Monday in May) |
| 336 | || isMemorialDay(d, m, y, w) |
| 337 | // Independence Day (Monday if Sunday) |
| 338 | || ((d == 4 || (d == 5 && w == Monday)) && m == July) |
| 339 | // Labor Day (first Monday in September) |
| 340 | || isLaborDay(d, m, y, w) |
| 341 | // Thanksgiving Day (fourth Thursday in November) |
| 342 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
| 343 | // Christmas (Monday if Sunday) |
| 344 | || ((d == 25 || (d == 26 && w == Monday)) && m == December)) |
| 345 | return false; // NOLINT(readability-simplify-boolean-expr) |
| 346 | return true; |
| 347 | } |
| 348 | |
| 349 | |
| 350 | bool UnitedStates::FederalReserveImpl::isBusinessDay(const Date& date) const { |
| 351 | // see https://www.frbservices.org/about/holiday-schedules for details |
| 352 | Weekday w = date.weekday(); |
| 353 | Day d = date.dayOfMonth(); |
| 354 | Month m = date.month(); |
| 355 | Year y = date.year(); |
| 356 | if (isWeekend(w) |
| 357 | // New Year's Day (possibly moved to Monday if on Sunday) |
| 358 | || ((d == 1 || (d == 2 && w == Monday)) && m == January) |
| 359 | // Martin Luther King's birthday (third Monday in January) |
| 360 | || ((d >= 15 && d <= 21) && w == Monday && m == January |
| 361 | && y >= 1983) |
| 362 | // Washington's birthday (third Monday in February) |
| 363 | || isWashingtonBirthday(d, m, y, w) |
| 364 | // Memorial Day (last Monday in May) |
| 365 | || isMemorialDay(d, m, y, w) |
| 366 | // Juneteenth (Monday if Sunday or Friday if Saturday) |
| 367 | || isJuneteenth(d, m, y, w) |
| 368 | // Independence Day (Monday if Sunday) |
| 369 | || ((d == 4 || (d == 5 && w == Monday)) && m == July) |
| 370 | // Labor Day (first Monday in September) |
| 371 | || isLaborDay(d, m, y, w) |
| 372 | // Columbus Day (second Monday in October) |
| 373 | || isColumbusDay(d, m, y, w) |
| 374 | // Veteran's Day (Monday if Sunday) |
| 375 | || isVeteransDayNoSaturday(d, m, y, w) |
| 376 | // Thanksgiving Day (fourth Thursday in November) |
| 377 | || ((d >= 22 && d <= 28) && w == Thursday && m == November) |
| 378 | // Christmas (Monday if Sunday) |
| 379 | || ((d == 25 || (d == 26 && w == Monday)) && m == December)) |
| 380 | return false; // NOLINT(readability-simplify-boolean-expr) |
| 381 | return true; |
| 382 | } |
| 383 | |
| 384 | } |
| 385 | |