Code Sample, a copy-pastable example if possible
import datetime
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, \
USMartinLutherKingJr, USPresidentsDay, USMemorialDay, USLaborDay, \
USThanksgivingDay, GoodFriday, nearest_workday
import pandas as pd
from pandas.tseries.offsets import BDay
class TradingCalendar(AbstractHolidayCalendar):
"""Calendar of trading holidays in the US"""
rules = [
Holiday('New Years Day', month=1, day=1, observance=nearest_workday),
USMartinLutherKingJr,
USPresidentsDay,
USMemorialDay,
Holiday('July 4th', month=7, day=4, observance=nearest_workday),
USLaborDay,
USThanksgivingDay,
GoodFriday,
Holiday('Christmas', month=12, day=25, observance=nearest_workday)
]
class TradingDay(CustomBusinessDay):
def __init__(self, n=1):
super(TradingDay, self).__init__(n, calendar=TradingCalendar())
r = pd.date_range(datetime.datetime(2017, 12, 26), datetime.datetime(2017, 1, 6), TradingDay(1))
len(r) == 9
Problem description
If you use a standard BDay() in the date_range() call, everything works as expected. However, if you use a CustomBusinessDay, it ends with a TypeError:
Traceback (most recent call last):
File "/Users/bspeice/Development/datemath.py", line 102, in trading_range
return pd.date_range(start, end, freq=TradingDay(skip))
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/index.py", line 2023, in date_range
closed=closed, **kwargs)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/util/decorators.py", line 91, in wrapper
return func(*args, **kwargs)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/index.py", line 301, in __new__
ambiguous=ambiguous)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/index.py", line 506, in _generate
index = _generate_regular_range(start, end, periods, offset)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/index.py", line 1976, in _generate_regular_range
dates = list(xdr)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/offsets.py", line 2881, in generate_range
start = offset.rollforward(start)
File "/Users/bspeice/venvs/scientific/lib/python2.7/site-packages/pandas/tseries/offsets.py", line 432, in rollforward
dt = dt + self.__class__(1, normalize=self.normalize, **self.kwds)
TypeError: __init__() got an unexpected keyword argument 'normalize'
This error happens with every release from 0.18.0 - 0.19.2, I haven't tested outside this range.
Expected Output
True
Output of pd.show_versions()
Comment From: jreback
you are not using this according the docs: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#custom-business-days-experimental
you need to pass the holidays to the CustomBusinessDay. In fact you are creating a new frequency (well you are trying to, but that is quite complicated).
Comment From: bspeice
It seems strange that I must first "realize" what the actual holiday dates are, before creating the CustomBusinessDay() object. I've attached the workaround code if anyone needs this in the future:
import datetime
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, \
USMartinLutherKingJr, USPresidentsDay, USMemorialDay, USLaborDay, \
USThanksgivingDay, GoodFriday, nearest_workday
import pandas as pd
from pandas.tseries.offsets import BDay
class TradingCalendar(AbstractHolidayCalendar):
"""Calendar of trading holidays in the US"""
rules = [
Holiday('New Years Day', month=1, day=1, observance=nearest_workday),
USMartinLutherKingJr,
USPresidentsDay,
USMemorialDay,
Holiday('July 4th', month=7, day=4, observance=nearest_workday),
USLaborDay,
USThanksgivingDay,
GoodFriday,
Holiday('Christmas', month=12, day=25, observance=nearest_workday)
]
start = datetime.datetime(2017, 12, 26)
end = datetime.datetime(2017, 1, 6)
realized_days = TradingCalendar().calendar(start, end)
r = pd.date_range(start, end, freq=CustomBusinessDay(holidays=realized_days))
len(r) == 9
Comment From: jreback
@bspeice generally a holiday calendar is defined for a range of available dates (years possibly), this essentially the definition of a calendar itself.
constructing a range generates the ranges from that calendar. I think this is clear from the docs (as this is exactly what the example does), but if not you are welcome to submit a doc update. If you feel this is a bug, please be specific.
Comment From: bspeice
So, what I missed as a dumb mistake is that datetime.datetime(2017, 12, 26) is after datetime.datetime(2017, 1, 6). If you change it to datetime.datetime(2016, 12, 26) as was originally intended, my first method works correctly.
This is what I believe is a bug:
# TradingCalendar has been set up as in previous comments
# This works:
pd.date_range(datetime.datetime(2017, 12, 26), datetime.datetime(2017, 1, 9), freq=BDay())
# This also works:
pd.date_range(datetime.datetime(2017, 12, 26), datetime.datetime(2017, 1, 9), freq=CustomBusinessDay(calendar=TradingCalendar()))
# This does not work:
class TradingDay(CustomBusinessDay):
def __init__(self):
super(TradingDay, self).__init__(calendar=TradingCalendar())
pd.date_range(datetime.datetime(2017, 12, 26), datetime.datetime(2017, 1, 9), freq=TradingDay())
# ...
# TypeError: __init__() got an unexpected keyword argument 'normalize'
Comment From: jreback
@bspeice that's not a bug, you are not calling the constructor properly. As I said inheriting from a frequency requires you to follow the pattern (there are args, kwargs passed).
Comment From: bspeice
OK, then I'll just use the original CustomBusinessDay. Thanks for your assistance.