36

I'm trying to create a Trading calendar using Pandas. I'm able to create a cal instance based on the USFederalHolidayCalendar. The USFederalHolidayCalendar is not consistent with the Trading calendar in that the Trading calendar doesn't include Columbus Day and Veteran's Day. However, the Trading calendar includes Good Friday (not included in the USFederalHolidayCalendar). Everything except for the last line in following code works:

from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, GoodFriday
from datetime import datetime

cal = get_calendar('USFederalHolidayCalendar')  # Create calendar instance
cal.rules.pop(7)                                # Remove Veteran's Day rule
cal.rules.pop(6)                                # Remove Columbus Day rule
tradingCal = HolidayCalendarFactory('TradingCalendar', cal, GoodFriday)

The tradingCal instance seems to work in that I'm able to view the Holiday rules.

In[10]: tradingCal.rules
Out[10]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: kwds={'weekday': MO(+1)}>),
 Holiday: Presidents Day (month=2, day=1, offset=<DateOffset: kwds={'weekday': MO(+3)}>),
 Holiday: Good Friday (month=1, day=1, offset=[<Easter>, <-2 * Days>]),
 Holiday: Dr. Martin Luther King Jr. (month=1, day=1, offset=<DateOffset: kwds={'weekday': MO(+3)}>),
 Holiday: New Years Day (month=1, day=1, observance=<function nearest_workday at 0x000000000A190BA8>),
 Holiday: Thanksgiving (month=11, day=1, offset=<DateOffset: kwds={'weekday': TH(+4)}>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x000000000A190BA8>),
 Holiday: Christmas (month=12, day=25, observance=<function nearest_workday at 0x000000000A190BA8>),
 Holiday: MemorialDay (month=5, day=31, offset=<DateOffset: kwds={'weekday': MO(-1)}>)]

When I try to list the holidays in a date range, I get the following error:

In[11]: tradingCal.holidays(datetime(2014, 12, 31), datetime(2016, 12, 31))
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\IPython\core\interactiveshell.py", line 3035, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-12-2708cd2db7a0>", line 1, in <module>
    tradingCal.holidays(datetime(2014, 12, 31), datetime(2016, 12, 31))
TypeError: unbound method holidays() must be called with TradingCalendar instance as first argument (got datetime instance instead)

Any ideas?

vlmercado
  • 1,848
  • 2
  • 17
  • 19

4 Answers4

46

Perhaps it is more straightforward to create the trade calendar from scratch, like so:

import datetime as dt

from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, nearest_workday, \
    USMartinLutherKingJr, USPresidentsDay, GoodFriday, USMemorialDay, \
    USLaborDay, USThanksgivingDay


class USTradingCalendar(AbstractHolidayCalendar):
    rules = [
        Holiday('NewYearsDay', month=1, day=1, observance=nearest_workday),
        USMartinLutherKingJr,
        USPresidentsDay,
        GoodFriday,
        USMemorialDay,
        Holiday('USIndependenceDay', month=7, day=4, observance=nearest_workday),
        USLaborDay,
        USThanksgivingDay,
        Holiday('Christmas', month=12, day=25, observance=nearest_workday)
    ]


def get_trading_close_holidays(year):
    inst = USTradingCalendar()

    return inst.holidays(dt.datetime(year-1, 12, 31), dt.datetime(year, 12, 31))


if __name__ == '__main__':
    print(get_trading_close_holidays(2016))
    #    DatetimeIndex(['2016-01-01', '2016-01-18', '2016-02-15', '2016-03-25',
    #                   '2016-05-30', '2016-07-04', '2016-09-05', '2016-11-24',
    #                   '2016-12-26'],
    #                  dtype='datetime64[ns]', freq=None)
