mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-17 11:25:55 +08:00
MAINT: Removes static calendar from schedule_function rules
This commit is contained in:
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user