mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-09 05:16:58 +08:00
ENH: Makes schedule_function work in daily mode.
This commit is contained in:
@@ -69,6 +69,7 @@ from zipline.test_algorithms import (
|
||||
record_variables,
|
||||
)
|
||||
|
||||
import zipline.utils.events
|
||||
from zipline.utils.test_utils import (
|
||||
assert_single_position,
|
||||
drain_zipline,
|
||||
@@ -224,6 +225,41 @@ class TestMiscellaneousAPI(TestCase):
|
||||
|
||||
self.assertEqual(algo.func_called, algo.days)
|
||||
|
||||
@parameterized.expand([
|
||||
('daily',),
|
||||
('minute'),
|
||||
])
|
||||
def test_schedule_funtion_rule_creation(self, mode):
|
||||
nop = lambda *args, **kwargs: None
|
||||
|
||||
self.sim_params.data_frequency = mode
|
||||
algo = TradingAlgorithm(
|
||||
initialize=nop, handle_data=nop, sim_params=self.sim_params,
|
||||
)
|
||||
|
||||
# Schedule something for NOT Always.
|
||||
algo.schedule_function(nop, time_rule=zipline.utils.events.Never())
|
||||
|
||||
event_rule = algo.event_manager._events[1].rule
|
||||
|
||||
self.assertIsInstance(event_rule, zipline.utils.events.OncePerDay)
|
||||
|
||||
inner_rule = event_rule.rule
|
||||
self.assertIsInstance(inner_rule, zipline.utils.events.ComposedRule)
|
||||
|
||||
first = inner_rule.first
|
||||
second = inner_rule.second
|
||||
composer = inner_rule.composer
|
||||
|
||||
self.assertIsInstance(first, zipline.utils.events.Always)
|
||||
|
||||
if mode == 'daily':
|
||||
self.assertIsInstance(second, zipline.utils.events.Always)
|
||||
else:
|
||||
self.assertIsInstance(second, zipline.utils.events.Never)
|
||||
|
||||
self.assertIs(composer, zipline.utils.events.ComposedRule.lazy_and)
|
||||
|
||||
|
||||
class TestTransformAlgorithm(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
+55
-47
@@ -13,16 +13,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import datetime
|
||||
import random
|
||||
from itertools import islice
|
||||
from six import iteritems
|
||||
import random
|
||||
from six.moves import range, map
|
||||
from nose_parameterized import parameterized
|
||||
from unittest import TestCase
|
||||
|
||||
import numpy as np
|
||||
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.finance.trading import TradingEnvironment, with_environment
|
||||
import zipline.utils.events
|
||||
from zipline.utils.events import (
|
||||
EventRule,
|
||||
@@ -185,30 +185,34 @@ class TestEventRule(TestCase):
|
||||
super(Always, Always()).should_trigger('a')
|
||||
|
||||
|
||||
@with_environment()
|
||||
def minutes_for_days(env=None):
|
||||
"""
|
||||
500 randomly selected days.
|
||||
This is used to make sure our test coverage is unbaised towards any rules.
|
||||
We use a random sample because testing on all the trading days took
|
||||
around 180 seconds on my laptop, which is far too much for normal unit
|
||||
testing.
|
||||
|
||||
We manually set the seed so that this will be deterministic.
|
||||
Results of multiple runs were compared to make sure that this is actually
|
||||
true.
|
||||
|
||||
This returns a generator of tuples each wrapping a single generator.
|
||||
Iterating over this yeilds a single day, iterating over the day yields
|
||||
the minutes for that day.
|
||||
"""
|
||||
random.seed('deterministic')
|
||||
return ((env.market_minutes_for_day(random.choice(env.trading_days)),)
|
||||
for _ in range(500))
|
||||
|
||||
|
||||
class RuleTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.env = TradingEnvironment.instance()
|
||||
cls.class_ = None # Mark that this is the base class.
|
||||
|
||||
def setUp(self):
|
||||
# Select a random sample of 5 trading days
|
||||
self.trading_days = self._get_random_days(5)
|
||||
|
||||
def _get_random_days(self, n):
|
||||
"""
|
||||
Returns a random selection n trading days.
|
||||
"""
|
||||
index = random.sample(range(len(self.env.trading_days)), n)
|
||||
test_dts = (self.env.trading_days[i] for i in index)
|
||||
return (self.env.market_minutes_for_day(dt) for dt in test_dts)
|
||||
|
||||
@property
|
||||
def minutes(self):
|
||||
for d in self.trading_days:
|
||||
for m in d:
|
||||
yield m.to_datetime()
|
||||
|
||||
def test_completeness(self):
|
||||
"""
|
||||
Tests that all rules are being tested.
|
||||
@@ -250,34 +254,37 @@ class TestStatelessRules(RuleTestCase):
|
||||
datetime.date(year=2014, month=9, day=26),
|
||||
)
|
||||
|
||||
def test_Always(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_Always(self, ms):
|
||||
should_trigger = Always().should_trigger
|
||||
self.assertTrue(all(map(should_trigger, self.minutes)))
|
||||
self.assertTrue(all(map(should_trigger, ms)))
|
||||
|
||||
def test_Never(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_Never(self, ms):
|
||||
should_trigger = Never().should_trigger
|
||||
self.assertFalse(any(map(should_trigger, self.minutes)))
|
||||
self.assertFalse(any(map(should_trigger, ms)))
|
||||
|
||||
def test_AfterOpen(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_AfterOpen(self, ms):
|
||||
should_trigger = AfterOpen(minutes=5, hours=1).should_trigger
|
||||
for d in self.trading_days:
|
||||
for m in islice(d, 64):
|
||||
# Check the first 64 minutes of data.
|
||||
# We use 64 because the offset is from market open
|
||||
# at 13:30 UTC, meaning the first minute of data has an
|
||||
# offset of 1.
|
||||
self.assertFalse(should_trigger(m))
|
||||
for m in islice(d, 64, None):
|
||||
# Check the rest of the day.
|
||||
self.assertTrue(should_trigger(m))
|
||||
for m in islice(ms, 64):
|
||||
# Check the first 64 minutes of data.
|
||||
# We use 64 because the offset is from market open
|
||||
# at 13:30 UTC, meaning the first minute of data has an
|
||||
# offset of 1.
|
||||
self.assertFalse(should_trigger(m))
|
||||
for m in islice(ms, 64, None):
|
||||
# Check the rest of the day.
|
||||
self.assertTrue(should_trigger(m))
|
||||
|
||||
def test_BeforeClose(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_BeforeClose(self, ms):
|
||||
ms = list(ms)
|
||||
should_trigger = BeforeClose(hours=1, minutes=5).should_trigger
|
||||
for d in self.trading_days:
|
||||
for m in d[0:-65]:
|
||||
self.assertFalse(should_trigger(m))
|
||||
for m in d[-65:]:
|
||||
self.assertTrue(should_trigger(m))
|
||||
for m in ms[0:-65]:
|
||||
self.assertFalse(should_trigger(m))
|
||||
for m in ms[-65:]:
|
||||
self.assertTrue(should_trigger(m))
|
||||
|
||||
def test_NotHalfDay(self):
|
||||
should_trigger = NotHalfDay().should_trigger
|
||||
@@ -334,7 +341,8 @@ class TestStatelessRules(RuleTestCase):
|
||||
else:
|
||||
self.assertNotEqual(n_days_before, n)
|
||||
|
||||
def test_ComposedRule(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_ComposedRule(self, ms):
|
||||
rule1 = Always()
|
||||
rule2 = Never()
|
||||
|
||||
@@ -342,7 +350,7 @@ class TestStatelessRules(RuleTestCase):
|
||||
self.assertIsInstance(composed, ComposedRule)
|
||||
self.assertIs(composed.first, rule1)
|
||||
self.assertIs(composed.second, rule2)
|
||||
self.assertFalse(any(map(composed.should_trigger, self.minutes)))
|
||||
self.assertFalse(any(map(composed.should_trigger, ms)))
|
||||
|
||||
|
||||
class TestStatefulRules(RuleTestCase):
|
||||
@@ -352,7 +360,8 @@ class TestStatefulRules(RuleTestCase):
|
||||
|
||||
cls.class_ = StatefulRule
|
||||
|
||||
def test_OncePerDay(self):
|
||||
@parameterized.expand(minutes_for_days())
|
||||
def test_OncePerDay(self, ms):
|
||||
class RuleCounter(StatefulRule):
|
||||
"""
|
||||
A rule that counts the number of times another rule triggers
|
||||
@@ -367,8 +376,7 @@ class TestStatefulRules(RuleTestCase):
|
||||
return st
|
||||
|
||||
rule = RuleCounter(OncePerDay())
|
||||
for m in self.minutes:
|
||||
for m in ms:
|
||||
rule.should_trigger(m)
|
||||
|
||||
# We are only using 5 trading days.
|
||||
self.assertEqual(rule.count, 5)
|
||||
self.assertEqual(rule.count, 1)
|
||||
|
||||
@@ -34,7 +34,6 @@ from zipline.errors import (
|
||||
UnsupportedCommissionModel,
|
||||
UnsupportedOrderParameters,
|
||||
UnsupportedSlippageModel,
|
||||
IncompatibleScheduleFunctionDataFrequency,
|
||||
)
|
||||
|
||||
from zipline.finance import trading
|
||||
@@ -543,11 +542,11 @@ class TradingAlgorithm(object):
|
||||
"""
|
||||
Schedules a function to be called with some timed rules.
|
||||
"""
|
||||
if self.sim_params.data_frequency != 'minute':
|
||||
raise IncompatibleScheduleFunctionDataFrequency()
|
||||
|
||||
date_rule = date_rule or DateRuleFactory.every_day()
|
||||
time_rule = time_rule or TimeRuleFactory.market_open()
|
||||
time_rule = ((time_rule or TimeRuleFactory.market_open())
|
||||
if self.sim_params.data_frequency == 'minute' else
|
||||
# If we are in daily mode the time_rule is ignored.
|
||||
zipline.utils.events.Always())
|
||||
|
||||
self.add_event(
|
||||
make_eventrule(date_rule, time_rule, half_days),
|
||||
|
||||
@@ -170,12 +170,3 @@ 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.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user