Pierre Boutquin
  • 624
  • 6
  • 7
  • Warning: If you use this for multiple years, you may get duplicate dates. For example, try 2021 and 2022. '2021-12-31' will be present for both years. – Michael Goldshteyn Aug 07 '18 at 20:21
  • Not sure why you are getting `2021-12-31` since that isn't a holiday. I updated `get_trading_close_holidays(startyr, endyr)` and it works just fine. – spitfiredd Feb 07 '19 at 16:01
  • 1
    It's probably not best to do this from scratch. For one, trading holidays do change (see Veterans day with the NYSE). Using a package like pandas_market_calendars as shown by Ryan Sheftel below is likely a better way to protect against that danger while also avoiding having to recreate the wheel for the task. – jmdeamer Nov 12 '20 at 03:48
  • NewYearsDay should be 'sunday_to_monday', not 'nearest_workday'. (If NYD is a Saturday, it is not a US trading holiday). – Charles Plager Jan 11 '22 at 12:23
  • + `Holiday('Juneteenth', month=6, day=19, observance=nearest_workday),` – gregV Dec 10 '22 at 04:37
  • @gregV : Juneteenth was declared in 2021 but its first NYSE observance was a year later; also we don't want it counted for other years before 2022 in backtesters : + Holiday('Juneteenth', month=6, day=19, observance=nearest_workday, start_date='2022-06-20') – Qayum Khan Aug 26 '23 at 03:16
30

If it helps, I had a similar need for exchange trading calendars. There was some excellent code buried in the Zipline project by Quantopian. I extracted out the relevant part and created a new project for creating market exchange trading calendars in pandas. The links are here, with some of the functionality described below.

https://github.com/rsheftel/pandas_market_calendars

https://pypi.python.org/pypi/pandas-market-calendars

Here is what it can do by creating a pandas DatetimeIndex of all of the valid open hours for the NYSE:

import pandas_market_calendars as mcal
nyse = mcal.get_calendar('NYSE')

early = nyse.schedule(start_date='2012-07-01', end_date='2012-07-10')
early

                  market_open             market_close
=========== ========================= =========================
2012-07-02 2012-07-02 13:30:00+00:00 2012-07-02 20:00:00+00:00
2012-07-03 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00
2012-07-05 2012-07-05 13:30:00+00:00 2012-07-05 20:00:00+00:00
2012-07-06 2012-07-06 13:30:00+00:00 2012-07-06 20:00:00+00:00
2012-07-09 2012-07-09 13:30:00+00:00 2012-07-09 20:00:00+00:00
2012-07-10 2012-07-10 13:30:00+00:00 2012-07-10 20:00:00+00:00

mcal.date_range(early, frequency='1D')

DatetimeIndex(['2012-07-02 20:00:00+00:00', '2012-07-03 17:00:00+00:00',
               '2012-07-05 20:00:00+00:00', '2012-07-06 20:00:00+00:00',
               '2012-07-09 20:00:00+00:00', '2012-07-10 20:00:00+00:00'],
               dtype='datetime64[ns, UTC]', freq=None)

mcal.date_range(early, frequency='1H')

DatetimeIndex(['2012-07-02 14:30:00+00:00', '2012-07-02 15:30:00+00:00',
               '2012-07-02 16:30:00+00:00', '2012-07-02 17:30:00+00:00',
               '2012-07-02 18:30:00+00:00', '2012-07-02 19:30:00+00:00',
               '2012-07-02 20:00:00+00:00', '2012-07-03 14:30:00+00:00',
               '2012-07-03 15:30:00+00:00', '2012-07-03 16:30:00+00:00',
               '2012-07-03 17:00:00+00:00', '2012-07-05 14:30:00+00:00',
               '2012-07-05 15:30:00+00:00', '2012-07-05 16:30:00+00:00',
               '2012-07-05 17:30:00+00:00', '2012-07-05 18:30:00+00:00',
               '2012-07-05 19:30:00+00:00', '2012-07-05 20:00:00+00:00',
               '2012-07-06 14:30:00+00:00', '2012-07-06 15:30:00+00:00',
               '2012-07-06 16:30:00+00:00', '2012-07-06 17:30:00+00:00',
               '2012-07-06 18:30:00+00:00', '2012-07-06 19:30:00+00:00',
               '2012-07-06 20:00:00+00:00', '2012-07-09 14:30:00+00:00',
               '2012-07-09 15:30:00+00:00', '2012-07-09 16:30:00+00:00',
               '2012-07-09 17:30:00+00:00', '2012-07-09 18:30:00+00:00',
               '2012-07-09 19:30:00+00:00', '2012-07-09 20:00:00+00:00',
               '2012-07-10 14:30:00+00:00', '2012-07-10 15:30:00+00:00',
               '2012-07-10 16:30:00+00:00', '2012-07-10 17:30:00+00:00',
               '2012-07-10 18:30:00+00:00', '2012-07-10 19:30:00+00:00',
               '2012-07-10 20:00:00+00:00'],
              dtype='datetime64[ns, UTC]', freq=None)

