mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-27 11:20:45 +08:00
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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user