ENH: Added early closes to trading environment

specifically, expected 1 PM closes since 1993
This commit is contained in:
Richard Frank
2013-07-15 16:17:24 -04:00
committed by Eddie Hebert
parent efe50f8494
commit 75dd77ea03
3 changed files with 170 additions and 2 deletions
+60
View File
@@ -182,3 +182,63 @@ If Nov has 5 Thursdays, {0} Thanksgiving is not the last week.
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
def test_day_after_thanksgiving(self):
early_closes = tradingcalendar.get_early_closes(
tradingcalendar.start,
tradingcalendar.end.replace(year=tradingcalendar.end.year + 1)
)
# November 2012
# Su Mo Tu We Th Fr Sa
# 1 2 3
# 4 5 6 7 8 9 10
# 11 12 13 14 15 16 17
# 18 19 20 21 22 23 24
# 25 26 27 28 29 30
fourth_friday = datetime.datetime(2012, 11, 23, tzinfo=pytz.utc)
self.assertIn(fourth_friday, early_closes)
# November 2013
# Su Mo Tu We Th Fr Sa
# 1 2
# 3 4 5 6 7 8 9
# 10 11 12 13 14 15 16
# 17 18 19 20 21 22 23
# 24 25 26 27 28 29 30
fifth_friday = datetime.datetime(2013, 11, 29, tzinfo=pytz.utc)
self.assertIn(fifth_friday, early_closes)
def test_early_close_independence_day_thursday(self):
"""
Until 2013, the market closed early the Friday after an
Independence Day on Thursday. Since then, the early close is on
Wednesday.
"""
early_closes = tradingcalendar.get_early_closes(
tradingcalendar.start,
tradingcalendar.end.replace(year=tradingcalendar.end.year + 1)
)
# July 2002
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = datetime.datetime(2002, 7, 3, tzinfo=pytz.utc)
friday_after = datetime.datetime(2002, 7, 5, tzinfo=pytz.utc)
self.assertNotIn(wednesday_before, early_closes)
self.assertIn(friday_after, early_closes)
# July 2013
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = datetime.datetime(2013, 7, 3, tzinfo=pytz.utc)
friday_after = datetime.datetime(2013, 7, 5, tzinfo=pytz.utc)
self.assertIn(wednesday_before, early_closes)
self.assertNotIn(friday_after, early_closes)
+9 -2
View File
@@ -22,6 +22,7 @@ from delorean import Delorean
import pandas as pd
from zipline.data.loader import load_market_data
from zipline.utils.tradingcalendar import get_early_closes
log = logbook.Logger('Trading')
@@ -89,6 +90,7 @@ class TradingEnvironment(object):
self.treasury_curves = self.treasury_curves[:max_date]
self.full_trading_day = datetime.timedelta(hours=6, minutes=30)
self.early_close_trading_day = datetime.timedelta(hours=3, minutes=30)
self.exchange_tz = exchange_tz
bm = None
@@ -112,6 +114,9 @@ class TradingEnvironment(object):
self.first_trading_day = self.trading_days[0]
self.last_trading_day = self.trading_days[-1]
self.early_closes = get_early_closes(self.first_trading_day,
self.last_trading_day)
def __enter__(self, *args, **kwargs):
global environment
self.prev_environment = environment
@@ -203,8 +208,10 @@ Last successful date: %s" % self.last_trading_day)
return market_open, market_close
def get_trading_day_duration(self, trading_day):
# TODO: make a list of half-days and modify the
# calculation of market close to reflect them.
trading_day = self.normalize_date(trading_day)
if trading_day in self.early_closes:
return self.early_close_trading_day
return self.full_trading_day
def trading_day_distance(self, first_date, second_date):
+101
View File
@@ -247,4 +247,105 @@ def get_trading_days(start, end):
return business_days - non_trading_days
trading_days = get_trading_days(start, end)
def get_early_closes(start, end):
# 1:00 PM close rules based on
# http://quant.stackexchange.com/questions/4083/nyse-early-close-rules-july-4th-and-dec-25th # noqa
# and verified against http://www.nyse.com/pdfs/closings.pdf
# These rules are valid starting in 1993
start = max(start, datetime(1993, 1, 1, tzinfo=pytz.utc))
end = max(end, datetime(1993, 1, 1, tzinfo=pytz.utc))
# Not included here are early closes prior to 1993
# or unplanned early closes
early_close_rules = []
day_after_thanksgiving = rrule.rrule(
rrule.MONTHLY,
bymonth=11,
# 4th Friday isn't correct if month starts on Friday, so restrict to
# day range:
byweekday=(rrule.FR),
bymonthday=range(23, 30),
cache=True,
dtstart=start,
until=end
)
early_close_rules.append(day_after_thanksgiving)
christmas_eve = rrule.rrule(
rrule.MONTHLY,
bymonth=12,
bymonthday=24,
byweekday=(rrule.MO, rrule.TU, rrule.WE, rrule.TH),
cache=True,
dtstart=start,
until=end
)
early_close_rules.append(christmas_eve)
friday_after_christmas = rrule.rrule(
rrule.MONTHLY,
bymonth=12,
bymonthday=26,
byweekday=rrule.FR,
cache=True,
dtstart=start,
# valid 1993-2007
until=min(end, datetime(2007, 12, 31, tzinfo=pytz.utc))
)
early_close_rules.append(friday_after_christmas)
day_before_independence_day = rrule.rrule(
rrule.MONTHLY,
bymonth=7,
bymonthday=3,
byweekday=(rrule.MO, rrule.TU, rrule.TH),
cache=True,
dtstart=start,
until=end
)
early_close_rules.append(day_before_independence_day)
day_after_independence_day = rrule.rrule(
rrule.MONTHLY,
bymonth=7,
bymonthday=5,
byweekday=rrule.FR,
cache=True,
dtstart=start,
# starting in 2013: wednesday before independence day
until=min(end, datetime(2012, 12, 31, tzinfo=pytz.utc))
)
early_close_rules.append(day_after_independence_day)
wednesday_before_independence_day = rrule.rrule(
rrule.MONTHLY,
bymonth=7,
bymonthday=3,
byweekday=rrule.WE,
cache=True,
# starting in 2013
dtstart=max(start, datetime(2013, 1, 1, tzinfo=pytz.utc)),
until=max(end, datetime(2013, 1, 1, tzinfo=pytz.utc))
)
early_close_rules.append(wednesday_before_independence_day)
early_close_ruleset = rrule.rruleset()
for rule in early_close_rules:
early_close_ruleset.rrule(rule)
early_closes = early_close_ruleset.between(start, end, inc=True)
# Misc early closings from NYSE listing.
# http://www.nyse.com/pdfs/closings.pdf
#
# New Year's Eve
early_closes.append(datetime(1999, 12, 31, tzinfo=pytz.utc))
return pd.DatetimeIndex(sorted(early_closes))