Skip to content

Commit

Permalink
ENH: Adds a new api method schedule_function.
Browse files Browse the repository at this point in the history
schedule_function takes a date rule, a time rule, and a function and
will call the function, passing context and data only when the two rules
fire. This allows for code that is conditional to the datetime of the
algo.

This is implemented internally with `Event` objects which are pairings
of `EventRule`s and callbacks.

handle_data becomes a special event with a rule that always fires. This
makes the logic for handling events more complete and compact.
  • Loading branch information
llllllllll committed Oct 6, 2014
1 parent b3c7c26 commit e0a4dbf
Show file tree
Hide file tree
Showing 10 changed files with 1,585 additions and 7 deletions.
42 changes: 40 additions & 2 deletions tests/test_algorithm.py
Expand Up @@ -12,7 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
from datetime import timedelta
from mock import MagicMock
from six.moves import range
Expand Down Expand Up @@ -77,6 +77,7 @@
from zipline.finance.execution import LimitOrder
from zipline.finance.trading import SimulationParameters
from zipline.utils.api_support import set_algo_instance
from zipline.utils.events import DateRuleFactory, TimeRuleFactory
from zipline.algorithm import TradingAlgorithm


Expand Down Expand Up @@ -174,6 +175,44 @@ def handle_data(algo, data):
sim_params=self.sim_params)
algo.run(self.source)

def test_schedule_function(self):
date_rules = DateRuleFactory
time_rules = TimeRuleFactory

def incrementer(algo, data):
algo.func_called += 1
self.assertEqual(
algo.get_datetime().time(),
datetime.time(hour=14, minute=31),
)

def initialize(algo):
algo.func_called = 0
algo.days = 1
algo.date = None
algo.schedule_function(
func=incrementer,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(),
)

def handle_data(algo, data):
if not algo.date:
algo.date = algo.get_datetime().date()

if algo.date < algo.get_datetime().date():
algo.days += 1
algo.date = algo.get_datetime().date()

algo = TradingAlgorithm(
initialize=initialize,
handle_data=handle_data,
sim_params=self.sim_params,
)
algo.run(self.source)

self.assertEqual(algo.func_called, algo.days)


class TestTransformAlgorithm(TestCase):
def setUp(self):
Expand Down Expand Up @@ -840,7 +879,6 @@ def handle_data(algo, data):
self.check_algo_succeeds(algo, handle_data, order_count=20)

def test_long_only(self):

# Sell immediately -> fail immediately.
def handle_data(algo, data):
algo.order(self.sid, -1)
Expand Down
268 changes: 268 additions & 0 deletions tests/utils/test_argcheck.py
@@ -0,0 +1,268 @@
#
# Copyright 2014 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from unittest import TestCase

from zipline.utils.argcheck import (
verify_callable_argspec,
Argument,
NoStarargs,
UnexpectedStarargs,
NoKwargs,
UnexpectedKwargs,
NotCallable,
NotEnoughArguments,
TooManyArguments,
MismatchedArguments,
)


class TestArgCheck(TestCase):
def test_not_callable(self):
"""
Check the results of a non-callable object.
"""
not_callable = 'a'

with self.assertRaises(NotCallable):
verify_callable_argspec(not_callable)

def test_no_starargs(self):
"""
Tests when a function does not have *args and it was expected.
"""
def f(a):
pass

with self.assertRaises(NoStarargs):
verify_callable_argspec(f, expect_starargs=True)

def test_starargs(self):
"""
Tests when a function has *args and it was expected.
"""
def f(*args):
pass

verify_callable_argspec(f, expect_starargs=True)

def test_unexcpected_starargs(self):
"""
Tests a function that unexpectedly accepts *args.
"""
def f(*args):
pass

with self.assertRaises(UnexpectedStarargs):
verify_callable_argspec(f, expect_starargs=False)

def test_ignore_starargs(self):
"""
Tests checking a function ignoring the presence of *args.
"""
def f(*args):
pass

def g():
pass

verify_callable_argspec(f, expect_starargs=Argument.ignore)
verify_callable_argspec(g, expect_starargs=Argument.ignore)

