BUG: Fixes various bugs with the event manager:

- NotHalfDay only worked at midnight
- week_(start|end) were actually month_(start|end)
- Removes check_args from api.
- Default offset of 30mins for market_(open|close)
This commit is contained in:
Joe Jevnik
2014-10-09 14:01:56 -04:00
parent 1d3a8759bc
commit df234f516c
5 changed files with 62 additions and 55 deletions
+6 -2
View File
@@ -116,8 +116,12 @@ class TestMiscellaneousAPI(TestCase):
setup_logger(self)
sids = [1, 2]
self.sim_params = factory.create_simulation_parameters(num_days=2,
sids=sids)
self.sim_params = factory.create_simulation_parameters(
num_days=2,
sids=sids,
data_frequency='minute',
emission_rate='minute',
)
self.source = factory.create_minutely_trade_source(
sids,
trade_count=100,
+16 -12
View File
@@ -47,10 +47,10 @@ from zipline.utils.events import (
# A day known to be a half day.
HALF_DAY = datetime.date(year=2014, month=7, day=3)
HALF_DAY = datetime.datetime(year=2014, month=7, day=3)
# A day known to be a full day.
FULL_DAY = datetime.date(year=2014, month=9, day=24)
FULL_DAY = datetime.datetime(year=2014, month=9, day=24)
def param_range(*args):
@@ -66,21 +66,25 @@ class TestUtils(TestCase):
with self.assertRaises(ValueError):
f(None, {})
def test_build_offset_default(self):
default = object()
self.assertIs(default, _build_offset(None, {}, default))
def test_build_offset_both(self):
with self.assertRaises(ValueError):
_build_offset(datetime.timedelta(minutes=1), {'minutes': 1})
_build_offset(datetime.timedelta(minutes=1), {'minutes': 1}, None)
def test_build_offset_kwargs(self):
kwargs = {'minutes': 1}
self.assertEqual(
_build_offset(None, kwargs),
_build_offset(None, kwargs, None),
datetime.timedelta(**kwargs),
)
def test_build_offset_td(self):
td = datetime.timedelta(minutes=1)
self.assertEqual(
_build_offset(td, {}),
_build_offset(td, {}, None),
td,
)
@@ -155,7 +159,7 @@ class TestEventManager(TestCase):
for r in [CountingRule] * 5:
self.em.add_event(
Event(r(), lambda context, data: None, check_args=False)
Event(r(), lambda context, data: None)
)
self.em.handle_data(None, None, datetime.datetime.now())
@@ -270,18 +274,18 @@ class TestStatelessRules(RuleTestCase):
@parameterized.expand(param_range(5))
def test_NthTradingDayOfWeek(self, n):
should_trigger = NthTradingDayOfWeek(n).should_trigger
prev_day = None
prev_day = self.sept_week[0].date()
n_tdays = 0
for m in dropwhile(lambda n: not should_trigger(n), self.sept_week):
if prev_day < m.date():
n_tdays += 1
prev_day = m.date()
if should_trigger(m):
self.assertEqual(n_tdays, n)
else:
self.assertNotEqual(n_tdays, n)
if not prev_day or prev_day < m.date():
n_tdays += 1
prev_day = m.date()
@parameterized.expand(param_range(5))
def test_NDaysBeforeLastTradingDayOfWeek(self, n):
should_trigger = NDaysBeforeLastTradingDayOfWeek(n).should_trigger
@@ -290,7 +294,7 @@ class TestStatelessRules(RuleTestCase):
n_tdays = 0
date = m.to_datetime().date()
next_date = self.env.next_trading_day(date)
while next_date.day > date.day:
while next_date.weekday() > date.weekday():
date = next_date
next_date = self.env.next_trading_day(date)
n_tdays += 1
+7 -6
View File
@@ -34,6 +34,7 @@ from zipline.errors import (
UnsupportedCommissionModel,
UnsupportedOrderParameters,
UnsupportedSlippageModel,
IncompatibleScheduleFunctionDataFrequency,
)
from zipline.finance import trading
@@ -218,7 +219,6 @@ class TradingAlgorithm(object):
# We pass handle_data.__func__ to get the unbound method.
# We will explicitly pass the algorithm to bind it again.
self.handle_data.__func__,
check_args=False,
),
prepend=True,
)
@@ -512,12 +512,12 @@ class TradingAlgorithm(object):
def get_environment(self):
return self._environment
def add_event(self, rule=None, callback=None, check_args=True):
def add_event(self, rule=None, callback=None):
"""
Adds an event to the algorithm's EventManager.
"""
self.event_manager.add_event(
zipline.utils.events.Event(rule, callback, check_args=check_args),
zipline.utils.events.Event(rule, callback),
)
@api_method
@@ -525,11 +525,13 @@ class TradingAlgorithm(object):
func,
date_rule=None,
time_rule=None,
half_days=True,
check_args=False):
half_days=True):
"""
Schedules a function to be called with some timed rules.
"""
if self.sim_params.data_frequency != 'minute':
raise IncompatibleScheduleFunctionDataFrequency()
# Defaults to every day 30 minutes before close.
date_rule = date_rule or DateRuleFactory.every_day()
time_rule = time_rule or TimeRuleFactory.market_close(minutes=30)
@@ -537,7 +539,6 @@ class TradingAlgorithm(object):
self.add_event(
make_eventrule(date_rule, time_rule, half_days),
func,
check_args=check_args,
)
@api_method
+9
View File
@@ -170,3 +170,12 @@ class IncompatibleHistoryFrequency(ZiplineError):
Requested history at frequency '{frequency}' cannot be created with data
at frequency '{data_frequency}'.
""".strip()
class IncompatibleScheduleFunctionDataFrequency(ZiplineError):
"""
Raised when schedule function is used in daily mode.
"""
msg = """
schedule_function may only be used in minute mode.
"""
+24 -35
View File
@@ -21,7 +21,6 @@ import pandas as pd
import pytz
from zipline.finance.trading import TradingEnvironment
from zipline.utils.argcheck import verify_callable_argspec, Argument
__all__ = [
@@ -67,13 +66,13 @@ def ensure_utc(time, tz='UTC'):
return time.replace(tzinfo=pytz.utc)
def _build_offset(offset, kwargs):
def _build_offset(offset, kwargs, default):
"""
Builds the offset argument for event rules.
"""
if offset is None:
if not kwargs:
return datetime.timedelta() # An empty offset (+0).
return default # use the default.
else:
return datetime.timedelta(**kwargs)
elif kwargs:
@@ -143,24 +142,8 @@ class Event(namedtuple('Event', ['rule', 'callback'])):
with the current algorithm context, data, and datetime only when the rule
is triggered.
"""
def __new__(cls, rule=None, callback=None, check_args=True):
def __new__(cls, rule=None, callback=None):
callback = callback or (lambda *args, **kwargs: None)
if check_args:
# Check the callback provided.
verify_callable_argspec(
callback,
[Argument('context' if check_args else Argument.ignore),
Argument('data' if check_args else Argument.ignore)]
)
# Make sure that the rule's should_trigger is valid. This will
# catch potential errors much more quickly and give a more helpful
# error.
verify_callable_argspec(
getattr(rule, 'should_trigger'),
[Argument('dt')]
)
return super(cls, cls).__new__(cls, rule=rule, callback=callback)
def handle_data(self, context, data, dt):
@@ -279,7 +262,11 @@ class AfterOpen(StatelessRule):
>>> AfterOpen(minutes=30)
"""
def __init__(self, offset=None, **kwargs):
self.offset = _build_offset(offset, kwargs)
self.offset = _build_offset(
offset,
kwargs,
datetime.timedelta(), # Defaults to the first minute.
)
def should_trigger(self, dt):
return self.env.get_open_and_close(dt)[0] + self.offset <= dt
@@ -293,7 +280,11 @@ class BeforeClose(StatelessRule):
>>> BeforeClose(minutes=30)
"""
def __init__(self, offset=None, **kwargs):
self.offset = _build_offset(offset, kwargs)
self.offset = _build_offset(
offset,
kwargs,
datetime.timedelta(minutes=1), # Defaults to the last minute.
)
def should_trigger(self, dt):
return self.env.get_open_and_close(dt)[1] - self.offset < dt
@@ -304,7 +295,7 @@ class NotHalfDay(StatelessRule):
A rule that only triggers when it is not a half day.
"""
def should_trigger(self, dt):
return dt not in self.env.early_closes
return dt.date() not in self.env.early_closes
class NthTradingDayOfWeek(StatelessRule):
@@ -326,9 +317,7 @@ class NthTradingDayOfWeek(StatelessRule):
def get_first_trading_day_of_week(self, dt):
prev = dt
dt = self.env.previous_trading_day(dt)
# Backtrack until we hit a week border, then jump to the next trading
# day.
while dt.day < prev.day:
while dt.date().weekday() < prev.date().weekday():
prev = dt
dt = self.env.previous_trading_day(dt)
return prev.date()
@@ -355,7 +344,7 @@ class NDaysBeforeLastTradingDayOfWeek(StatelessRule):
dt = self.env.next_trading_day(dt)
# Traverse forward until we hit a week border, then jump back to the
# previous trading day.
while dt.day > prev.day:
while dt.date().weekday() > prev.date().weekday():
prev = dt
dt = self.env.next_trading_day(dt)
return prev.date()
@@ -482,20 +471,20 @@ class DateRuleFactory(object):
every_day = Always
@staticmethod
def month_start(offset=0):
return NthTradingDayOfMonth(n=offset)
def month_start(days_offset=0):
return NthTradingDayOfMonth(n=days_offset)
@staticmethod
def month_end(offset=0):
return NDaysBeforeLastTradingDayOfMonth(n=offset)
def month_end(days_offset=0):
return NDaysBeforeLastTradingDayOfMonth(n=days_offset)
@staticmethod
def week_start(offset=0):
return NthTradingDayOfWeek(n=offset)
def week_start(days_offset=0):
return NthTradingDayOfWeek(n=days_offset)
@staticmethod
def week_end(offset=0):
return NDaysBeforeLastTradingDayOfWeek(n=offset)
def week_end(days_offset=0):
return NDaysBeforeLastTradingDayOfWeek(n=days_offset)
class TimeRuleFactory(object):