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.