def test_no_kwargs(self):
"""
Tests when a function does not have **kwargs and it was expected.
"""
def f():
pass

with self.assertRaises(NoKwargs):
verify_callable_argspec(f, expect_kwargs=True)

def test_kwargs(self):
"""
Tests when a function has **kwargs and it was expected.
"""
def f(**kwargs):
pass

verify_callable_argspec(f, expect_kwargs=True)

def test_unexpected_kwargs(self):
"""
Tests a function that unexpectedly accepts **kwargs.
"""
def f(**kwargs):
pass

with self.assertRaises(UnexpectedKwargs):
verify_callable_argspec(f, expect_kwargs=False)

def test_ignore_kwargs(self):
"""
Tests checking a function ignoring the presence of **kwargs.
"""
def f(**kwargs):
pass

def g():
pass

verify_callable_argspec(f, expect_kwargs=Argument.ignore)
verify_callable_argspec(g, expect_kwargs=Argument.ignore)

def test_arg_subset(self):
"""
Tests when the args are a subset of the expectations.
"""
def f(a, b):
pass

with self.assertRaises(NotEnoughArguments):
verify_callable_argspec(
f, [Argument('a'), Argument('b'), Argument('c')]
)

def test_arg_superset(self):
def f(a, b, c):
pass

with self.assertRaises(TooManyArguments):
verify_callable_argspec(f, [Argument('a'), Argument('b')])

def test_no_default(self):
"""
Tests when an argument expects a default and it is not present.
"""
def f(a):
pass

with self.assertRaises(MismatchedArguments):
verify_callable_argspec(f, [Argument('a', 1)])

def test_default(self):
"""
Tests when an argument expects a default and it is present.
"""
def f(a=1):
pass

verify_callable_argspec(f, [Argument('a', 1)])

def test_ignore_default(self):
"""
Tests that ignoring defaults works as intended.
"""
def f(a=1):
pass

verify_callable_argspec(f, [Argument('a')])

def test_mismatched_args(self):
def f(a, b):
pass

with self.assertRaises(MismatchedArguments):
verify_callable_argspec(f, [Argument('c'), Argument('d')])

def test_ignore_args(self):
"""
Tests the ignore argument list feature.
"""
def f(a):
pass

def g():
pass

h = 'not_callable'

verify_callable_argspec(f)
verify_callable_argspec(g)
with self.assertRaises(NotCallable):
verify_callable_argspec(h)

def test_out_of_order(self):
"""
Tests the case where arguments are not in the correct order.
"""
def f(a, b):
pass

with self.assertRaises(MismatchedArguments):
verify_callable_argspec(f, [Argument('b'), Argument('a')])

def test_wrong_default(self):
"""
Tests the case where a default is expected, but the default provided
does not match the one expected.
"""
def f(a=1):
pass

with self.assertRaises(MismatchedArguments):
verify_callable_argspec(f, [Argument('a', 2)])

def test_any_default(self):
"""
Tests the any_default option.
"""
def f(a=1):
pass

def g(a=2):
pass

def h(a):
pass

expected_args = [Argument('a', Argument.any_default)]
verify_callable_argspec(f, expected_args)
verify_callable_argspec(g, expected_args)
with self.assertRaises(MismatchedArguments):
verify_callable_argspec(h, expected_args)

def test_ignore_name(self):
"""
Tests ignoring a param name.
"""
def f(a):
pass

def g(b):
pass

def h(c=1):
pass

expected_args = [Argument(Argument.ignore, Argument.no_default)]
verify_callable_argspec(f, expected_args)
verify_callable_argspec(f, expected_args)
with self.assertRaises(MismatchedArguments):
verify_callable_argspec(h, expected_args)

def test_bound_method(self):
class C(object):
def f(self, a, b):
pass

method = C().f

verify_callable_argspec(method, [Argument('a'), Argument('b')])
with self.assertRaises(NotEnoughArguments):
# Assert that we don't count self.
verify_callable_argspec(
method,
[Argument('self'), Argument('a'), Argument('b')],
)

0 comments on commit e0a4dbf

Please sign in to comment.