MAINT: Removes static calendar from schedule_function rules

This commit is contained in:
jfkirk
2016-06-08 13:34:19 -04:00
committed by Jean Bredeche
parent 591ae02a02
commit 31f9f06c9a
4 changed files with 95 additions and 50 deletions
+28 -8
View File
@@ -239,6 +239,10 @@ class RuleTestCase(TestCase):
cls.after_open = AfterOpen(hours=1, minutes=5)
cls.class_ = None # Mark that this is the base class.
cal = get_calendar('NYSE')
cls.before_close.cal = cal
cls.after_open.cal = cal
def test_completeness(self):
"""
Tests that all rules are being tested.
@@ -316,7 +320,10 @@ class TestStatelessRules(RuleTestCase):
@subtest(minutes_for_days(), 'ms')
def test_NotHalfDay(self, ms):
should_trigger = NotHalfDay().should_trigger
cal = get_calendar('NYSE')
rule = NotHalfDay()
rule.cal = cal
should_trigger = rule.should_trigger
self.assertTrue(should_trigger(FULL_DAY))
self.assertFalse(should_trigger(HALF_DAY))
@@ -325,15 +332,19 @@ class TestStatelessRules(RuleTestCase):
Test that we don't blow up when trying to call week_start's
should_trigger on the first day of a trading environment.
"""
cal = get_calendar('NYSE')
rule = NthTradingDayOfWeek(0)
rule.cal = cal
self.assertTrue(
NthTradingDayOfWeek(0).should_trigger(
self.nyse_cal.all_trading_days[0]
)
rule.should_trigger(self.nyse_cal.all_trading_days[0])
)
@subtest(param_range(MAX_WEEK_RANGE), 'n')
def test_NthTradingDayOfWeek(self, n):
should_trigger = NthTradingDayOfWeek(n).should_trigger
cal = get_calendar('NYSE')
rule = NthTradingDayOfWeek(n)
rule.cal = cal
should_trigger = rule.should_trigger
prev_day = self.sept_week[0].date()
n_tdays = 0
for m in self.sept_week:
@@ -348,7 +359,10 @@ class TestStatelessRules(RuleTestCase):
@subtest(param_range(MAX_WEEK_RANGE), 'n')
def test_NDaysBeforeLastTradingDayOfWeek(self, n):
should_trigger = NDaysBeforeLastTradingDayOfWeek(n).should_trigger
cal = get_calendar('NYSE')
rule = NDaysBeforeLastTradingDayOfWeek(n)
rule.cal = cal
should_trigger = rule.should_trigger
for m in self.sept_week:
if should_trigger(m):
n_tdays = 0
@@ -470,7 +484,10 @@ class TestStatelessRules(RuleTestCase):
@subtest(param_range(MAX_MONTH_RANGE), 'n')
def test_NthTradingDayOfMonth(self, n):
should_trigger = NthTradingDayOfMonth(n).should_trigger
cal = get_calendar('NYSE')
rule = NthTradingDayOfMonth(n)
rule.cal = cal
should_trigger = rule.should_trigger
for n_tdays, d in enumerate(self.sept_days):
for m in self.nyse_cal.trading_minutes_for_day(d):
if should_trigger(m):
@@ -480,7 +497,10 @@ class TestStatelessRules(RuleTestCase):
@subtest(param_range(MAX_MONTH_RANGE), 'n')
def test_NDaysBeforeLastTradingDayOfMonth(self, n):
should_trigger = NDaysBeforeLastTradingDayOfMonth(n).should_trigger
cal = get_calendar('NYSE')
rule = NDaysBeforeLastTradingDayOfMonth(n)
rule.cal = cal
should_trigger = rule.should_trigger
for n_days_before, d in enumerate(reversed(self.sept_days)):
for m in self.nyse_cal.trading_minutes_for_day(d):
if should_trigger(m):
+21 -4
View File
@@ -53,8 +53,12 @@ from zipline.errors import (
UnsupportedDatetimeFormat,
UnsupportedOrderParameters,
UnsupportedSlippageModel,
CannotOrderDelistedAsset, UnsupportedCancelPolicy, SetCancelPolicyPostInit,
OrderInBeforeTradingStart)
CannotOrderDelistedAsset,
UnsupportedCancelPolicy,
SetCancelPolicyPostInit,
OrderInBeforeTradingStart,
ScheduleFunctionWithoutCalendar,
)
from zipline.finance.trading import TradingEnvironment
from zipline.finance.blotter import Blotter
from zipline.finance.commission import PerShare, CommissionModel
@@ -94,7 +98,10 @@ from zipline.utils.api_support import (
from zipline.utils.input_validation import ensure_upper_case, error_keywords
from zipline.utils.cache import CachedObject, Expired
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import (
default_nyse_schedule,
ExchangeTradingSchedule,
)
import zipline.utils.events
from zipline.utils.events import (
EventManager,
@@ -981,8 +988,18 @@ class TradingAlgorithm(object):
# If we are in daily mode the time_rule is ignored.
time_rules.every_minute())
# Check the type of the algorithm's schedule before pulling calendar
# Note that the ExchangeTradingSchedule is currently the only
# TradingSchedule class, so this is unlikely to be hit
# TODO The calendar should be a required arg for schedule_function
if not isinstance(self.trading_schedule, ExchangeTradingSchedule):
raise ScheduleFunctionWithoutCalendar(
schedule=self.trading_schedule
)
cal = self.trading_schedule._exchange_calendar
self.add_event(
make_eventrule(date_rule, time_rule, half_days),
make_eventrule(date_rule, time_rule, cal, half_days),
func,
)
+12
View File
@@ -654,3 +654,15 @@ class CalendarNameCollision(ZiplineError):
msg = (
"A calendar with the name {calendar_name} is already registered."
)
class ScheduleFunctionWithoutCalendar(ZiplineError):
"""
Raised when schedule_function is called but there is not a calendar to be
used in the construction of an event rule.
"""
# TODO update message when new TradingSchedules are built
msg = (
"To use schedule_function, the TradingAlgorithm must be running on an "
"ExchangeTradingSchedule, rather than {schedule}."
)
+34 -38
View File
@@ -22,10 +22,7 @@ import pytz
from .context_tricks import nop_context
from zipline.utils.calendars import (
get_calendar,
normalize_date,
)
from zipline.utils.calendars import normalize_date
__all__ = [
'EventManager',
@@ -56,9 +53,6 @@ MAX_MONTH_RANGE = 26
MAX_WEEK_RANGE = 5
_static_nyse_cal = get_calendar('NYSE')
def naive_to_utc(ts):
"""
Converts a UTC tz-naive timestamp to a tz-aware timestamp.
@@ -347,10 +341,8 @@ class AfterOpen(StatelessRule):
def calculate_dates(self, dt):
# given a dt, find that day's open and period end (open + offset)
self._period_start, self._period_close = \
_static_nyse_cal.open_and_close(dt)
self._period_end = \
self._period_start + self.offset - self._one_minute
self._period_start, self._period_close = self.cal.open_and_close(dt)
self._period_end = self._period_start + self.offset - self._one_minute
def should_trigger(self, dt):
# There are two reasons why we might want to recalculate the dates.
@@ -392,9 +384,8 @@ class BeforeClose(StatelessRule):
def calculate_dates(self, dt):
# given a dt, find that day's close and period start (close - offset)
self._period_end = _static_nyse_cal.open_and_close(dt)[1]
self._period_start = \
self._period_end - self.offset
self._period_end = self.cal.open_and_close(dt)[1]
self._period_start = self._period_end - self.offset
self._period_close = self._period_end
def should_trigger(self, dt):
@@ -421,7 +412,7 @@ class NotHalfDay(StatelessRule):
A rule that only triggers when it is not a half day.
"""
def should_trigger(self, dt):
return normalize_date(dt) not in _static_nyse_cal.early_closes
return normalize_date(dt) not in self.cal.early_closes
class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
@@ -441,7 +432,7 @@ class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
def calculate_start_and_end(self, dt):
next_trading_day = _coerce_datetime(
_static_nyse_cal.add_trading_days(
self.cal.add_trading_days(
self.td_delta,
self.date_func(dt),
)
@@ -452,15 +443,13 @@ class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
while next_trading_day.isocalendar()[1] != dt.isocalendar()[1]:
dt += datetime.timedelta(days=7)
next_trading_day = _coerce_datetime(
env.add_trading_days(
self.cal.add_trading_days(
self.td_delta,
self.date_func(dt, env),
self.date_func(dt),
)
)
next_open, next_close = _static_nyse_cal.open_and_close(
next_trading_day
)
next_open, next_close = self.cal.open_and_close(next_trading_day)
self.next_date_start = next_open
self.next_date_end = next_close
self.next_midnight_timestamp = next_trading_day
@@ -491,9 +480,9 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
This is zero-indexed, n=0 is the first trading day of the week.
"""
@staticmethod
def get_first_trading_day_of_week(dt):
def get_first_trading_day_of_week(dt, cal):
prev = dt
dt = _static_nyse_cal.previous_trading_day(dt)
dt = cal.previous_trading_day(dt)
# If we're on the first trading day of the TradingEnvironment,
# calling previous_trading_day on it will return None, which
# will blow up when we try and call .date() on it. The first
@@ -504,14 +493,14 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
return prev
while dt.date().weekday() < prev.date().weekday():
prev = dt
dt = _static_nyse_cal.previous_trading_day(dt)
dt = cal.previous_trading_day(dt)
if dt is None:
return prev
if env.is_trading_day(prev):
if cal.is_trading_day(prev):
return prev.date()
else:
return env.next_trading_day(prev).date()
return cal.next_trading_day(prev).date()
date_func = get_first_trading_day_of_week
@@ -524,19 +513,19 @@ class NDaysBeforeLastTradingDayOfWeek(TradingDayOfWeekRule):
super(NDaysBeforeLastTradingDayOfWeek, self).__init__(-n)
@staticmethod
def get_last_trading_day_of_week(dt):
def get_last_trading_day_of_week(dt, cal):
prev = dt
dt = _static_nyse_cal.next_trading_day(dt)
dt = cal.next_trading_day(dt)
# Traverse forward until we hit a week border, then jump back to the
# previous trading day.
while dt.date().weekday() > prev.date().weekday():
prev = dt
dt = _static_nyse_cal.next_trading_day(dt)
dt = cal.next_trading_day(dt)
if env.is_trading_day(prev):
if cal.is_trading_day(prev):
return prev.date()
else:
return env.previous_trading_day(prev).date()
return cal.previous_trading_day(prev).date()
date_func = get_last_trading_day_of_week
@@ -564,7 +553,7 @@ class NthTradingDayOfMonth(StatelessRule):
if not self.td_delta:
self.day = self.get_first_trading_day_of_month(dt)
else:
self.day = _static_nyse_cal.add_trading_days(
self.day = self.cal.add_trading_days(
self.td_delta,
self.get_first_trading_day_of_month(dt),
).date()
@@ -575,8 +564,8 @@ class NthTradingDayOfMonth(StatelessRule):
self.month = dt.month
dt = dt.replace(day=1)
self.first_day = (dt if _static_nyse_cal.is_open_on_day(dt)
else _static_nyse_cal.next_trading_day(dt)).date()
self.first_day = (dt if self.cal.is_open_on_day(dt)
else self.cal.next_trading_day(dt)).date()
return self.first_day
@@ -602,7 +591,7 @@ class NDaysBeforeLastTradingDayOfMonth(StatelessRule):
if not self.td_delta:
self.day = self.get_last_trading_day_of_month(dt)
else:
self.day = _static_nyse_cal.add_trading_days(
self.day = self.cal.add_trading_days(
self.td_delta,
self.get_last_trading_day_of_month(dt),
).date()
@@ -621,7 +610,7 @@ class NDaysBeforeLastTradingDayOfMonth(StatelessRule):
year = dt.year
month = dt.month + 1
self.last_day = _static_nyse_cal.previous_trading_day(
self.last_day = self.cal.previous_trading_day(
dt.replace(year=year, month=month, day=1)
).date()
return self.last_day
@@ -699,13 +688,20 @@ class time_rules(object):
every_minute = Always
def make_eventrule(date_rule, time_rule, half_days=True):
def make_eventrule(date_rule, time_rule, cal, half_days=True):
"""
Constructs an event rule from the factory api.
"""
# Insert the calendar in to the individual rules
date_rule.cal = cal
time_rule.cal = cal
if half_days:
inner_rule = date_rule & time_rule
else:
inner_rule = date_rule & time_rule & NotHalfDay()
nhd_rule = NotHalfDay()
nhd_rule.cal = cal
inner_rule = date_rule & time_rule & nhd_rule
return OncePerDay(rule=inner_rule)