Merge pull request #1335 from quantopian/more-calendars

ENH: ICE calendar, more calendar tests, cleaned up CME calendar
This commit is contained in:
Jean Bredeche
2016-07-20 08:29:35 -04:00
committed by GitHub
18 changed files with 22140 additions and 560 deletions
View File
+42
View File
@@ -0,0 +1,42 @@
from unittest import TestCase
import pandas as pd
from tests.calendars.test_trading_calendar import ExchangeCalendarTestBase
from zipline.utils.calendars.exchange_calendar_cfe import CFEExchangeCalendar
class CFECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = "cfe"
calendar_class = CFEExchangeCalendar
MAX_SESSION_HOURS = 8
def test_2016_holidays(self):
# new years: jan 1
# mlk: jan 18
# presidents: feb 15
# good friday: mar 25
# mem day: may 30
# independence day: july 4
# labor day: sep 5
# thanksgiving day: nov 24
# christmas (observed): dec 26
# new years (observed): jan 2 2017
for day in ["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", "2017-01-02"]:
self.assertFalse(
self.calendar.is_session(pd.Timestamp(day, tz='UTC'))
)
def test_2016_early_closes(self):
# only early close is day after thanksgiving: nov 25
dt = pd.Timestamp("2016-11-25", tz='UTC')
self.assertTrue(dt in self.calendar.early_closes)
market_close = self.calendar.schedule.loc[dt].market_close
market_close = market_close.tz_localize("UTC").tz_convert(
self.calendar.tz
)
self.assertEqual(12, market_close.hour)
self.assertEqual(15, market_close.minute)
+41
View File
@@ -0,0 +1,41 @@
from unittest import TestCase
import pandas as pd
from .test_trading_calendar import ExchangeCalendarTestBase
from zipline.utils.calendars.exchange_calendar_cme import CMEExchangeCalendar
class CMECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = "cme"
calendar_class = CMEExchangeCalendar
GAPS_BETWEEN_SESSIONS = False
MAX_SESSION_HOURS = 24
def test_2016_holidays(self):
# good friday: 2016-03-25
# christmas (observed)_: 2016-12-26
# new years (observed): 2016-01-02
for date in ["2016-03-25", "2016-12-26", "2016-01-02"]:
self.assertFalse(
self.calendar.is_session(pd.Timestamp(date, tz='UTC'))
)
def test_2016_early_closes(self):
# mlk day: 2016-01-18
# presidents: 2016-02-15
# mem day: 2016-05-30
# july 4: 2016-07-04
# labor day: 2016-09-05
# thankgiving: 2016-11-24
for date in ["2016-01-18", "2016-02-15", "2016-05-30", "2016-07-04",
"2016-09-05", "2016-11-24"]:
dt = pd.Timestamp(date, tz='UTC')
self.assertTrue(dt in self.calendar.early_closes)
market_close = self.calendar.schedule.loc[dt].market_close
self.assertEqual(
12,
market_close.tz_localize('UTC').tz_convert(
self.calendar.tz
).hour
)
+54
View File
@@ -0,0 +1,54 @@
from unittest import TestCase
import pandas as pd
from .test_trading_calendar import ExchangeCalendarTestBase
from zipline.utils.calendars.exchange_calendar_ice import ICEExchangeCalendar
class ICECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = 'ice'
calendar_class = ICEExchangeCalendar
MAX_SESSION_HOURS = 22
def test_hurricane_sandy_one_day(self):
self.assertFalse(
self.calendar.is_session(pd.Timestamp("2012-10-29", tz='UTC'))
)
# ICE wasn't closed on day 2 of hurricane sandy
self.assertTrue(
self.calendar.is_session(pd.Timestamp("2012-10-30", tz='UTC'))
)
def test_2016_holidays(self):
# 2016 holidays:
# new years: 2016-01-01
# good friday: 2016-03-25
# christmas (observed): 2016-12-26
for date in ["2016-01-01", "2016-03-25", "2016-12-26"]:
self.assertFalse(
self.calendar.is_session(pd.Timestamp(date, tz='UTC'))
)
def test_2016_early_closes(self):
# 2016 early closes
# mlk: 2016-01-18
# presidents: 2016-02-15
# mem day: 2016-05-30
# independence day: 2016-07-04
# labor: 2016-09-05
# thanksgiving: 2016-11-24
for date in ["2016-01-18", "2016-02-15", "2016-05-30", "2016-07-04",
"2016-09-05", "2016-11-24"]:
dt = pd.Timestamp(date, tz='UTC')
self.assertTrue(dt in self.calendar.early_closes)
market_close = self.calendar.schedule.loc[dt].market_close
self.assertEqual(
13, # all ICE early closes are 1 pm local
market_close.tz_localize("UTC").tz_convert(
self.calendar.tz
).hour
)
+228
View File
@@ -0,0 +1,228 @@
from unittest import TestCase
import pandas as pd
from tests.calendars.test_trading_calendar import ExchangeCalendarTestBase
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
class NYSECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = 'nyse'
calendar_class = NYSEExchangeCalendar
MAX_SESSION_HOURS = 6.5
def test_2012(self):
# holidays we expect:
holidays_2012 = [
pd.Timestamp("2012-01-02", tz='UTC'),
pd.Timestamp("2012-01-16", tz='UTC'),
pd.Timestamp("2012-02-20", tz='UTC'),
pd.Timestamp("2012-04-06", tz='UTC'),
pd.Timestamp("2012-05-28", tz='UTC'),
pd.Timestamp("2012-07-04", tz='UTC'),
pd.Timestamp("2012-09-03", tz='UTC'),
pd.Timestamp("2012-11-22", tz='UTC'),
pd.Timestamp("2012-12-25", tz='UTC')
]
for session_label in holidays_2012:
self.assertNotIn(session_label, self.calendar.all_sessions)
# early closes we expect:
early_closes_2012 = [
pd.Timestamp("2012-07-03", tz='UTC'),
pd.Timestamp("2012-11-23", tz='UTC'),
pd.Timestamp("2012-12-24", tz='UTC')
]
for early_close_session_label in early_closes_2012:
self.assertIn(early_close_session_label,
self.calendar.early_closes)
def test_special_holidays(self):
# 9/11
# Sept 11, 12, 13, 14 2001
self.assertNotIn(pd.Period("9/11/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/12/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/13/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/14/2001"), self.calendar.all_sessions)
# Hurricane Sandy
# Oct 29, 30 2012
self.assertNotIn(pd.Period("10/29/2012"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("10/30/2012"), self.calendar.all_sessions)
# various national days of mourning
# Gerald Ford - 1/2/2007
self.assertNotIn(pd.Period("1/2/2007"), self.calendar.all_sessions)
# Ronald Reagan - 6/11/2004
self.assertNotIn(pd.Period("6/11/2004"), self.calendar.all_sessions)
# Richard Nixon - 4/27/1994
self.assertNotIn(pd.Period("4/27/1994"), self.calendar.all_sessions)
def test_new_years(self):
"""
Check whether the TradingCalendar contains certain dates.
"""
# January 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 31
start_session = pd.Timestamp("2012-01-02", tz='UTC')
end_session = pd.Timestamp("2013-12-31", tz='UTC')
sessions = self.calendar.sessions_in_range(start_session, end_session)
day_after_new_years_sunday = pd.Timestamp("2012-01-02",
tz='UTC')
self.assertNotIn(day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Monday after is a holiday.
""".strip().format(day_after_new_years_sunday)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
# January 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
new_years_day = pd.Timestamp("2013-01-01", tz='UTC')
self.assertNotIn(new_years_day, sessions,
"""
If NYE falls during the week, e.g. {0}, it is a holiday.
""".strip().format(new_years_day)
)
first_trading_day_after_new_years = pd.Timestamp("2013-01-02",
tz='UTC')
self.assertIn(first_trading_day_after_new_years, sessions,
"""
If the day after NYE falls during the week, {0} \
is the first trading day.
""".strip().format(first_trading_day_after_new_years)
)
def test_thanksgiving(self):
"""
Check TradingCalendar Thanksgiving dates.
"""
# November 2005
# 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
start_session_label = pd.Timestamp('2005-01-01', tz='UTC')
end_session_label = pd.Timestamp('2012-12-31', tz='UTC')
sessions = self.calendar.sessions_in_range(start_session_label,
end_session_label)
thanksgiving_with_four_weeks = pd.Timestamp("2005-11-24", tz='UTC')
self.assertNotIn(thanksgiving_with_four_weeks, sessions,
"""
If Nov has 4 Thursdays, {0} Thanksgiving is the last Thursday.
""".strip().format(thanksgiving_with_four_weeks)
)
# November 2006
# 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
thanksgiving_with_five_weeks = pd.Timestamp("2006-11-23", tz='UTC')
self.assertNotIn(thanksgiving_with_five_weeks, sessions,
"""
If Nov has 5 Thursdays, {0} Thanksgiving is not the last week.
""".strip().format(thanksgiving_with_five_weeks)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
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):
# 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_open = pd.Timestamp('11/23/2012 11:00AM', tz='EST')
fourth_friday = pd.Timestamp('11/23/2012 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fourth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fourth_friday))
# 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_open = pd.Timestamp('11/29/2013 11:00AM', tz='EST')
fifth_friday = pd.Timestamp('11/29/2013 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fifth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fifth_friday))
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.
"""
# 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 = pd.Timestamp('7/3/2002 3:00PM', tz='EST')
friday_after_open = pd.Timestamp('7/5/2002 11:00AM', tz='EST')
friday_after = pd.Timestamp('7/5/2002 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertFalse(self.calendar.is_open_on_minute(friday_after))
# 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 = pd.Timestamp('7/3/2013 3:00PM', tz='EST')
friday_after_open = pd.Timestamp('7/5/2013 11:00AM', tz='EST')
friday_after = pd.Timestamp('7/5/2013 3:00PM', tz='EST')
self.assertFalse(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertTrue(self.calendar.is_open_on_minute(friday_after))
@@ -12,7 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import time
from os.path import (
abspath,
@@ -25,24 +24,23 @@ from collections import namedtuple
import numpy as np
import pandas as pd
from nose_parameterized import parameterized
from pandas import (
read_csv,
Timestamp,
)
from pandas import read_csv
from pandas.tslib import Timedelta
from pandas.util.testing import assert_index_equal
from pytz import timezone
from zipline.errors import (
CalendarNameCollision,
InvalidCalendarName,
)
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars.trading_calendar import days_at_time
from zipline.utils.calendars import(
register_calendar,
deregister_calendar,
get_calendar,
clear_calendars,
)
from zipline.utils.calendars.trading_calendar import days_at_time
class CalendarRegistrationTestCase(TestCase):
@@ -121,6 +119,10 @@ class ExchangeCalendarTestBase(object):
answer_key_filename = None
calendar_class = None
GAPS_BETWEEN_SESSIONS = True
MAX_SESSION_HOURS = 0
@staticmethod
def load_answer_key(filename):
"""
@@ -128,7 +130,7 @@ class ExchangeCalendarTestBase(object):
"""
fullpath = join(
dirname(abspath(__file__)),
'resources',
'../resources',
'calendars',
filename + '.csv',
)
@@ -154,6 +156,13 @@ class ExchangeCalendarTestBase(object):
cls.one_minute = pd.Timedelta(minutes=1)
cls.one_hour = pd.Timedelta(hours=1)
def test_sanity_check_session_lengths(self):
# make sure that no session is longer than self.MAX_SESSION_HOURS hours
for session in self.calendar.all_sessions:
o, c = self.calendar.open_and_close_for_session(session)
delta = c - o
self.assertTrue((delta.seconds / 3600) <= self.MAX_SESSION_HOURS)
def test_calculated_against_csv(self):
assert_index_equal(self.calendar.schedule.index, self.answers.index)
@@ -165,18 +174,21 @@ class ExchangeCalendarTestBase(object):
# The exchange should be classified as open on its first minute
self.assertTrue(self.calendar.is_open_on_minute(market_minute_utc))
# Decrement minute by one, to minute where the market was not open
pre_market = market_minute_utc - one_minute
self.assertFalse(self.calendar.is_open_on_minute(pre_market))
if self.GAPS_BETWEEN_SESSIONS:
# Decrement minute by one, to minute where the market was not
# open
pre_market = market_minute_utc - one_minute
self.assertFalse(self.calendar.is_open_on_minute(pre_market))
for market_minute in self.answers.market_close:
close_minute_utc = market_minute
# should be open on its last minute
self.assertTrue(self.calendar.is_open_on_minute(close_minute_utc))
# increment minute by one minute, should be closed
post_market = close_minute_utc + one_minute
self.assertFalse(self.calendar.is_open_on_minute(post_market))
if self.GAPS_BETWEEN_SESSIONS:
# increment minute by one minute, should be closed
post_market = close_minute_utc + one_minute
self.assertFalse(self.calendar.is_open_on_minute(post_market))
def _verify_minute(self, calendar, minute,
next_open_answer, prev_open_answer,
@@ -203,12 +215,12 @@ class ExchangeCalendarTestBase(object):
def test_next_prev_open_close(self):
# for each session, check:
# - the minute before the open
# - the minute before the open (if gaps exist between sessions)
# - the first minute of the session
# - the second minute of the session
# - the minute before the close
# - the last minute of the session
# - the first minute after the close
# - the first minute after the close (if gaps exist between sessions)
answers_to_use = self.answers[1:-2]
for idx, info in enumerate(answers_to_use.iterrows()):
@@ -227,10 +239,11 @@ class ExchangeCalendarTestBase(object):
next_close = self.answers.iloc[idx + 2].market_close
# minute before open
self._verify_minute(
self.calendar, minute_before_open, open_minute, previous_open,
close_minute, previous_close
)
if self.GAPS_BETWEEN_SESSIONS:
self._verify_minute(
self.calendar, minute_before_open, open_minute,
previous_open, close_minute, previous_close
)
# open minute
self._verify_minute(
@@ -257,10 +270,11 @@ class ExchangeCalendarTestBase(object):
)
# minute after the close
self._verify_minute(
self.calendar, close_minute + self.one_minute, next_open,
open_minute, next_close, close_minute
)
if self.GAPS_BETWEEN_SESSIONS:
self._verify_minute(
self.calendar, close_minute + self.one_minute, next_open,
open_minute, next_close, close_minute
)
def test_next_prev_minute(self):
all_minutes = self.calendar.all_minutes
@@ -278,19 +292,20 @@ class ExchangeCalendarTestBase(object):
)
# test a couple of non-market minutes
for open_minute in self.answers.market_open[1:]:
hour_before_open = open_minute - self.one_hour
self.assertEqual(
open_minute,
self.calendar.next_minute(hour_before_open)
)
if self.GAPS_BETWEEN_SESSIONS:
for open_minute in self.answers.market_open[1:]:
hour_before_open = open_minute - self.one_hour
self.assertEqual(
open_minute,
self.calendar.next_minute(hour_before_open)
)
for close_minute in self.answers.market_close[1:]:
hour_after_close = close_minute + self.one_hour
self.assertEqual(
close_minute,
self.calendar.previous_minute(hour_after_close)
)
for close_minute in self.answers.market_close[1:]:
hour_after_close = close_minute + self.one_hour
self.assertEqual(
close_minute,
self.calendar.previous_minute(hour_after_close)
)
def test_minute_to_session_label(self):
for idx, info in enumerate(self.answers[1:-2].iterrows()):
@@ -328,18 +343,29 @@ class ExchangeCalendarTestBase(object):
direction="previous"),
self.calendar.minute_to_session_label(close_minute,
direction="none"),
self.calendar.minute_to_session_label(minute_before_session),
self.calendar.minute_to_session_label(
minute_before_session,
direction="next"
),
self.calendar.minute_to_session_label(
minute_after_session,
direction="previous"
),
session_label
]
if self.GAPS_BETWEEN_SESSIONS:
minutes_that_resolve_to_this_session.append(
self.calendar.minute_to_session_label(
minute_before_session
)
)
minutes_that_resolve_to_this_session.append(
self.calendar.minute_to_session_label(
minute_before_session,
direction="next"
)
)
minutes_that_resolve_to_this_session.append(
self.calendar.minute_to_session_label(
minute_after_session,
direction="previous"
)
)
self.assertTrue(all(x == minutes_that_resolve_to_this_session[0]
for x in minutes_that_resolve_to_this_session))
@@ -363,9 +389,12 @@ class ExchangeCalendarTestBase(object):
with self.assertRaises(ValueError):
self.calendar.minute_to_session_label(open_minute, "asdf")
with self.assertRaises(ValueError):
self.calendar.minute_to_session_label(minute_before_session,
direction="none")
if self.GAPS_BETWEEN_SESSIONS:
with self.assertRaises(ValueError):
self.calendar.minute_to_session_label(
minute_before_session,
direction="none"
)
def test_next_prev_session(self):
session_labels = self.answers.index[1:-2]
@@ -494,7 +523,11 @@ class ExchangeCalendarTestBase(object):
minute_after_last_close
)
np.testing.assert_array_equal(minutes1, minutes2)
if self.GAPS_BETWEEN_SESSIONS:
np.testing.assert_array_equal(minutes1, minutes2)
else:
# if no gaps, then minutes2 should have 2 extra minutes
np.testing.assert_array_equal(minutes1, minutes2[1:-1])
# manually construct the minutes
all_minutes = np.concatenate([
@@ -574,223 +607,34 @@ class ExchangeCalendarTestBase(object):
self.assertEqual(open_answer, found_open)
self.assertEqual(close_answer, found_close)
def test_daylight_savings(self):
# 2004 daylight savings switches:
# Sunday 2004-04-04 and Sunday 2004-10-31
class NYSECalendarTestCase(ExchangeCalendarTestBase, TestCase):
# make sure there's no weirdness around calculating the next day's
# session's open time.
answer_key_filename = 'nyse'
calendar_class = NYSEExchangeCalendar
for date in ["2004-04-05", "2004-11-01"]:
next_day = pd.Timestamp(date, tz='UTC')
open_date = next_day + Timedelta(days=self.calendar.open_offset)
def test_2012(self):
# holidays we expect:
holidays_2012 = [
pd.Timestamp("2012-01-02", tz='UTC'),
pd.Timestamp("2012-01-16", tz='UTC'),
pd.Timestamp("2012-02-20", tz='UTC'),
pd.Timestamp("2012-04-06", tz='UTC'),
pd.Timestamp("2012-05-28", tz='UTC'),
pd.Timestamp("2012-07-04", tz='UTC'),
pd.Timestamp("2012-09-03", tz='UTC'),
pd.Timestamp("2012-11-22", tz='UTC'),
pd.Timestamp("2012-12-25", tz='UTC')
]
the_open = self.calendar.schedule.loc[next_day].market_open
for session_label in holidays_2012:
self.assertNotIn(session_label, self.calendar.all_sessions)
localized_open = the_open.tz_localize("UTC").tz_convert(
self.calendar.tz
)
# early closes we expect:
early_closes_2012 = [
pd.Timestamp("2012-07-03", tz='UTC'),
pd.Timestamp("2012-11-23", tz='UTC'),
pd.Timestamp("2012-12-24", tz='UTC')
]
self.assertEqual(
(open_date.year, open_date.month, open_date.day),
(localized_open.year, localized_open.month, localized_open.day)
)
for early_close_session_label in early_closes_2012:
self.assertIn(early_close_session_label,
self.calendar.early_closes)
self.assertEqual(
self.calendar.open_time.hour,
localized_open.hour
)
def test_special_holidays(self):
# 9/11
# Sept 11, 12, 13, 14 2001
self.assertNotIn(pd.Period("9/11/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/12/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/13/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/14/2001"), self.calendar.all_sessions)
# Hurricane Sandy
# Oct 29, 30 2012
self.assertNotIn(pd.Period("10/29/2012"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("10/30/2012"), self.calendar.all_sessions)
# various national days of mourning
# Gerald Ford - 1/2/2007
self.assertNotIn(pd.Period("1/2/2007"), self.calendar.all_sessions)
# Ronald Reagan - 6/11/2004
self.assertNotIn(pd.Period("6/11/2004"), self.calendar.all_sessions)
# Richard Nixon - 4/27/1994
self.assertNotIn(pd.Period("4/27/1994"), self.calendar.all_sessions)
def test_new_years(self):
"""
Check whether the TradingCalendar contains certain dates.
"""
# January 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 31
start_session = pd.Timestamp("2012-01-02", tz='UTC')
end_session = pd.Timestamp("2013-12-31", tz='UTC')
sessions = self.calendar.sessions_in_range(start_session, end_session)
day_after_new_years_sunday = pd.Timestamp("2012-01-02",
tz='UTC')
self.assertNotIn(day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Monday after is a holiday.
""".strip().format(day_after_new_years_sunday)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
# January 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
new_years_day = pd.Timestamp("2013-01-01", tz='UTC')
self.assertNotIn(new_years_day, sessions,
"""
If NYE falls during the week, e.g. {0}, it is a holiday.
""".strip().format(new_years_day)
)
first_trading_day_after_new_years = pd.Timestamp("2013-01-02",
tz='UTC')
self.assertIn(first_trading_day_after_new_years, sessions,
"""
If the day after NYE falls during the week, {0} \
is the first trading day.
""".strip().format(first_trading_day_after_new_years)
)
def test_thanksgiving(self):
"""
Check TradingCalendar Thanksgiving dates.
"""
# November 2005
# 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
start_session_label = pd.Timestamp('2005-01-01', tz='UTC')
end_session_label = pd.Timestamp('2012-12-31', tz='UTC')
sessions = self.calendar.sessions_in_range(start_session_label,
end_session_label)
thanksgiving_with_four_weeks = pd.Timestamp("2005-11-24", tz='UTC')
self.assertNotIn(thanksgiving_with_four_weeks, sessions,
"""
If Nov has 4 Thursdays, {0} Thanksgiving is the last Thursday.
""".strip().format(thanksgiving_with_four_weeks)
)
# November 2006
# 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
thanksgiving_with_five_weeks = pd.Timestamp("2006-11-23", tz='UTC')
self.assertNotIn(thanksgiving_with_five_weeks, sessions,
"""
If Nov has 5 Thursdays, {0} Thanksgiving is not the last week.
""".strip().format(thanksgiving_with_five_weeks)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
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):
# 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_open = Timestamp('11/23/2012 11:00AM', tz='EST')
fourth_friday = Timestamp('11/23/2012 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fourth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fourth_friday))
# 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_open = Timestamp('11/29/2013 11:00AM', tz='EST')
fifth_friday = Timestamp('11/29/2013 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fifth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fifth_friday))
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.
"""
# 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 = Timestamp('7/3/2002 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2002 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2002 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertFalse(self.calendar.is_open_on_minute(friday_after))
# 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 = Timestamp('7/3/2013 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2013 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2013 3:00PM', tz='EST')
self.assertFalse(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertTrue(self.calendar.is_open_on_minute(friday_after))
self.assertEqual(
self.calendar.open_time.minute,
localized_open.minute
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -2,7 +2,8 @@ from zipline.errors import (
InvalidCalendarName,
CalendarNameCollision,
)
from zipline.utils.calendars.exchange_calendar_cfe import CFEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_ice import ICEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_cme import CMEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_bmf import BMFExchangeCalendar
@@ -37,6 +38,10 @@ def get_calendar(name):
cal = LSEExchangeCalendar()
elif name == 'TSX':
cal = TSXExchangeCalendar()
elif name == "ICE":
cal = ICEExchangeCalendar()
elif name == "CFE":
cal = CFEExchangeCalendar()
else:
raise InvalidCalendarName(calendar_name=name)
@@ -10,8 +10,8 @@ from pytz import timezone
from .trading_calendar import (
TradingCalendar,
FRIDAY
)
FRIDAY,
HolidayCalendar)
# Universal Confraternization (new years day)
ConfUniversal = Holiday(
@@ -196,23 +196,47 @@ class BMFExchangeCalendar(TradingCalendar):
- New Year's Eve (December 31)
"""
name = "BMF"
tz = timezone('America/Sao_Paulo')
open_time = time(10, 1)
close_time = time(17)
@property
def name(self):
return "BMF"
# Does the market open or close on a different calendar day, compared to
# the calendar day assigned by the exchange to this session?
open_offset = 0
close_offset = 0
@property
def tz(self):
return timezone("America/Sao_Paolo")
holidays_calendar = BMFHolidayCalendar()
special_opens_calendars = [
(time(13, 1), BMFLateOpenCalendar()),
]
special_closes_calendars = ()
@property
def open_time(self):
return time(10, 1)
holidays_adhoc = ()
@property
def close_time(self):
return time(16)
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def regular_holidays(self):
return HolidayCalendar([
ConfUniversal,
AniversarioSaoPaulo,
CarnavalSegunda,
CarnavalTerca,
SextaPaixao,
CorpusChristi,
Tiradentes,
DiaTrabalho,
Constitucionalista,
Independencia,
Aparecida,
Finados,
ProclamacaoRepublica,
ConscienciaNegra,
VesperaNatal,
Natal,
AnoNovo,
AnoNovoSabado,
])
@property
def special_opens(self):
return [
(time(13, 1), HolidayCalendar([QuartaCinzas]))
]
@@ -0,0 +1,71 @@
from datetime import time
from pandas.tseries.holiday import (
USPresidentsDay,
USLaborDay,
USThanksgivingDay,
GoodFriday
)
from pytz import timezone
from zipline.utils.calendars import TradingCalendar
from zipline.utils.calendars.trading_calendar import HolidayCalendar
from zipline.utils.calendars.us_holidays import (
USMartinLutherKingJrAfter1998,
USMemorialDay,
USBlackFridayInOrAfter1993,
USNewYearsDay,
USIndependenceDay,
Christmas
)
class CFEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for the CBOE Futures Exchange (CFE).
http://cfe.cboe.com/aboutcfe/expirationcalendar.aspx
Open Time: 8:30am, America/Chicago
Close Time: 3:15pm, America/Chicago
(We are ignoring extended trading hours for now)
"""
@property
def name(self):
return "CFE"
@property
def tz(self):
return timezone("America/Chicago")
@property
def open_time(self):
return time(8, 31)
@property
def close_time(self):
return time(15, 15)
@property
def regular_holidays(self):
return HolidayCalendar([
USNewYearsDay,
USMartinLutherKingJrAfter1998,
USPresidentsDay,
GoodFriday,
USIndependenceDay,
USMemorialDay,
USLaborDay,
USThanksgivingDay,
Christmas
])
@property
def special_closes(self):
return [(
time(12, 15),
HolidayCalendar([
USBlackFridayInOrAfter1993,
])
)]
+68 -103
View File
@@ -14,71 +14,29 @@
# limitations under the License.
from datetime import time
from itertools import chain
from pandas.tseries.holiday import AbstractHolidayCalendar
from pandas.tseries.holiday import (
USPresidentsDay,
USLaborDay,
USThanksgivingDay,
GoodFriday
)
from pytz import timezone
# Useful resources for making changes to this file:
# http://www.cmegroup.com/tools-information/holiday-calendar.html
from .trading_calendar import TradingCalendar
from .trading_calendar import TradingCalendar, HolidayCalendar
from .us_holidays import (
USNewYearsDay,
Christmas,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
FridayAfterIndependenceDayExcept2013,
MonTuesThursBeforeIndependenceDay,
USBlackFridayInOrAfter1993,
September11Closings,
USNationalDaysofMourning
)
US_CENTRAL = timezone('America/Chicago')
CME_OPEN = time(17)
CME_CLOSE = time(16)
# The CME seems to have different holiday rules depending on the type
# of instrument. For example, http://www.cmegroup.com/tools-information/holiday-calendar/files/2016-4th-of-july-holiday-schedule.pdf # noqa
# shows that Equity, Interest Rate, FX, Energy, Metals & DME Products close at
# 1200 CT on July 4, 2016, while Grain, Oilseed & MGEX Products and Livestock,
# Dairy & Lumber products are completely closed.
# For now, we will treat the CME as having a single calendar, and just go with
# the most conservative hours - and treat July 4 as an early close at noon.
CME_STANDARD_EARLY_CLOSE = time(12)
# Does the market open or close on a different calendar day, compared to the
# calendar day assigned by the exchange to this session?
CME_OPEN_OFFSET = -1
CME_CLOSE_OFFSET = -0
class CMEHolidayCalendar(AbstractHolidayCalendar):
"""
Non-trading days for the CME.
See CMEExchangeCalendar for full description.
"""
rules = [
USNewYearsDay,
Christmas,
]
class CMEEarlyCloseCalendar(AbstractHolidayCalendar):
"""
Regular early close calendar for NYSE
"""
rules = [
MonTuesThursBeforeIndependenceDay,
FridayAfterIndependenceDayExcept2013,
USBlackFridayInOrAfter1993,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
]
USNationalDaysofMourning,
USMartinLutherKingJrAfter1998,
USMemorialDay,
USIndependenceDay)
class CMEExchangeCalendar(TradingCalendar):
@@ -89,58 +47,65 @@ class CMEExchangeCalendar(TradingCalendar):
Close Time: 5:00 PM, America/Chicago
Regularly-Observed Holidays:
- New Years Day (observed on monday when Jan 1 is a Sunday)
- Martin Luther King Jr. Day (3rd Monday in January, only after 1998)
- Washington's Birthday (aka President's Day, 3rd Monday in February)
- Good Friday (two days before Easter Sunday)
- Memorial Day (last Monday in May)
- Independence Day (observed on the nearest weekday to July 4th)
- Labor Day (first Monday in September)
- Thanksgiving (fourth Thursday in November)
- Christmas (observed on nearest weekday to December 25)
NOTE: For the following US Federal Holidays, part of the CME is closed
(Foreign Exchange, Interest Rates) but Commodities, GSCI, Weather & Real
Estate is open. Thus, we don't treat these as holidays.
- Columbus Day
- Veterans Day
Regularly-Observed Early Closes:
- Christmas Eve (except on Fridays, when the exchange is closed entirely)
- Day After Thanksgiving (aka Black Friday, observed from 1992 onward)
Additional Irregularities:
- Closed from 9/11/2001 to 9/16/2001 due to terrorist attacks in NYC.
- Closed on 10/29/2012 and 10/30/2012 due to Hurricane Sandy.
- Closed on 4/27/1994 due to Richard Nixon's death.
- Closed on 6/11/2004 due to Ronald Reagan's death.
- Closed on 1/2/2007 due to Gerald Ford's death.
- Closed at 1:00 PM on Wednesday, July 3rd, 2013
- Closed at 1:00 PM on Friday, December 31, 1999
- Closed at 1:00 PM on Friday, December 26, 1997
- Closed at 1:00 PM on Friday, December 26, 2003
NOTE: The exchange was **not** closed early on Friday December 26, 2008,
nor was it closed on Friday December 26, 2014. The next Thursday Christmas
will be in 2025. If someone is still maintaining this code in 2025, then
we've done alright...and we should check if it's a half day.
- New Years Day
- Good Friday
- Christmas
"""
@property
def name(self):
return "CME"
name = "CME"
tz = US_CENTRAL
open_time = CME_OPEN
close_time = CME_CLOSE
open_offset = CME_OPEN_OFFSET
close_offset = CME_CLOSE_OFFSET
@property
def tz(self):
return timezone('America/Chicago')
holidays_calendar = CMEHolidayCalendar()
special_opens_calendars = ()
special_closes_calendars = []
@property
def open_time(self):
return time(17, 1)
holidays_adhoc = list(chain(
September11Closings,
USNationalDaysofMourning,
))
@property
def close_time(self):
return time(17)
special_opens_adhoc = ()
special_closes_adhoc = []
@property
def open_offset(self):
return -1
@property
def regular_holidays(self):
# The CME has different holiday rules depending on the type of
# instrument. For example, http://www.cmegroup.com/tools-information/holiday-calendar/files/2016-4th-of-july-holiday-schedule.pdf # noqa
# shows that Equity, Interest Rate, FX, Energy, Metals & DME Products
# close at 1200 CT on July 4, 2016, while Grain, Oilseed & MGEX
# Products and Livestock, Dairy & Lumber products are completely
# closed.
# For now, we will treat the CME as having a single calendar, and just
# go with the most conservative hours - and treat July 4 as an early
# close at noon.
return HolidayCalendar([
USNewYearsDay,
GoodFriday,
Christmas,
])
@property
def adhoc_holidays(self):
return USNationalDaysofMourning
@property
def special_closes(self):
return [(
time(12),
HolidayCalendar([
USMartinLutherKingJrAfter1998,
USPresidentsDay,
USMemorialDay,
USLaborDay,
USIndependenceDay,
USThanksgivingDay,
USBlackFridayInOrAfter1993,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
])
)]
@@ -0,0 +1,82 @@
from datetime import time
from itertools import chain
from pandas.tseries.holiday import (
GoodFriday,
USPresidentsDay,
USLaborDay,
USThanksgivingDay
)
from pandas.tslib import Timestamp
from pytz import timezone
from zipline.utils.calendars import TradingCalendar
from zipline.utils.calendars.trading_calendar import HolidayCalendar
from zipline.utils.calendars.us_holidays import (
USNewYearsDay,
Christmas,
USMartinLutherKingJrAfter1998,
USMemorialDay,
USIndependenceDay,
USNationalDaysofMourning)
class ICEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for ICE US.
Open Time: 8pm, US/Eastern
Close Time: 6pm, US/Eastern
https://www.theice.com/publicdocs/futures_us/ICE_Futures_US_Regular_Trading_Hours.pdf # noqa
"""
@property
def name(self):
return "ICE"
@property
def tz(self):
return timezone("US/Eastern")
@property
def open_time(self):
return time(20, 1)
@property
def close_time(self):
return time(18)
@property
def open_offset(self):
return -1
@property
def special_closes(self):
return [
(time(13), HolidayCalendar([
USMartinLutherKingJrAfter1998,
USPresidentsDay,
USMemorialDay,
USIndependenceDay,
USLaborDay,
USThanksgivingDay
]))
]
@property
def adhoc_holidays(self):
return list(chain(
USNationalDaysofMourning,
# ICE was only closed on the first day of the Hurricane Sandy
# closings (was not closed on 2012-10-30)
[Timestamp('2012-10-29', tz='UTC')]
))
@property
def regular_holidays(self):
# https://www.theice.com/publicdocs/futures_us/exchange_notices/NewExNot2016Holidays.pdf # noqa
return HolidayCalendar([
USNewYearsDay,
GoodFriday,
Christmas
])
@@ -1,6 +1,5 @@
from datetime import time
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
Holiday,
DateOffset,
MO,
@@ -14,7 +13,7 @@ from .trading_calendar import (
TradingCalendar,
MONDAY,
TUESDAY,
)
HolidayCalendar)
# New Year's Day
LSENewYearsDay = Holiday(
@@ -73,26 +72,6 @@ WeekendBoxingDay = Holiday(
)
class LSEHolidayCalendar(AbstractHolidayCalendar):
"""
Non-trading days for the LSE.
See NYSEExchangeCalendar for full description.
"""
rules = [
LSENewYearsDay,
GoodFriday,
EasterMonday,
MayBank,
SpringBank,
SummerBank,
Christmas,
WeekendChristmas,
BoxingDay,
WeekendBoxingDay,
]
class LSEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for the London Stock Exchange
@@ -113,18 +92,33 @@ class LSEExchangeCalendar(TradingCalendar):
- Dec. 28th (if Boxing Day is on a weekend)
"""
name = 'LSE'
tz = timezone('Europe/London')
open_time = time(8, 1)
close_time = time(16, 30)
open_offset = 0
close_offset = 0
@property
def name(self):
return "LSE"
holidays_calendar = LSEHolidayCalendar()
special_opens_calendars = ()
special_closes_calendars = ()
@property
def tz(self):
return timezone('Europe/London')
holidays_adhoc = ()
@property
def open_time(self):
return time(8, 1)
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def close_time(self):
return time(16, 30)
@property
def regular_holidays(self):
return HolidayCalendar([
LSENewYearsDay,
GoodFriday,
EasterMonday,
MayBank,
SpringBank,
SummerBank,
Christmas,
WeekendChristmas,
BoxingDay,
WeekendBoxingDay
])
@@ -17,7 +17,6 @@ from datetime import time
from itertools import chain
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
GoodFriday,
USLaborDay,
USPresidentsDay,
@@ -25,8 +24,7 @@ from pandas.tseries.holiday import(
)
from pytz import timezone
from .trading_calendar import TradingCalendar
from .trading_calendar import TradingCalendar, HolidayCalendar
from .us_holidays import (
USNewYearsDay,
USMartinLutherKingJrAfter1998,
@@ -48,58 +46,6 @@ from .us_holidays import (
# http://www.nyse.com/pdfs/closings.pdf
# http://www.stevemorse.org/jcal/whendid.html
US_EASTERN = timezone('US/Eastern')
NYSE_OPEN = time(9, 31)
NYSE_CLOSE = time(16)
NYSE_STANDARD_EARLY_CLOSE = time(13)
# Whether market opens or closes on a different calendar day, compared to the
# calendar day assigned by the exchange to this session.
NYSE_OPEN_OFFSET = 0
NYSE_CLOSE_OFFSET = 0
class NYSEHolidayCalendar(AbstractHolidayCalendar):
"""
Non-trading days for the NYSE.
See NYSEExchangeCalendar for full description.
"""
rules = [
USNewYearsDay,
USMartinLutherKingJrAfter1998,
USPresidentsDay,
GoodFriday,
USMemorialDay,
USIndependenceDay,
USLaborDay,
USThanksgivingDay,
USIndependenceDay,
Christmas,
]
class NYSE2PMCloseCalendar(AbstractHolidayCalendar):
"""
Holiday Calendar for 2PM closes for NYSE
"""
rules = [
ChristmasEveBefore1993,
USBlackFridayBefore1993,
]
class NYSEEarlyCloseCalendar(AbstractHolidayCalendar):
"""
Regular early close calendar for NYSE
"""
rules = [
MonTuesThursBeforeIndependenceDay,
FridayAfterIndependenceDayExcept2013,
USBlackFridayInOrAfter1993,
ChristmasEveInOrAfter1993,
]
class NYSEExchangeCalendar(TradingCalendar):
"""
@@ -149,30 +95,68 @@ class NYSEExchangeCalendar(TradingCalendar):
we've done alright...and we should check if it's a half day.
"""
name = "NYSE"
tz = US_EASTERN
open_time = NYSE_OPEN
close_time = NYSE_CLOSE
open_offset = NYSE_OPEN_OFFSET
close_offset = NYSE_CLOSE_OFFSET
regular_early_close = time(13)
holidays_calendar = NYSEHolidayCalendar()
special_opens_calendars = ()
special_closes_calendars = [
(NYSE_STANDARD_EARLY_CLOSE, NYSEEarlyCloseCalendar()),
(time(14), NYSE2PMCloseCalendar()),
]
@property
def name(self):
return "NYSE"
holidays_adhoc = list(chain(
September11Closings,
HurricaneSandyClosings,
USNationalDaysofMourning,
))
@property
def tz(self):
return timezone('US/Eastern')
special_opens_adhoc = ()
special_closes_adhoc = [
(NYSE_STANDARD_EARLY_CLOSE, ('1997-12-26',
'1999-12-31',
'2003-12-26',
'2013-07-03')),
]
@property
def open_time(self):
return time(9, 31)
@property
def close_time(self):
return time(16)
@property
def regular_holidays(self):
return HolidayCalendar([
USNewYearsDay,
USMartinLutherKingJrAfter1998,
USPresidentsDay,
GoodFriday,
USMemorialDay,
USIndependenceDay,
USLaborDay,
USThanksgivingDay,
Christmas,
])
@property
def adhoc_holidays(self):
return list(chain(
September11Closings,
HurricaneSandyClosings,
USNationalDaysofMourning,
))
@property
def special_closes(self):
return [
(self.regular_early_close, HolidayCalendar([
MonTuesThursBeforeIndependenceDay,
FridayAfterIndependenceDayExcept2013,
USBlackFridayInOrAfter1993,
ChristmasEveInOrAfter1993
])),
(time(14), HolidayCalendar([
ChristmasEveBefore1993,
USBlackFridayBefore1993,
])),
]
@property
def special_closes_adhoc(self):
return [
(self.regular_early_close, [
'1997-12-26',
'1999-12-31',
'2003-12-26',
'2013-07-03'
])
]
@@ -1,20 +1,20 @@
from datetime import time
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
Holiday,
DateOffset,
MO,
weekend_to_monday,
GoodFriday,
GoodFriday
)
from pytz import timezone
from zipline.utils.calendars.trading_calendar import TradingCalendar
from zipline.utils.calendars.trading_calendar import TradingCalendar, \
HolidayCalendar
from zipline.utils.calendars.us_holidays import Christmas
from zipline.utils.calendars.exchange_calendar_lse import (
WeekendChristmas,
BoxingDay,
WeekendBoxingDay,
WeekendBoxingDay
)
# New Year's Day
@@ -69,28 +69,6 @@ Thanksgiving = Holiday(
)
class TSXHolidayCalendar(AbstractHolidayCalendar):
"""
Non-trading days for the TSX.
See NYSEExchangeCalendar for full description.
"""
rules = [
TSXNewYearsDay,
FamilyDay,
GoodFriday,
VictoriaDay,
CanadaDay,
CivicHoliday,
LaborDay,
Thanksgiving,
Christmas,
WeekendChristmas,
BoxingDay,
WeekendBoxingDay,
]
class TSXExchangeCalendar(TradingCalendar):
"""
Exchange calendar for the Toronto Stock Exchange
@@ -113,18 +91,35 @@ class TSXExchangeCalendar(TradingCalendar):
- Dec. 28th (if Boxing Day is on a weekend)
"""
name = 'TSX'
tz = timezone('Canada/Atlantic')
open_time = time(9, 31)
close_time = time(16)
open_offset = 0
close_offset = 0
@property
def name(self):
return "TSX"
holidays_calendar = TSXHolidayCalendar()
special_opens_calendars = ()
special_closes_calendars = ()
@property
def tz(self):
return timezone('Canada/Atlantic')
holidays_adhoc = ()
@property
def open_time(self):
return time(9, 31)
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def close_time(self):
return time(16)
@property
def regular_holidays(self):
return HolidayCalendar([
TSXNewYearsDay,
FamilyDay,
GoodFriday,
VictoriaDay,
CanadaDay,
CivicHoliday,
LaborDay,
Thanksgiving,
Christmas,
WeekendChristmas,
BoxingDay,
WeekendBoxingDay
])
+109 -25
View File
@@ -12,7 +12,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABCMeta
from abc import ABCMeta, abstractproperty
from pandas.tseries.holiday import AbstractHolidayCalendar
from six import with_metaclass
from numpy import searchsorted
import numpy as np
@@ -24,13 +26,12 @@ from pandas import (
DateOffset
)
from pandas.tseries.offsets import CustomBusinessDay
from zipline.utils.calendars._calendar_helpers import (
next_divider_idx,
previous_divider_idx,
is_open
)
from zipline.utils.memoize import remember_last
from zipline.utils.memoize import remember_last, lazyval
start_default = pd.Timestamp('1990-01-01', tz='UTC')
end_base = pd.Timestamp('today', tz='UTC')
@@ -57,28 +58,19 @@ class TradingCalendar(with_metaclass(ABCMeta)):
For each session, we store the open and close time in UTC time.
"""
def __init__(self, start=start_default, end=end_default):
open_offset = self.open_offset
close_offset = self.close_offset
# Define those days on which the exchange is usually open.
self.day = CustomBusinessDay(
holidays=self.holidays_adhoc,
calendar=self.holidays_calendar,
)
# Midnight in UTC for each trading day.
_all_days = date_range(start, end, freq=self.day, tz='UTC')
# `DatetimeIndex`s of standard opens/closes for each day.
self._opens = days_at_time(_all_days, self.open_time, self.tz,
open_offset)
self.open_offset)
self._closes = days_at_time(
_all_days, self.close_time, self.tz, close_offset
_all_days, self.close_time, self.tz, self.close_offset
)
# `DatetimeIndex`s of nonstandard opens/closes
_special_opens = self._special_opens(start, end)
_special_closes = self._special_closes(start, end)
_special_opens = self._calculate_special_opens(start, end)
_special_closes = self._calculate_special_closes(start, end)
# Overwrite the special opens and closes on top of the standard ones.
_overwrite_special_dates(_all_days, self._opens, _special_opens)
@@ -113,7 +105,95 @@ class TradingCalendar(with_metaclass(ABCMeta)):
_special_closes.map(self.minute_to_session_label)
)
@lazyval
def day(self):
return CustomBusinessDay(
holidays=self.adhoc_holidays,
calendar=self.regular_holidays,
)
@abstractproperty
def name(self):
raise NotImplementedError()
@abstractproperty
def tz(self):
raise NotImplementedError()
@abstractproperty
def open_time(self):
raise NotImplementedError()
@abstractproperty
def close_time(self):
raise NotImplementedError()
@property
def open_offset(self):
return 0
@property
def close_offset(self):
return 0
@property
def regular_holidays(self):
"""
Returns
-------
pd.AbstractHolidayCalendar: a calendar containing the regular holidays
for this calendar
"""
return None
@property
def adhoc_holidays(self):
return []
@property
def special_opens(self):
"""
A list of special open times and corresponding HolidayCalendars.
Returns
-------
list: List of (time, AbstractHolidayCalendar) tuples
"""
return []
@property
def special_opens_adhoc(self):
"""
Returns
-------
list: List of (time, DatetimeIndex) tuples that represent special
closes that cannot be codified into rules.
"""
return []
@property
def special_closes(self):
"""
A list of special close times and corresponding HolidayCalendars.
Returns
-------
list: List of (time, AbstractHolidayCalendar) tuples
"""
return []
@property
def special_closes_adhoc(self):
"""
Returns
-------
list: List of (time, DatetimeIndex) tuples that represent special
closes that cannot be codified into rules.
"""
return []
# -----
def opens(self):
return self.schedule.market_open
@@ -176,7 +256,7 @@ class TradingCalendar(with_metaclass(ABCMeta)):
The UTC timestamp of the next open.
"""
idx = next_divider_idx(self.market_opens_nanos, dt.value)
return self.schedule.market_open[idx].tz_localize('UTC')
return pd.Timestamp(self.market_opens_nanos[idx], tz='UTC')
def next_close(self, dt):
"""
@@ -193,7 +273,7 @@ class TradingCalendar(with_metaclass(ABCMeta)):
The UTC timestamp of the next close.
"""
idx = next_divider_idx(self.market_closes_nanos, dt.value)
return self.schedule.market_close[idx].tz_localize('UTC')
return pd.Timestamp(self.market_closes_nanos[idx], tz='UTC')
def previous_open(self, dt):
"""
@@ -210,7 +290,7 @@ class TradingCalendar(with_metaclass(ABCMeta)):
The UTC imestamp of the previous open.
"""
idx = previous_divider_idx(self.market_opens_nanos, dt.value)
return self.schedule.market_open[idx].tz_localize('UTC')
return pd.Timestamp(self.market_opens_nanos[idx], tz='UTC')
def previous_close(self, dt):
"""
@@ -227,7 +307,7 @@ class TradingCalendar(with_metaclass(ABCMeta)):
The UTC timestamp of the previous close.
"""
idx = previous_divider_idx(self.market_closes_nanos, dt.value)
return self.schedule.market_close[idx].tz_localize('UTC')
return pd.Timestamp(self.market_closes_nanos[idx], tz='UTC')
def next_minute(self, dt):
"""
@@ -643,17 +723,17 @@ class TradingCalendar(with_metaclass(ABCMeta)):
)
return _dates[(_dates >= start_date) & (_dates <= end_date)]
def _special_opens(self, start, end):
def _calculate_special_opens(self, start, end):
return self._special_dates(
self.special_opens_calendars,
self.special_opens,
self.special_opens_adhoc,
start,
end,
)
def _special_closes(self, start, end):
def _calculate_special_closes(self, start, end):
return self._special_dates(
self.special_closes_calendars,
self.special_closes,
self.special_closes_adhoc,
start,
end,
@@ -677,7 +757,6 @@ def days_at_time(days, t, tz, day_offset=0):
day_offset : int
The number of days we want to offset @days by
"""
# Offset days without tz to avoid timezone issues.
days = DatetimeIndex(days).tz_localize(None)
days_offset = days + DateOffset(days=day_offset)
@@ -735,3 +814,8 @@ def _overwrite_special_dates(midnight_utcs,
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
opens_or_closes.values[indexer] = special_opens_or_closes.values
class HolidayCalendar(AbstractHolidayCalendar):
def __init__(self, rules):
super(HolidayCalendar, self).__init__(self, rules=rules)