If you just want to get the pandas Holiday Calendar that can be used in other pandas functions that take that as an argument:

holidays = nyse.holidays()

holidays.holidays[-5:]
(numpy.datetime64('2030-05-27'),
 numpy.datetime64('2030-07-04'),
 numpy.datetime64('2030-09-02'),
 numpy.datetime64('2030-11-28'),
 numpy.datetime64('2030-12-25'))
Ryan Sheftel
  • 476
  • 5
  • 4
  • This library isn't reliable. When I tried it, 2010-01-01 and other New Years Days were showing up in the calendar, as well as Good Friday and others. – Marshall Farrier Feb 23 '17 at 05:36
  • 6
    You are likely not using the package properly. 2010-01-01 and all other New Years Days and Good Friday's are not included as a trading day. Try the example code above. If you would like to learn how to use the package, I suggest the online docs or you can message me directly if you are still having issues. – Ryan Sheftel Feb 24 '17 at 12:04
  • 3
    You're right. I checked it against the calendar I've tested since 1995, and it was accurate. Can't remember what syntax I was using that gave me undesirable results. – Marshall Farrier Mar 03 '17 at 05:22
  • Another user reported the minor change to make it work for python 2 here: https://github.com/rsheftel/pandas_market_calendars/issues/6 – Ryan Sheftel Mar 09 '17 at 18:43
  • The new version (v0.5) now supports Python2. It is available on GitHub or PyPi – Ryan Sheftel Mar 27 '17 at 14:58
  • 1
    As of 2020, this package is still actively maintained, with PRs from over 20 contributors, a responsive package owner, and over 30 releases on PyPi. Great job giving starting attribution to Quantopian/Zipline, as well! – ClimbsRocks Aug 14 '20 at 21:39
16

You have to create new instance of class: cal1 = tradingCal(). This works for me.

from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, GoodFriday
from datetime import datetime

cal = get_calendar('USFederalHolidayCalendar')  # Create calendar instance
cal.rules.pop(7)                                # Remove Veteran's Day rule
cal.rules.pop(6)                                # Remove Columbus Day rule
tradingCal = HolidayCalendarFactory('TradingCalendar', cal, GoodFriday)
print tradingCal.rules

#new instance of class
cal1 = tradingCal()

print cal1.holidays(datetime(2014, 12, 31), datetime(2016, 12, 31))

#DatetimeIndex(['2015-01-01', '2015-01-19', '2015-02-16', '2015-04-03',
#               '2015-05-25', '2015-07-03', '2015-09-07', '2015-11-26',
#               '2015-12-25', '2016-01-01', '2016-01-18', '2016-02-15',
#              '2016-03-25', '2016-05-30', '2016-07-04', '2016-09-05',
#               '2016-11-24', '2016-12-26'],
#              dtype='datetime64[ns]', freq=None, tz=None)
jezrael
  • 822,522
  • 95
  • 1,334
  • 1,252
  • 6
    I think this has a significant shortcoming! Using `.pop` affects the underlying class `pandas.tseries.holiday.USFederalHolidayCalendar` because it is an "in-place" operation (not sure of the terminology). This means that if you try and recreate cal2 from `get_calendar('USFederalHolidayCalendar')` the rules are the same as `cal`. ie you don't get a *clean* version of `USFederalHolidayCalendar` because it doesn't exist anymore, you've modified it! – evan54 Aug 13 '16 at 14:32
  • @evan54 I just noticed this as well. Do you have a solution? – WillZ Aug 17 '16 at 09:42
0

I took a slightly different approach using the same package:

https://pandas-market-calendars.readthedocs.io/en/latest/usage.html#exchange-open-valid-business-days

import calendar
from datetime import timedelta
import pandas_market_calendars as mcal

nyse = mcal.get_calendar('NYSE')
days = nyse.valid_days(start_date=today, end_date=today + timedelta(days=60))
if dt in days:
    print('ok')

so just get your list, check if your trade date is good to go, and proceed.

grantr
  • 878
  • 8
  • 16