From 372ff1be0bcb65c6ed2a2caa1ac687522fb4566a Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Thu, 3 May 2018 12:01:44 -0400 Subject: [PATCH 01/14] MAINT: Use fixture in WithRecordAlgorithm. --- tests/test_algorithm.py | 34 ++++++++++++++++++++-------------- zipline/test_algorithms.py | 12 ------------ zipline/testing/fixtures.py | 7 +++++-- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 6bfab1da13..e61d167fad 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -36,7 +36,6 @@ import zipline.api from zipline import run_algorithm -from zipline import TradingAlgorithm from zipline.api import FixedSlippage from zipline.assets import Equity, Future, Asset from zipline.assets.continuous_futures import ContinuousFuture @@ -105,21 +104,13 @@ tmp_dir, ) from zipline.testing import RecordBatchBlotter -from zipline.testing.fixtures import ( - WithDataPortal, - WithLogger, - WithSimParams, - WithTradingEnvironment, - WithTmpDir, - ZiplineTestCase, -) +import zipline.testing.fixtures as zf from zipline.test_algorithms import ( access_account_in_init, access_portfolio_in_init, AmbitiousStopLimitAlgorithm, EmptyPositionsAlgorithm, InvalidOrderAlgorithm, - RecordAlgorithm, FutureFlipAlgo, TestOrderAlgorithm, TestOrderPercentAlgorithm, @@ -197,12 +188,27 @@ _multiprocess_can_split_ = False -class TestRecordAlgorithm(WithSimParams, WithDataPortal, ZiplineTestCase): - ASSET_FINDER_EQUITY_SIDS = 133, +class TestRecord(zf.WithMakeAlgo, zf.ZiplineTestCase): + ASSET_FINDER_EQUITY_SIDS = (133,) + SIM_PARAMS_DATA_FREQUENCY = 'daily' + DATA_PORTAL_USE_MINUTE_DATA = False def test_record_incr(self): - algo = RecordAlgorithm(sim_params=self.sim_params, env=self.env) - output = algo.run(self.data_portal) + + def initialize(self): + self.incr = 0 + + def handle_data(self, data): + self.incr += 1 + self.record(incr=self.incr) + name = 'name' + self.record(name, self.incr) + zipline.api.record(name, self.incr, 'name2', 2, name3=self.incr) + + output = self.run_algorithm( + initialize=initialize, + handle_data=handle_data, + ) np.testing.assert_array_equal(output['incr'].values, range(1, len(output) + 1)) diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index 24c0bf3f2c..bf62500b45 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -241,18 +241,6 @@ def handle_data(self, data): pass -class RecordAlgorithm(TradingAlgorithm): - def initialize(self): - self.incr = 0 - - def handle_data(self, data): - self.incr += 1 - self.record(incr=self.incr) - name = 'name' - self.record(name, self.incr) - record(name, self.incr, 'name2', 2, name3=self.incr) - - class TestOrderAlgorithm(TradingAlgorithm): def initialize(self): self.incr = 0 diff --git a/zipline/testing/fixtures.py b/zipline/testing/fixtures.py index d36a0b5077..b28772db7b 100644 --- a/zipline/testing/fixtures.py +++ b/zipline/testing/fixtures.py @@ -1806,6 +1806,7 @@ class WithMakeAlgo(WithSimParams, START_DATE = pd.Timestamp('2014-12-29', tz='UTC') END_DATE = pd.Timestamp('2015-1-05', tz='UTC') SIM_PARAMS_DATA_FREQUENCY = 'minute' + DEFAULT_ALGORITHM_CLASS = TradingAlgorithm @classproperty def BENCHMARK_SID(cls): @@ -1827,8 +1828,10 @@ def make_algo_kwargs(self, **overrides): overrides, ) - def make_algo(self, **overrides): - return TradingAlgorithm(**self.make_algo_kwargs(**overrides)) + def make_algo(self, algo_class=None, **overrides): + if algo_class is None: + algo_class = self.DEFAULT_ALGORITHM_CLASS + return algo_class(**self.make_algo_kwargs(**overrides)) def run_algorithm(self, **overrides): """ From 22d8bdbc1fdba1f7176298b058c82d497e3db03f Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Thu, 3 May 2018 16:02:41 -0400 Subject: [PATCH 02/14] MAINT: Update TestMiscellaneousAPI to use fixtures --- tests/test_algorithm.py | 177 ++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 97 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index e61d167fad..d6657b275f 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -220,16 +220,16 @@ def handle_data(self, data): range(1, len(output) + 1)) -class TestMiscellaneousAPI(WithLogger, - WithSimParams, - WithDataPortal, - ZiplineTestCase): +class TestMiscellaneousAPI(zf.WithMakeAlgo, zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-03', tz='UTC') END_DATE = pd.Timestamp('2006-01-04', tz='UTC') SIM_PARAMS_DATA_FREQUENCY = 'minute' sids = 1, 2 + # FIXME: Pass a benchmark source instead of this. + BENCHMARK_SID = None + @classmethod def make_equity_info(cls): return pd.concat(( @@ -297,13 +297,9 @@ def initialize(algo): def handle_data(algo, data): set_cancel_policy(cancel_policy.NeverCancel()) """ - - algo = TradingAlgorithm(script=code, - sim_params=self.sim_params, - env=self.env) - + algo = self.make_algo(script=code) with self.assertRaises(SetCancelPolicyPostInit): - algo.run(self.data_portal) + algo.run() def test_cancel_policy_invalid_param(self): code = """ @@ -315,20 +311,15 @@ def initialize(algo): def handle_data(algo, data): pass """ - algo = TradingAlgorithm(script=code, - sim_params=self.sim_params, - env=self.env) - + algo = self.make_algo(script=code) with self.assertRaises(UnsupportedCancelPolicy): - algo.run(self.data_portal) + algo.run() def test_zipline_api_resolves_dynamically(self): # Make a dummy algo. - algo = TradingAlgorithm( + algo = self.make_algo( initialize=lambda context: None, handle_data=lambda context, data: None, - sim_params=self.sim_params, - env=self.env, ) # Verify that api methods get resolved dynamically by patching them out @@ -354,11 +345,10 @@ def handle_data(context, data): aapl_dt = data.current(sid(1), "last_traded") assert_equal(aapl_dt, get_datetime()) """ - algo = TradingAlgorithm(script=algo_text, - sim_params=self.sim_params, - env=self.env) - algo.namespace['assert_equal'] = self.assertEqual - algo.run(self.data_portal) + self.run_algorithm( + script=algo_text, + namespace={'assert_equal': self.assertEqual}, + ) def test_datetime_bad_params(self): algo_text = """ @@ -371,11 +361,9 @@ def initialize(context): def handle_data(context, data): get_datetime(timezone) """ + algo = self.make_algo(script=algo_text) with self.assertRaises(TypeError): - algo = TradingAlgorithm(script=algo_text, - sim_params=self.sim_params, - env=self.env) - algo.run(self.data_portal) + algo.run() def test_get_environment(self): expected_env = { @@ -394,11 +382,7 @@ def initialize(algo): def handle_data(algo, data): pass - algo = TradingAlgorithm(initialize=initialize, - handle_data=handle_data, - sim_params=self.sim_params, - env=self.env) - algo.run(self.data_portal) + self.run_algorithm(initialize=initialize, handle_data=handle_data) def test_get_open_orders(self): def initialize(algo): @@ -444,11 +428,7 @@ def handle_data(algo, data): algo.minute += 1 - algo = TradingAlgorithm(initialize=initialize, - handle_data=handle_data, - sim_params=self.sim_params, - env=self.env) - algo.run(self.data_portal) + self.run_algorithm(initialize=initialize, handle_data=handle_data) def test_schedule_function_custom_cal(self): # run a simulation on the CME cal, and schedule a function @@ -483,14 +463,11 @@ def log_nyse_close(context, data): context.nyse_closes.append(get_datetime()) """ - algo = TradingAlgorithm( + algo = self.make_algo( script=algotext, - sim_params=self.sim_params, - env=self.env, - trading_calendar=get_calendar("CME") + trading_calendar=get_calendar("CME"), ) - - algo.run(self.data_portal) + algo.run() nyse = get_calendar("NYSE") @@ -520,15 +497,13 @@ def my_func(context, data): """ ) - algo = TradingAlgorithm( + algo = self.make_algo( script=erroring_algotext, - sim_params=self.sim_params, - env=self.env, trading_calendar=get_calendar('CME'), ) with self.assertRaises(ScheduleFunctionInvalidCalendar): - algo.run(self.data_portal) + algo.run() def test_schedule_function(self): us_eastern = pytz.timezone('US/Eastern') @@ -564,13 +539,11 @@ def handle_data(algo, data): algo.days += 1 algo.date = algo.get_datetime().date() - algo = TradingAlgorithm( + algo = self.make_algo( initialize=initialize, handle_data=handle_data, - sim_params=self.sim_params, - env=self.env, ) - algo.run(self.data_portal) + algo.run() self.assertEqual(algo.func_called, algo.days) @@ -602,12 +575,10 @@ def f(context, data): def g(context, data): function_stack.append(g) - algo = TradingAlgorithm( + algo = self.make_algo( initialize=initialize, handle_data=handle_data, - sim_params=self.sim_params, create_event_context=CallbackManager(pre, post), - env=self.env, ) algo.run(self.data_portal) @@ -639,7 +610,7 @@ def nop(*args, **kwargs): return None self.sim_params.data_frequency = mode - algo = TradingAlgorithm( + algo = self.make_algo( initialize=nop, handle_data=nop, sim_params=self.sim_params, @@ -679,7 +650,7 @@ def nop(*args, **kwargs): self.assertIs(composer, ComposedRule.lazy_and) def test_asset_lookup(self): - algo = TradingAlgorithm(env=self.env) + algo = self.make_algo() # this date doesn't matter start_session = pd.Timestamp("2000-01-01", tz="UTC") @@ -749,7 +720,7 @@ def test_asset_lookup(self): def test_future_symbol(self): """ Tests the future_symbol API function. """ - algo = TradingAlgorithm(env=self.env) + algo = self.make_algo() algo.datetime = pd.Timestamp('2006-12-01', tz='UTC') # Check that we get the correct fields for the CLG06 symbol @@ -788,55 +759,67 @@ def test_future_symbol(self): with self.assertRaises(TypeError): algo.future_symbol({'foo': 'bar'}) + +class TestSetSymbolLookupDate(zf.WithMakeAlgo, zf.ZiplineTestCase): + # January 2006 + # Su Mo Tu We Th Fr Sa + # 1 2 3 4 5 6 7 + # 8 9 10 11 12 13 14 + # 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 31 + START_DATE = pd.Timestamp('2006-01-03', tz='UTC') + END_DATE = pd.Timestamp('2006-01-06', tz='UTC') + SIM_PARAMS_START_DATE = pd.Timestamp('2006-01-04', tz='UTC') + SIM_PARAMS_DATA_FREQUENCY = 'daily' + DATA_PORTAL_USE_MINUTE_DATA = False + BENCHMARK_SID = 3 + + @classmethod + def make_equity_info(cls): + dates = pd.date_range(cls.START_DATE, cls.END_DATE) + assert len(dates) == 4, "Expected four dates." + + # Two assets with the same ticker, ending on days[1] and days[3], plus + # a benchmark that spans the whole period. + cls.sids = [1, 2, 3] + cls.asset_starts = [dates[0], dates[2]] + cls.asset_ends = [dates[1], dates[3]] + return pd.DataFrame.from_records([ + {'symbol': 'DUP', + 'start_date': cls.asset_starts[0], + 'end_date': cls.asset_ends[0], + 'exchange': 'TEST', + 'asset_name': 'FIRST'}, + {'symbol': 'DUP', + 'start_date': cls.asset_starts[1], + 'end_date': cls.asset_ends[1], + 'exchange': 'TEST', + 'asset_name': 'SECOND'}, + {'symbol': 'BENCH', + 'start_date': cls.START_DATE, + 'end_date': cls.END_DATE, + 'exchange': 'TEST', + 'asset_name': 'BENCHMARK'}, + ], index=cls.sids) + def test_set_symbol_lookup_date(self): """ Test the set_symbol_lookup_date API method. """ - # Note we start sid enumeration at i+3 so as not to - # collide with sids [1, 2] added in the setUp() method. - dates = pd.date_range('2013-01-01', freq='2D', periods=2, tz='UTC') - # Create two assets with the same symbol but different - # non-overlapping date ranges. - metadata = pd.DataFrame.from_records( - [ - { - 'sid': i + 3, - 'symbol': 'DUP', - 'start_date': date.value, - 'end_date': (date + timedelta(days=1)).value, - 'exchange': 'TEST', - } - for i, date in enumerate(dates) - ] - ) - with tmp_trading_env(equities=metadata, - load=self.make_load_function()) as env: - algo = TradingAlgorithm(env=env) - - # Set the period end to a date after the period end - # dates for our assets. - algo.sim_params = algo.sim_params.create_new( - algo.sim_params.start_session, - pd.Timestamp('2015-01-01', tz='UTC') - ) + set_symbol_lookup_date = zipline.api.set_symbol_lookup_date - # With no symbol lookup date set, we will use the period end date - # for the as_of_date, resulting here in the asset with the earlier - # start date being returned. - result = algo.symbol('DUP') - self.assertEqual(result.symbol, 'DUP') + def initialize(context): + set_symbol_lookup_date(self.asset_ends[0]) + self.assertEqual(zipline.api.symbol('DUP').sid, self.sids[0]) - # By first calling set_symbol_lookup_date, the relevant asset - # should be returned by lookup_symbol - for i, date in enumerate(dates): - algo.set_symbol_lookup_date(date) - result = algo.symbol('DUP') - self.assertEqual(result.symbol, 'DUP') - self.assertEqual(result.sid, i + 3) + set_symbol_lookup_date(self.asset_ends[1]) + self.assertEqual(zipline.api.symbol('DUP').sid, self.sids[1]) with self.assertRaises(UnsupportedDatetimeFormat): - algo.set_symbol_lookup_date('foobar') + set_symbol_lookup_date('foobar') + self.run_algorithm(initialize=initialize) class TestTransformAlgorithm(WithLogger, WithDataPortal, From e5228e66fdf27bdcac3f7e4606afc9a4464f52d3 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 01:13:31 -0400 Subject: [PATCH 03/14] MAINT: Rework TestTransformAlgorithm. - Move ordering tests to a new `test_ordering` suite. - Drop `test_data_frequency_setting`. --- tests/test_algorithm.py | 350 +++++++------------------------------ tests/test_ordering.py | 341 ++++++++++++++++++++++++++++++++++++ zipline/test_algorithms.py | 246 +------------------------- 3 files changed, 404 insertions(+), 533 deletions(-) create mode 100644 tests/test_ordering.py diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index d6657b275f..8d9df5ce44 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -58,7 +58,6 @@ CannotOrderDelistedAsset, IncompatibleSlippageModel, OrderDuringInitialize, - OrderInBeforeTradingStart, RegisterTradingControlPostInit, ScheduleFunctionInvalidCalendar, SetCancelPolicyPostInit, @@ -67,14 +66,6 @@ UnsupportedCancelPolicy, UnsupportedDatetimeFormat, ) -from zipline.api import ( - order, - order_value, - order_percent, - order_target, - order_target_value, - order_target_percent -) from zipline.finance.commission import PerShare from zipline.finance.execution import LimitOrder @@ -109,18 +100,8 @@ access_account_in_init, access_portfolio_in_init, AmbitiousStopLimitAlgorithm, - EmptyPositionsAlgorithm, - InvalidOrderAlgorithm, FutureFlipAlgo, - TestOrderAlgorithm, - TestOrderPercentAlgorithm, - TestOrderStyleForwardingAlgorithm, - TestOrderValueAlgorithm, TestPositionWeightsAlgorithm, - TestRegisterTransformAlgorithm, - TestTargetAlgorithm, - TestTargetPercentAlgorithm, - TestTargetValueAlgorithm, SetLongOnlyAlgorithm, SetAssetDateBoundsAlgorithm, SetMaxPositionSizeAlgorithm, @@ -168,6 +149,7 @@ empty_positions, no_handle_data, ) +import zipline.test_algorithms as zta from zipline.testing.predicates import assert_equal from zipline.utils.api_support import ZiplineAPI, set_algo_instance from zipline.utils.calendars import get_calendar, register_calendar @@ -821,101 +803,34 @@ def initialize(context): self.run_algorithm(initialize=initialize) -class TestTransformAlgorithm(WithLogger, - WithDataPortal, - WithSimParams, - ZiplineTestCase): + +class TestRunTwice(zf.WithMakeAlgo, zf.ZiplineTestCase): + # January 2006 + # Su Mo Tu We Th Fr Sa + # 1 2 3 4 5 6 7 + # 8 9 10 11 12 13 14 + # 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 31 START_DATE = pd.Timestamp('2006-01-03', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') - sids = ASSET_FINDER_EQUITY_SIDS = [0, 1, 133] - - @classmethod - def make_futures_info(cls): - return pd.DataFrame.from_dict({ - 3: { - 'multiplier': 10, - 'symbol': 'F', - 'exchange': 'TEST' - } - }, orient='index') - - @classmethod - def make_equity_daily_bar_data(cls): - return trades_by_sid_to_dfs( - { - sid: factory.create_trade_history( - sid, - [10.0, 10.0, 11.0, 11.0], - [100, 100, 100, 300], - timedelta(days=1), - cls.sim_params, - cls.trading_calendar, - ) for sid in cls.sids - }, - index=cls.sim_params.sessions, - ) - - @classmethod - def init_class_fixtures(cls): - super(TestTransformAlgorithm, cls).init_class_fixtures() - cls.futures_env = cls.enter_class_context( - tmp_trading_env(futures=cls.make_futures_info(), - load=cls.make_load_function()), - ) - - def test_invalid_order_parameters(self): - algo = InvalidOrderAlgorithm( - sids=[133], - sim_params=self.sim_params, - env=self.env, - ) - algo.run(self.data_portal) - - @parameterized.expand([ - (order, 1), - (order_value, 1000), - (order_target, 1), - (order_target_value, 1000), - (order_percent, 1), - (order_target_percent, 1), - ]) - def test_cannot_order_in_before_trading_start(self, order_method, amount): - algotext = """ -from zipline.api import sid -from zipline.api import {order_func} - -def initialize(context): - context.asset = sid(133) - -def before_trading_start(context, data): - {order_func}(context.asset, {arg}) - """.format(order_func=order_method.__name__, arg=amount) - - algo = TradingAlgorithm(script=algotext, sim_params=self.sim_params, - data_frequency='daily', env=self.env) + ASSET_FINDER_EQUITY_SIDS = [0, 1, 133] + SIM_PARAMS_DATA_FREQUENCY = 'daily' + DATA_PORTAL_USE_MINUTE_DATA = False - with self.assertRaises(OrderInBeforeTradingStart): - algo.run(self.data_portal) + # FIXME: Pass a benchmark source explicitly here. + BENCHMARK_SID = None def test_run_twice(self): - algo1 = TestRegisterTransformAlgorithm( - sim_params=self.sim_params, - sids=[0, 1], - env=self.env, - ) - - res1 = algo1.run(self.data_portal) - - # Create a new trading algorithm, which will - # use the newly instantiated environment. - algo2 = TestRegisterTransformAlgorithm( - sim_params=self.sim_params, - sids=[0, 1], - env=self.env, - ) + algo = self.make_algo(script=zta.noop_algo) - res2 = algo2.run(self.data_portal) + res1 = algo.run() + # XXX: Calling run() twice only works if you pass a data portal + # explicitly on the second call, because we `del self.data_portal` at + # the end of an algorithm execution. Do we actually still care about + # the ability to double-run an algo? + res2 = algo.run(self.data_portal) # There are some np.NaN values in the first row because there is not # enough data to calculate the metric, e.g. beta. @@ -924,186 +839,16 @@ def test_run_twice(self): np.testing.assert_array_equal(res1, res2) - def test_data_frequency_setting(self): - self.sim_params.data_frequency = 'daily' - - sim_params = factory.create_simulation_parameters( - num_days=4, data_frequency='daily') - - algo = TestRegisterTransformAlgorithm( - sim_params=sim_params, - env=self.env, - ) - self.assertEqual(algo.sim_params.data_frequency, 'daily') - - sim_params = factory.create_simulation_parameters( - num_days=4, data_frequency='minute') - - algo = TestRegisterTransformAlgorithm( - sim_params=sim_params, - env=self.env, - ) - self.assertEqual(algo.sim_params.data_frequency, 'minute') - - def test_order_rounding(self): - answer_key = [ - (0, 0), - (10, 10), - (1.1, 1), - (1.5, 1), - (1.9998, 1), - (1.99991, 2), - ] - - for input, answer in answer_key: - self.assertEqual( - answer, - TradingAlgorithm.round_order(input) - ) - - self.assertEqual( - -1 * answer, - TradingAlgorithm.round_order(-1 * input) - ) - @parameterized.expand([ - ('order', TestOrderAlgorithm,), - ('order_value', TestOrderValueAlgorithm,), - ('order_target', TestTargetAlgorithm,), - ('order_percent', TestOrderPercentAlgorithm,), - ('order_target_percent', TestTargetPercentAlgorithm,), - ('order_target_value', TestTargetValueAlgorithm,), - ]) - def test_order_methods(self, test_name, algo_class): - algo = algo_class( - sim_params=self.sim_params, - env=self.env, - ) - # Ensure that the environment's asset 0 is an Equity - asset_to_test = algo.sid(0) - self.assertIsInstance(asset_to_test, Equity) - - algo.run(self.data_portal) - - @parameterized.expand([ - (TestOrderAlgorithm,), - (TestOrderValueAlgorithm,), - (TestTargetAlgorithm,), - (TestOrderPercentAlgorithm,), - (TestTargetValueAlgorithm,), - ]) - def test_order_methods_for_future(self, algo_class): - algo = algo_class( - sim_params=self.sim_params, - env=self.env, - ) - # Ensure that the environment's asset 3 is a Future - asset_to_test = algo.sid(3) - self.assertIsInstance(asset_to_test, Future) - - algo.run(self.data_portal) - - @parameterized.expand([ - ("order",), - ("order_value",), - ("order_percent",), - ("order_target",), - ("order_target_percent",), - ("order_target_value",), - ]) - def test_order_method_style_forwarding(self, order_style): - algo = TestOrderStyleForwardingAlgorithm( - sim_params=self.sim_params, - method_name=order_style, - env=self.env - ) - algo.run(self.data_portal) - - def test_order_on_each_day_of_asset_lifetime(self): - algo_code = dedent(""" - from zipline.api import sid, schedule_function, date_rules, order - def initialize(context): - schedule_function(order_it, date_rule=date_rules.every_day()) - - def order_it(context, data): - order(sid(133), 1) - - def handle_data(context, data): - pass - """) - - asset133 = self.env.asset_finder.retrieve_asset(133) - - sim_params = SimulationParameters( - start_session=asset133.start_date, - end_session=asset133.end_date, - data_frequency="minute", - trading_calendar=self.trading_calendar - ) - - algo = TradingAlgorithm( - script=algo_code, - sim_params=sim_params, - env=self.env - ) - - results = algo.run(FakeDataPortal(self.env)) - - for orders_for_day in results.orders: - self.assertEqual(1, len(orders_for_day)) - self.assertEqual(orders_for_day[0]["status"], ORDER_STATUS.FILLED) - - for txns_for_day in results.transactions: - self.assertEqual(1, len(txns_for_day)) - self.assertEqual(1, txns_for_day[0]["amount"]) - - @parameterized.expand([ - (TestOrderAlgorithm,), - (TestOrderValueAlgorithm,), - (TestTargetAlgorithm,), - (TestOrderPercentAlgorithm,) - ]) - def test_minute_data(self, algo_class): - start_session = pd.Timestamp('2002-1-2', tz='UTC') - period_end = pd.Timestamp('2002-1-4', tz='UTC') - equities = pd.DataFrame([{ - 'start_date': start_session, - 'end_date': period_end + timedelta(days=1), - 'exchange': "TEST", - }] * 2) - equities['symbol'] = ['A', 'B'] - with TempDirectory() as tempdir, \ - tmp_trading_env(equities=equities, - load=self.make_load_function()) as env: - sim_params = SimulationParameters( - start_session=start_session, - end_session=period_end, - capital_base=1.0e5, - data_frequency='minute', - trading_calendar=self.trading_calendar, - ) - - data_portal = create_data_portal( - env.asset_finder, - tempdir, - sim_params, - equities.index, - self.trading_calendar, - ) - algo = algo_class(sim_params=sim_params, env=env) - algo.run(data_portal) - - -class TestPositions(WithLogger, - WithDataPortal, - WithSimParams, - ZiplineTestCase): +class TestPositions(zf.WithMakeAlgo, zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-03', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') SIM_PARAMS_CAPITAL_BASE = 1000 ASSET_FINDER_EQUITY_SIDS = (1, 133) + SIM_PARAMS_DATA_FREQUENCY = 'daily' + @classmethod def make_equity_daily_bar_data(cls): frame = pd.DataFrame( @@ -1156,11 +901,42 @@ def make_future_minute_bar_data(cls): ) return ((sid, frame) for sid in sids) - def test_empty_portfolio(self): - algo = EmptyPositionsAlgorithm(self.asset_finder.equities_sids, - sim_params=self.sim_params, - env=self.env) - daily_stats = algo.run(self.data_portal) + def test_portfolio_exited_position(self): + # This test ensures ensures that 'phantom' positions do not appear in + # context.portfolio.positions in the case that a position has been + # entered and fully exited. + + def initialize(context, sids): + context.ordered = False + context.exited = False + context.sids = sids + + def handle_data(context, data): + if not context.ordered: + for s in context.sids: + context.order(context.sid(s), 1) + context.ordered = True + + if not context.exited: + amounts = [pos.amount for pos + in itervalues(context.portfolio.positions)] + + if ( + len(amounts) > 0 and + all([(amount == 1) for amount in amounts]) + ): + for stock in context.portfolio.positions: + context.order(context.sid(stock), -1) + context.exited = True + + # Should be 0 when all positions are exited. + context.record(num_positions=len(context.portfolio.positions)) + + result = self.run_algorithm( + initialize=initialize, + handle_data=handle_data, + sids=self.ASSET_FINDER_EQUITY_SIDS, + ) expected_position_count = [ 0, # Before entering the first position @@ -1168,10 +944,8 @@ def test_empty_portfolio(self): 0, # After exiting 0, ] - for i, expected in enumerate(expected_position_count): - self.assertEqual(daily_stats.ix[i]['num_positions'], - expected) + self.assertEqual(result.ix[i]['num_positions'], expected) def test_noop_orders(self): algo = AmbitiousStopLimitAlgorithm(sid=1, diff --git a/tests/test_ordering.py b/tests/test_ordering.py new file mode 100644 index 0000000000..165e0cf079 --- /dev/null +++ b/tests/test_ordering.py @@ -0,0 +1,341 @@ +from nose_parameterized import parameterized +import pandas as pd + +from zipline.algorithm import TradingAlgorithm +import zipline.api as api +import zipline.errors as ze +from zipline.finance.execution import StopLimitOrder +import zipline.testing.fixtures as zf +from zipline.testing.predicates import assert_equal +import zipline.test_algorithms as zta + + +def T(s): + return pd.Timestamp(s, tz='UTC') + + +class TestOrderMethods(zf.WithConstantEquityMinuteBarData, + zf.WithConstantFutureMinuteBarData, + zf.WithMakeAlgo, + zf.ZiplineTestCase): + # January 2006 + # Su Mo Tu We Th Fr Sa + # 1 2 3 4 5 6 7 + # 8 9 10 11 12 13 14 + # 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 31 + START_DATE = T('2006-01-03') + END_DATE = T('2006-01-06') + SIM_PARAMS_START_DATE = T('2006-01-04') + + ASSET_FINDER_EQUITY_SIDS = (1,) + + EQUITY_DAILY_BAR_SOURCE_FROM_MINUTE = True + FUTURE_DAILY_BAR_SOURCE_FROM_MINUTE = True + + EQUITY_MINUTE_CONSTANT_LOW = 2.0 + EQUITY_MINUTE_CONSTANT_OPEN = 2.0 + EQUITY_MINUTE_CONSTANT_CLOSE = 2.0 + EQUITY_MINUTE_CONSTANT_HIGH = 2.0 + EQUITY_MINUTE_CONSTANT_VOLUME = 10000.0 + + FUTURE_MINUTE_CONSTANT_LOW = 2.0 + FUTURE_MINUTE_CONSTANT_OPEN = 2.0 + FUTURE_MINUTE_CONSTANT_CLOSE = 2.0 + FUTURE_MINUTE_CONSTANT_HIGH = 2.0 + FUTURE_MINUTE_CONSTANT_VOLUME = 10000.0 + + SIM_PARAMS_CAPITAL_BASE = 10000 + + @classmethod + def make_futures_info(cls): + return pd.DataFrame.from_dict({ + 2: { + 'multiplier': 10, + 'symbol': 'F', + 'exchange': 'TEST' + } + }, orient='index') + + @classmethod + def init_class_fixtures(cls): + super(TestOrderMethods, cls).init_class_fixtures() + cls.EQUITY = cls.asset_finder.retrieve_asset(1) + cls.FUTURE = cls.asset_finder.retrieve_asset(2) + + @parameterized.expand([ + ('order', 1), + ('order_value', 1000), + ('order_target', 1), + ('order_target_value', 1000), + ('order_percent', 1), + ('order_target_percent', 1), + ]) + def test_cannot_order_in_before_trading_start(self, order_method, amount): + algotext = """ +from zipline.api import sid, {order_func} + +def initialize(context): + context.asset = sid(1) + +def before_trading_start(context, data): + {order_func}(context.asset, {arg}) + """.format(order_func=order_method, arg=amount) + + algo = self.make_algo(script=algotext) + with self.assertRaises(ze.OrderInBeforeTradingStart): + algo.run() + + @parameterized.expand([ + # These should all be orders for the same amount. + ('order', 5000), # 5000 shares times $2 per share + ('order_value', 10000), # $10000 + ('order_percent', 1), # 100% on a $10000 capital base. + ]) + def test_order_equity_non_targeted(self, order_method, amount): + # Every day, place an order for $10000 worth of sid(1) + algotext = """ +import zipline.api as api + +def initialize(context): + api.set_slippage(api.slippage.FixedSlippage(spread=0.0)) + api.set_commission(api.commission.PerShare(0)) + + context.equity = api.sid(1) + + api.schedule_function( + func=do_order, + date_rule=api.date_rules.every_day(), + time_rule=api.time_rules.market_open(), + ) + +def do_order(context, data): + context.ordered = True + api.{order_func}(context.equity, {arg}) + """.format(order_func=order_method, arg=amount) + result = self.run_algorithm(script=algotext) + + for orders in result.orders.values: + assert_equal(len(orders), 1) + assert_equal(orders[0]['amount'], 5000) + assert_equal(orders[0]['sid'], self.EQUITY) + + for i, positions in enumerate(result.positions.values, start=1): + assert_equal(len(positions), 1) + assert_equal(positions[0]['amount'], 5000.0 * i) + assert_equal(positions[0]['sid'], self.EQUITY) + + @parameterized.expand([ + # These should all be orders for the same amount. + ('order_target', 5000), # 5000 shares times $2 per share + ('order_target_value', 10000), # $10000 + ('order_target_percent', 1), # 100% on a $10000 capital base. + ]) + def test_order_equity_targeted(self, order_method, amount): + # Every day, place an order for a target of $10000 worth of sid(1). + # With no commissions or slippage, we should only place one order. + algotext = """ +import zipline.api as api + +def initialize(context): + api.set_slippage(api.slippage.FixedSlippage(spread=0.0)) + api.set_commission(api.commission.PerShare(0)) + + context.equity = api.sid(1) + + api.schedule_function( + func=do_order, + date_rule=api.date_rules.every_day(), + time_rule=api.time_rules.market_open(), + ) + +def do_order(context, data): + context.ordered = True + api.{order_func}(context.equity, {arg}) + """.format(order_func=order_method, arg=amount) + + result = self.run_algorithm(script=algotext) + + assert_equal([len(ords) for ords in result.orders], [1, 0, 0, 0]) + order = result.orders.iloc[0][0] + assert_equal(order['amount'], 5000) + assert_equal(order['sid'], self.EQUITY) + + for positions in result.positions.values: + assert_equal(len(positions), 1) + assert_equal(positions[0]['amount'], 5000.0) + assert_equal(positions[0]['sid'], self.EQUITY) + + @parameterized.expand([ + # These should all be orders for the same amount. + ('order', 500), # 500 contracts times $2 per contract * 10x + # multiplier. + ('order_value', 10000), # $10000 + ('order_percent', 1), # 100% on a $10000 capital base. + ]) + def test_order_future_non_targeted(self, order_method, amount): + # Every day, place an order for $10000 worth of sid(2) + algotext = """ +import zipline.api as api + +def initialize(context): + api.set_slippage(us_futures=api.slippage.FixedSlippage(spread=0.0)) + api.set_commission(us_futures=api.commission.PerTrade(0.0)) + + context.future = api.sid(2) + + api.schedule_function( + func=do_order, + date_rule=api.date_rules.every_day(), + time_rule=api.time_rules.market_open(), + ) + +def do_order(context, data): + context.ordered = True + api.{order_func}(context.future, {arg}) + """.format(order_func=order_method, arg=amount) + result = self.run_algorithm(script=algotext) + + for orders in result.orders.values: + assert_equal(len(orders), 1) + assert_equal(orders[0]['amount'], 500) + assert_equal(orders[0]['sid'], self.FUTURE) + + for i, positions in enumerate(result.positions.values, start=1): + assert_equal(len(positions), 1) + assert_equal(positions[0]['amount'], 500.0 * i) + assert_equal(positions[0]['sid'], self.FUTURE) + + @parameterized.expand([ + # These should all be orders targeting the same amount. + ('order_target', 500), # 500 contracts * $2 per contract * 10x + # multiplier. + ('order_target_value', 10000), # $10000 + ('order_target_percent', 1), # 100% on a $10000 capital base. + ]) + def test_order_future_targeted(self, order_method, amount): + # Every day, place an order for a target of $10000 worth of sid(2). + # With no commissions or slippage, we should only place one order. + algotext = """ +import zipline.api as api + +def initialize(context): + api.set_slippage(us_futures=api.slippage.FixedSlippage(spread=0.0)) + api.set_commission(us_futures=api.commission.PerTrade(0.0)) + + context.future = api.sid(2) + + api.schedule_function( + func=do_order, + date_rule=api.date_rules.every_day(), + time_rule=api.time_rules.market_open(), + ) + +def do_order(context, data): + context.ordered = True + api.{order_func}(context.future, {arg}) + """.format(order_func=order_method, arg=amount) + + result = self.run_algorithm(script=algotext) + + # We should get one order on the first day. + assert_equal([len(ords) for ords in result.orders], [1, 0, 0, 0]) + order = result.orders.iloc[0][0] + assert_equal(order['amount'], 500) + assert_equal(order['sid'], self.FUTURE) + + # Our position at the end of each day should be worth $10,000. + for positions in result.positions.values: + assert_equal(len(positions), 1) + assert_equal(positions[0]['amount'], 500.0) + assert_equal(positions[0]['sid'], self.FUTURE) + + @parameterized.expand([ + (api.order, 5000), + (api.order_value, 10000), + (api.order_percent, 1.0), + (api.order_target, 5000), + (api.order_target_value, 10000), + (api.order_target_percent, 1.0), + ]) + def test_order_method_style_forwarding(self, order_method, order_param): + # Test that we correctly forward values passed via `style` to Order + # objects. + def initialize(context): + api.schedule_function( + func=do_order, + date_rule=api.date_rules.every_day(), + time_rule=api.time_rules.market_open(), + ) + + def do_order(context, data): + assert len(context.portfolio.positions.keys()) == 0 + + order_method( + self.EQUITY, + order_param, + style=StopLimitOrder(10, 10), + ) + + assert len(context.blotter.open_orders[self.EQUITY]) == 1 + result = context.blotter.open_orders[self.EQUITY][0] + assert result.limit == 10 + assert result.stop == 10 + + # We only need to run for a single day for this test. + self.run_algorithm( + initialize=initialize, + sim_params=self.sim_params.create_new( + start_session=self.END_DATE, + end_session=self.END_DATE, + ), + ) + + +class TestOrderMethodsDailyFrequency(zf.WithMakeAlgo, + zf.ZiplineTestCase): + # January 2006 + # Su Mo Tu We Th Fr Sa + # 1 2 3 4 5 6 7 + # 8 9 10 11 12 13 14 + # 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 31 + START_DATE = T('2006-01-03') + END_DATE = T('2006-01-06') + SIM_PARAMS_START_DATE = T('2006-01-04') + ASSET_FINDER_EQUITY_SIDS = (1,) + + SIM_PARAMS_DATA_FREQUENCY = 'daily' + DATA_PORTAL_USE_MINUTE_DATA = False + + def test_invalid_order_parameters(self): + self.run_algorithm( + algo_class=zta.InvalidOrderAlgorithm, + sids=[1], + ) + + +class TestOrderRounding(zf.ZiplineTestCase): + + def test_order_rounding(self): + answer_key = [ + (0, 0), + (10, 10), + (1.1, 1), + (1.5, 1), + (1.9998, 1), + (1.99991, 2), + ] + + for input, answer in answer_key: + self.assertEqual( + answer, + TradingAlgorithm.round_order(input) + ) + + self.assertEqual( + -1 * answer, + TradingAlgorithm.round_order(-1 * input) + ) diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index bf62500b45..6c5515f593 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -75,7 +75,6 @@ from nose.tools import assert_raises -from six.moves import range from six import itervalues from zipline.algorithm import TradingAlgorithm @@ -87,8 +86,7 @@ sid, ) from zipline.errors import UnsupportedOrderParameters -from zipline.assets import Future, Equity -from zipline.finance.commission import PerShare, PerTrade +from zipline.finance.commission import PerTrade from zipline.finance.execution import ( LimitOrder, MarketOrder, @@ -96,7 +94,6 @@ StopOrder, ) from zipline.finance.controls import AssetDateBounds -from zipline.utils.math_utils import round_if_near_integer class TestAlgorithm(TradingAlgorithm): @@ -241,239 +238,6 @@ def handle_data(self, data): pass -class TestOrderAlgorithm(TradingAlgorithm): - def initialize(self): - self.incr = 0 - - def handle_data(self, data): - if self.incr == 0: - assert 0 not in self.portfolio.positions - else: - assert ( - self.portfolio.positions[0].amount == - self.incr - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - self.incr += 1 - self.order(self.sid(0), 1) - - -class TestOrderInstantAlgorithm(TradingAlgorithm): - def initialize(self): - self.incr = 0 - self.last_price = None - - def handle_data(self, data): - if self.incr == 0: - assert 0 not in self.portfolio.positions - else: - assert ( - self.portfolio.positions[0].amount == - self.incr - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - self.incr += 1 - self.order_value(self.sid(0), data.current(sid(0), "price")) - self.last_price = data.current(sid(0), "price") - - -class TestOrderStyleForwardingAlgorithm(TradingAlgorithm): - """ - Test Algorithm for verifying that ExecutionStyles are properly forwarded by - order API helper methods. Pass the name of the method to be tested as a - string parameter to this algorithm's constructor. - """ - - def __init__(self, *args, **kwargs): - self.method_name = kwargs.pop('method_name') - super(TestOrderStyleForwardingAlgorithm, self)\ - .__init__(*args, **kwargs) - - def initialize(self): - self.incr = 0 - self.last_price = None - - def handle_data(self, data): - if self.incr == 0: - assert len(self.portfolio.positions.keys()) == 0 - - method_to_check = getattr(self, self.method_name) - method_to_check(self.sid(133), - data.current(sid(0), "price"), - style=StopLimitOrder(10, 10)) - - assert len(self.blotter.open_orders[self.sid(133)]) == 1 - result = self.blotter.open_orders[self.sid(133)][0] - assert result.limit == 10 - assert result.stop == 10 - - self.incr += 1 - - -class TestOrderValueAlgorithm(TradingAlgorithm): - def initialize(self): - self.incr = 0 - self.sale_price = None - - def handle_data(self, data): - if self.incr == 0: - assert 0 not in self.portfolio.positions - else: - assert ( - self.portfolio.positions[0].amount == - self.incr - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - self.incr += 2 - - multiplier = 2. - if isinstance(self.sid(0), Future): - multiplier *= self.sid(0).multiplier - - self.order_value( - self.sid(0), - data.current(sid(0), "price") * multiplier - ) - - -class TestTargetAlgorithm(TradingAlgorithm): - def initialize(self): - self.set_slippage(FixedSlippage()) - self.target_shares = 0 - self.sale_price = None - - def handle_data(self, data): - if self.target_shares == 0: - assert 0 not in self.portfolio.positions - else: - assert ( - self.portfolio.positions[0].amount == - self.target_shares - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - self.target_shares = 10 - self.order_target(self.sid(0), self.target_shares) - - -class TestOrderPercentAlgorithm(TradingAlgorithm): - def initialize(self): - self.set_slippage(FixedSlippage()) - self.target_shares = 0 - self.sale_price = None - - def handle_data(self, data): - if self.target_shares == 0: - assert 0 not in self.portfolio.positions - self.order(self.sid(0), 10) - self.target_shares = 10 - return - else: - assert ( - self.portfolio.positions[0].amount == - self.target_shares - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - - self.order_percent(self.sid(0), .001) - - if isinstance(self.sid(0), Equity): - price = data.current(sid(0), "price") - new_shares = (.001 * self.portfolio.portfolio_value) / price - elif isinstance(self.sid(0), Future): - new_shares = (.001 * self.portfolio.portfolio_value) / \ - (data.current(sid(0), "price") * - self.sid(0).contract_multiplier) - - new_shares = int(round_if_near_integer(new_shares)) - self.target_shares += new_shares - - -class TestTargetPercentAlgorithm(TradingAlgorithm): - def initialize(self): - self.ordered = False - self.sale_price = None - - # this makes the math easier to check - self.set_slippage(FixedSlippage()) - self.set_commission(PerShare(0)) - - def handle_data(self, data): - if not self.ordered: - assert not self.portfolio.positions - else: - # Since you can't own fractional shares (at least in this - # example), we want to make sure that our target amount is - # no more than a share's value away from our current - # holdings. - target_value = self.portfolio.portfolio_value * 0.002 - position_value = self.portfolio.positions[0].amount * \ - self.sale_price - - assert ( - abs(target_value - position_value) <= - self.sale_price - ), "Orders not filled correctly" - - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current price." - - self.sale_price = data.current(sid(0), "price") - self._order(sid(0), .002) - self.ordered = True - - def _order(self, asset, target): - return self.order_target_percent(asset, target) - - -class TestTargetValueAlgorithm(TradingAlgorithm): - def initialize(self): - self.set_slippage(FixedSlippage()) - self.target_shares = 0 - self.sale_price = None - - def handle_data(self, data): - if self.target_shares == 0: - assert 0 not in self.portfolio.positions - self.order(self.sid(0), 10) - self.target_shares = 10 - return - else: - assert ( - self.portfolio.positions[0].amount == - self.target_shares - ), "Orders not filled immediately." - assert ( - self.portfolio.positions[0].last_sale_date == - self.get_datetime() - ), "Orders not filled at current datetime." - - self.order_target_value(self.sid(0), 20) - self.target_shares = np.round(20 / data.current(sid(0), "price")) - - if isinstance(self.sid(0), Equity): - self.target_shares = np.round(20 / data.current(sid(0), "price")) - if isinstance(self.sid(0), Future): - self.target_shares = np.round( - 20 / (data.current(sid(0), "price") * self.sid(0).multiplier)) - - class FutureFlipAlgo(TestAlgorithm): def handle_data(self, data): if len(self.portfolio.positions) > 0: @@ -567,14 +331,6 @@ def handle_data(algo, data): algo.order(algo.sid(999), 1) -class TestRegisterTransformAlgorithm(TradingAlgorithm): - def initialize(self, *args, **kwargs): - self.set_slippage(FixedSlippage()) - - def handle_data(self, data): - pass - - class AmbitiousStopLimitAlgorithm(TradingAlgorithm): """ Algorithm that tries to buy with extremely low stops/limits and tries to From 4a4c567b4656f318f082aa68e4603aafd2fc6486 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 01:19:02 -0400 Subject: [PATCH 04/14] MAINT: Remove unused testing algorithms. --- zipline/test_algorithms.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index 6c5515f593..d646147c02 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -213,31 +213,6 @@ def handle_data(self, data): pass -class TooMuchProcessingAlgorithm(TradingAlgorithm): - - def initialize(self, sid): - self.asset = self.sid(sid) - - def handle_data(self, data): - # Unless we're running on some sort of - # supercomputer this will hit timeout. - for i in range(1000000000): - self.foo = i - - -class TimeoutAlgorithm(TradingAlgorithm): - - def initialize(self, sid): - self.asset = self.sid(sid) - self.incr = 0 - - def handle_data(self, data): - if self.incr > 4: - import time - time.sleep(100) - pass - - class FutureFlipAlgo(TestAlgorithm): def handle_data(self, data): if len(self.portfolio.positions) > 0: From af3c6b6f419dfa43530ae297400720eced13dc42 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 01:30:25 -0400 Subject: [PATCH 05/14] MAINT: Use WithMakeAlgo in TestBeforeTradingStart. --- tests/test_algorithm.py | 81 +++++++++++++---------------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 8d9df5ce44..39b27f9083 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -1002,9 +1002,7 @@ def test_position_weights(self): assert_equal(daily_stats.iloc[i]['position_weights'], expected) -class TestBeforeTradingStart(WithDataPortal, - WithSimParams, - ZiplineTestCase): +class TestBeforeTradingStart(zf.WithMakeAlgo, zf.ZiplineTestCase): START_DATE = pd.Timestamp('2016-01-06', tz='utc') END_DATE = pd.Timestamp('2016-01-07', tz='utc') SIM_PARAMS_CAPITAL_BASE = 10000 @@ -1047,13 +1045,13 @@ def make_equity_minute_bar_data(cls): yield sid, create_minute_df_for_asset( cls.trading_calendar, cls.data_start, - cls.sim_params.end_session, + cls.END_DATE, ) yield 2, create_minute_df_for_asset( cls.trading_calendar, cls.data_start, - cls.sim_params.end_session, + cls.END_DATE, 50, ) yield cls.SPLIT_ASSET_SID, split_data @@ -1074,7 +1072,7 @@ def make_equity_daily_bar_data(cls): yield sid, create_daily_df_for_asset( cls.trading_calendar, cls.data_start, - cls.sim_params.end_session, + cls.END_DATE, ) def test_data_in_bts_minute(self): @@ -1100,13 +1098,8 @@ def handle_data(context, data): pass """) - algo = TradingAlgorithm( - script=algo_code, - sim_params=self.sim_params, - env=self.env - ) - - results = algo.run(self.data_portal) + algo = self.make_algo(script=algo_code) + results = algo.run() # fetching data at midnight gets us the previous market minute's data self.assertEqual(390, results.iloc[0].the_price1) @@ -1172,13 +1165,8 @@ def handle_data(context, data): pass """) - algo = TradingAlgorithm( - script=algo_code, - sim_params=self.sim_params, - env=self.env - ) - - results = algo.run(self.data_portal) + algo = self.make_algo(script=algo_code) + results = algo.run() self.assertEqual(392, results.the_high1[0]) self.assertEqual(390, results.the_price1[0]) @@ -1217,14 +1205,8 @@ def handle_data(context, data): context.hd_portfolio = context.portfolio """) - algo = TradingAlgorithm( - script=algo_code, - data_frequency="minute", - sim_params=self.sim_params, - env=self.env - ) - - results = algo.run(self.data_portal) + algo = self.make_algo(script=algo_code) + results = algo.run() # Asset starts with price 1 on 1/05 and increases by 1 every minute. # Simulation starts on 1/06, where the price in bts is 390, and @@ -1235,11 +1217,12 @@ def handle_data(context, data): def test_account_bts(self): algo_code = dedent(""" - from zipline.api import order, sid, record + from zipline.api import order, sid, record, set_slippage, slippage def initialize(context): context.ordered = False context.hd_account = context.account + set_slippage(slippage.VolumeShareSlippage()) def before_trading_start(context, data): bts_account = context.account @@ -1256,15 +1239,8 @@ def handle_data(context, data): context.hd_account = context.account """) - algo = TradingAlgorithm( - script=algo_code, - data_frequency="minute", - sim_params=self.sim_params, - env=self.env, - ) - algo.set_slippage(VolumeShareSlippage()) - - results = algo.run(self.data_portal) + algo = self.make_algo(script=algo_code) + results = algo.run() # Starting portfolio value is 10000. Order for the asset fills on the # second bar of 1/06, where the price is 391, and costs the default @@ -1278,9 +1254,11 @@ def handle_data(context, data): def test_portfolio_bts_with_overnight_split(self): algo_code = dedent(""" from zipline.api import order, sid, record + def initialize(context): context.ordered = False context.hd_portfolio = context.portfolio + def before_trading_start(context, data): bts_portfolio = context.portfolio # Assert that the portfolio in BTS is the same as the last @@ -1294,6 +1272,7 @@ def before_trading_start(context, data): record( last_sale_price=bts_portfolio.positions[sid(3)].last_sale_price ) + def handle_data(context, data): if not context.ordered: order(sid(3), 1) @@ -1301,14 +1280,7 @@ def handle_data(context, data): context.hd_portfolio = context.portfolio """) - algo = TradingAlgorithm( - script=algo_code, - data_frequency="minute", - sim_params=self.sim_params, - env=self.env - ) - - results = algo.run(self.data_portal) + results = self.run_algorithm(script=algo_code) # On 1/07, positions value should by 780, same as without split self.assertEqual(results.pos_value.iloc[0], 0) @@ -1324,16 +1296,21 @@ def handle_data(context, data): def test_account_bts_with_overnight_split(self): algo_code = dedent(""" - from zipline.api import order, sid, record + from zipline.api import order, sid, record, set_slippage, slippage + def initialize(context): context.ordered = False context.hd_account = context.account + set_slippage(slippage.VolumeShareSlippage()) + + def before_trading_start(context, data): bts_account = context.account # Assert that the account in BTS is the same as the last account # in handle_data assert (context.hd_account == bts_account) record(port_value=bts_account.equity_with_loan) + def handle_data(context, data): if not context.ordered: order(sid(1), 1) @@ -1341,15 +1318,7 @@ def handle_data(context, data): context.hd_account = context.account """) - algo = TradingAlgorithm( - script=algo_code, - data_frequency="minute", - sim_params=self.sim_params, - env=self.env, - ) - algo.set_slippage(VolumeShareSlippage()) - - results = algo.run(self.data_portal) + results = self.run_algorithm(script=algo_code) # On 1/07, portfolio value is the same as without split self.assertEqual(results.port_value.iloc[0], 10000) From a52de4c08490e7c74c7f55a4ac13b3a70e6404c3 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:11:44 -0400 Subject: [PATCH 06/14] MAINT: Use WithMakeAlgo in TestAlgoScript. --- tests/test_algorithm.py | 254 ++++++++++------------------------------ 1 file changed, 60 insertions(+), 194 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 39b27f9083..64ef0efbb2 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -23,7 +23,6 @@ import logbook import toolz from logbook import TestHandler, WARNING -from mock import MagicMock from nose_parameterized import parameterized from six import iteritems, itervalues, string_types from six.moves import range @@ -57,7 +56,6 @@ AccountControlViolation, CannotOrderDelistedAsset, IncompatibleSlippageModel, - OrderDuringInitialize, RegisterTradingControlPostInit, ScheduleFunctionInvalidCalendar, SetCancelPolicyPostInit, @@ -77,7 +75,6 @@ StaticRestrictions, RESTRICTION_STATES, ) -from zipline.finance.slippage import VolumeShareSlippage from zipline.testing import ( FakeDataPortal, copy_market_data, @@ -115,8 +112,6 @@ api_algo, api_get_environment_algo, api_symbol_algo, - call_all_order_methods, - call_order_in_init, handle_data_api, handle_data_noop, initialize_api, @@ -151,7 +146,7 @@ ) import zipline.test_algorithms as zta from zipline.testing.predicates import assert_equal -from zipline.utils.api_support import ZiplineAPI, set_algo_instance +from zipline.utils.api_support import ZiplineAPI from zipline.utils.calendars import get_calendar, register_calendar from zipline.utils.context_tricks import CallbackManager, nop_context from zipline.utils.events import ( @@ -1326,12 +1321,10 @@ def handle_data(context, data): 10000 + 780 - 392 - 0, places=2) -class TestAlgoScript(WithLogger, - WithDataPortal, - WithSimParams, - ZiplineTestCase): +class TestAlgoScript(zf.WithMakeAlgo, zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-03', tz='utc') END_DATE = pd.Timestamp('2006-12-31', tz='utc') + SIM_PARAMS_DATA_FREQUENCY = 'daily' DATA_PORTAL_USE_MINUTE_DATA = False EQUITY_DAILY_BAR_LOOKBACK_DAYS = 5 # max history window length @@ -1387,6 +1380,9 @@ class TestAlgoScript(WithLogger, sids = 0, 1, 3, 133 + # FIXME: Pass a benchmark explicitly here. + BENCHMARK_SID = None + @classmethod def make_equity_info(cls): register_calendar("TEST", get_calendar("NYSE"), force=True) @@ -1401,69 +1397,52 @@ def make_equity_info(cls): @classmethod def make_equity_daily_bar_data(cls): - days = len(cls.equity_daily_bar_days) - return trades_by_sid_to_dfs( - { - 0: factory.create_trade_history( - 0, - [10.0] * days, - [100] * days, - timedelta(days=1), - cls.sim_params, - cls.trading_calendar), - 3: factory.create_trade_history( - 3, - [10.0] * days, - [100] * days, - timedelta(days=1), - cls.sim_params, - cls.trading_calendar) - }, - index=cls.equity_daily_bar_days, - ) + cal = cls.trading_calendars[Equity] + sessions = cal.sessions_in_range(cls.START_DATE, cls.END_DATE) + frame = pd.DataFrame({ + 'close': 10., 'high': 10.5, 'low': 9.5, 'open': 10., 'volume': 100, + }, index=sessions) + + for sid in cls.sids: + yield sid, frame def test_noop(self): - algo = TradingAlgorithm(initialize=initialize_noop, - handle_data=handle_data_noop, - env=self.env) - algo.run(self.data_portal) + self.run_algorithm( + initialize=initialize_noop, + handle_data=handle_data_noop, + ) def test_noop_string(self): - algo = TradingAlgorithm(script=noop_algo, env=self.env) - algo.run(self.data_portal) + self.run_algorithm(script=noop_algo) def test_no_handle_data(self): - algo = TradingAlgorithm(script=no_handle_data, env=self.env) - algo.run(self.data_portal) + self.run_algorithm(script=no_handle_data) def test_api_calls(self): - algo = TradingAlgorithm(initialize=initialize_api, - handle_data=handle_data_api, - env=self.env) - algo.run(self.data_portal) + self.run_algorithm( + initialize=initialize_api, + handle_data=handle_data_api, + ) def test_api_calls_string(self): - algo = TradingAlgorithm(script=api_algo, env=self.env) - algo.run(self.data_portal) + self.run_algorithm(script=api_algo) def test_api_get_environment(self): platform = 'zipline' - algo = TradingAlgorithm(script=api_get_environment_algo, - platform=platform, - env=self.env) - algo.run(self.data_portal) + algo = self.make_algo( + script=api_get_environment_algo, + platform=platform, + ) + algo.run() self.assertEqual(algo.environment, platform) def test_api_symbol(self): - algo = TradingAlgorithm(script=api_symbol_algo, - env=self.env, - sim_params=self.sim_params) - algo.run(self.data_portal) + self.run_algorithm(script=api_symbol_algo) def test_fixed_slippage(self): # verify order -> transaction -> portfolio position. # -------------- - test_algo = TradingAlgorithm( + test_algo = self.make_algo( script=""" from zipline.api import (slippage, commission, @@ -1486,10 +1465,8 @@ def handle_data(context, data): record(price=data.current(sid(0), "price")) context.incr += 1""", - sim_params=self.sim_params, - env=self.env, ) - results = test_algo.run(self.data_portal) + results = test_algo.run() # flatten the list of txns all_txns = [val for sublist in results["transactions"].tolist() @@ -1529,7 +1506,7 @@ def test_volshare_slippage(self, name, minimum_commission): # verify order -> transaction -> portfolio position. # -------------- - test_algo = TradingAlgorithm( + test_algo = self.make_algo( script=""" from zipline.api import * @@ -1554,8 +1531,6 @@ def handle_data(context, data): record(incr=context.incr) context.incr += 1 """.format(commission_line), - sim_params=self.sim_params, - env=self.env, ) trades = factory.create_daily_trade_source( [0], self.sim_params, self.env, self.trading_calendar) @@ -1607,69 +1582,30 @@ def initialize(context): set_slippage(MySlippage()) """ ) - test_algo = TradingAlgorithm( - script=code, sim_params=self.sim_params, env=self.env, - ) + test_algo = self.make_algo(script=code) with self.assertRaises(IncompatibleSlippageModel): # Passing a futures slippage model as the first argument, which is # for setting equity models, should fail. - test_algo.run(self.data_portal) + test_algo.run() def test_algo_record_vars(self): - test_algo = TradingAlgorithm( - script=record_variables, - sim_params=self.sim_params, - env=self.env, - ) + test_algo = self.make_algo(script=record_variables) results = test_algo.run(self.data_portal) for i in range(1, 252): self.assertEqual(results.iloc[i-1]["incr"], i) - def test_algo_record_allow_mock(self): - """ - Test that values from "MagicMock"ed methods can be passed to record. - - Relevant for our basic/validation and methods like history, which - will end up returning a MagicMock instead of a DataFrame. - """ - test_algo = TradingAlgorithm( - script=record_variables, - sim_params=self.sim_params, - env=self.env, - ) - set_algo_instance(test_algo) - - test_algo.record(foo=MagicMock()) - def test_algo_record_nan(self): - test_algo = TradingAlgorithm( - script=record_float_magic % 'nan', - sim_params=self.sim_params, - env=self.env, - ) - results = test_algo.run(self.data_portal) - + test_algo = self.make_algo(script=record_float_magic % 'nan') + results = test_algo.run() for i in range(1, 252): self.assertTrue(np.isnan(results.iloc[i-1]["data"])) - def test_order_methods(self): - """ - Only test that order methods can be called without error. - Correct filling of orders is tested in zipline. - """ - test_algo = TradingAlgorithm( - script=call_all_order_methods, - sim_params=self.sim_params, - env=self.env, - ) - test_algo.run(self.data_portal) - def test_batch_market_order_matches_multiple_manual_orders(self): share_counts = pd.Series([50, 100]) multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) - multi_test_algo = TradingAlgorithm( + multi_test_algo = self.make_algo( script=dedent("""\ from collections import OrderedDict from six import iteritems @@ -1691,13 +1627,12 @@ def handle_data(context, data): """).format(share_counts=list(share_counts)), blotter=multi_blotter, - env=self.env, ) multi_stats = multi_test_algo.run(self.data_portal) self.assertFalse(multi_blotter.order_batch_called) batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) - batch_test_algo = TradingAlgorithm( + batch_test_algo = self.make_algo( script=dedent("""\ import pandas as pd @@ -1722,9 +1657,8 @@ def handle_data(context, data): """).format(share_counts=list(share_counts)), blotter=batch_blotter, - env=self.env, ) - batch_stats = batch_test_algo.run(self.data_portal) + batch_stats = batch_test_algo.run() self.assertTrue(batch_blotter.order_batch_called) for stats in (multi_stats, batch_stats): @@ -1740,7 +1674,7 @@ def test_batch_market_order_filters_null_orders(self): share_counts = [50, 0] batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) - batch_test_algo = TradingAlgorithm( + batch_test_algo = self.make_algo( script=dedent("""\ import pandas as pd @@ -1764,9 +1698,8 @@ def handle_data(context, data): """).format(share_counts=share_counts), blotter=batch_blotter, - env=self.env, ) - batch_test_algo.run(self.data_portal) + batch_test_algo.run() self.assertTrue(batch_blotter.order_batch_called) def test_order_dead_asset(self): @@ -1778,7 +1711,7 @@ def test_order_dead_asset(self): ) # order method shouldn't blow up - test_algo = TradingAlgorithm( + self.run_algorithm( script=""" from zipline.api import order, sid @@ -1788,15 +1721,11 @@ def initialize(context): def handle_data(context, data): order(sid(0), 10) """, - sim_params=params, - env=self.env ) - test_algo.run(self.data_portal) - # order_value and order_percent should blow up for order_str in ["order_value", "order_percent"]: - test_algo = TradingAlgorithm( + test_algo = self.make_algo( script=""" from zipline.api import order_percent, order_value, sid @@ -1807,65 +1736,34 @@ def handle_data(context, data): {0}(sid(0), 10) """.format(order_str), sim_params=params, - env=self.env ) with self.assertRaises(CannotOrderDelistedAsset): - test_algo.run(self.data_portal) - - def test_order_in_init(self): - """ - Test that calling order in initialize - will raise an error. - """ - with self.assertRaises(OrderDuringInitialize): - test_algo = TradingAlgorithm( - script=call_order_in_init, - sim_params=self.sim_params, - env=self.env, - ) - test_algo.run(self.data_portal) + test_algo.run() def test_portfolio_in_init(self): """ Test that accessing portfolio in init doesn't break. """ - test_algo = TradingAlgorithm( - script=access_portfolio_in_init, - sim_params=self.sim_params, - env=self.env, - ) - test_algo.run(self.data_portal) + self.run_algorithm(script=access_portfolio_in_init) def test_account_in_init(self): """ Test that accessing account in init doesn't break. """ - test_algo = TradingAlgorithm( - script=access_account_in_init, - sim_params=self.sim_params, - env=self.env, - ) - test_algo.run(self.data_portal) + self.run_algorithm(script=access_account_in_init) def test_without_kwargs(self): """ Test that api methods on the data object can be called with positional arguments. """ - params = SimulationParameters( start_session=pd.Timestamp("2006-01-10", tz='UTC'), end_session=pd.Timestamp("2006-01-11", tz='UTC'), trading_calendar=self.trading_calendar, ) - - test_algo = TradingAlgorithm( - script=call_without_kwargs, - sim_params=params, - env=self.env, - ) - test_algo.run(self.data_portal) + self.run_algorithm(sim_params=params, script=call_without_kwargs) def test_good_kwargs(self): """ @@ -1877,13 +1775,7 @@ def test_good_kwargs(self): end_session=pd.Timestamp("2006-01-11", tz='UTC'), trading_calendar=self.trading_calendar, ) - - test_algo = TradingAlgorithm( - script=call_with_kwargs, - sim_params=params, - env=self.env, - ) - test_algo.run(self.data_portal) + self.run_algorithm(script=call_with_kwargs, sim_params=params) @parameterized.expand([('history', call_with_bad_kwargs_history), ('current', call_with_bad_kwargs_current)]) @@ -1893,13 +1785,9 @@ def test_bad_kwargs(self, name, algo_text): a meaningful TypeError that we create, rather than an unhelpful cython error """ + algo = self.make_algo(script=algo_text) with self.assertRaises(TypeError) as cm: - test_algo = TradingAlgorithm( - script=algo_text, - sim_params=self.sim_params, - env=self.env, - ) - test_algo.run(self.data_portal) + algo.run() self.assertEqual("%s() got an unexpected keyword argument 'blahblah'" % name, cm.exception.args[0]) @@ -1909,13 +1797,9 @@ def test_arg_types(self, name, inputs): keyword = name.split('__')[1] + algo = self.make_algo(script=inputs[0]) with self.assertRaises(TypeError) as cm: - algo = TradingAlgorithm( - script=inputs[0], - sim_params=self.sim_params, - env=self.env - ) - algo.run(self.data_portal) + algo.run() expected = "Expected %s argument to be of type %s%s" % ( keyword, @@ -1932,7 +1816,7 @@ def test_empty_asset_list_to_history(self): trading_calendar=self.trading_calendar, ) - algo = TradingAlgorithm( + self.run_algorithm( script=dedent(""" def initialize(context): pass @@ -1941,23 +1825,15 @@ def handle_data(context, data): data.history([], "price", 5, '1d') """), sim_params=params, - env=self.env ) - algo.run(self.data_portal) - @parameterized.expand( [('bad_kwargs', call_with_bad_kwargs_get_open_orders), ('good_kwargs', call_with_good_kwargs_get_open_orders), ('no_kwargs', call_with_no_kwargs_get_open_orders)] ) def test_get_open_orders_kwargs(self, name, script): - algo = TradingAlgorithm( - script=script, - sim_params=self.sim_params, - env=self.env - ) - + algo = self.make_algo(script=script) if name == 'bad_kwargs': with self.assertRaises(TypeError) as cm: algo.run(self.data_portal) @@ -1974,13 +1850,7 @@ def test_empty_positions(self): (but more importantly, we don't crash) and don't save this Position to the user-facing dictionary PositionTracker._positions_store """ - algo = TradingAlgorithm( - script=empty_positions, - sim_params=self.sim_params, - env=self.env - ) - - results = algo.run(self.data_portal) + results = self.run_algorithm(script=empty_positions) num_positions = results.num_positions amounts = results.amounts self.assertTrue(all(num_positions == 0)) @@ -2022,12 +1892,8 @@ def handle_data(algo, data): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("ignore", PerformanceWarning) - algo = TradingAlgorithm( - script=algocode, - sim_params=sim_params, - env=self.env - ) - algo.run(self.data_portal) + algo = self.make_algo(script=algocode, sim_params=sim_params) + algo.run() self.assertEqual(len(w), 2) From f745d3d36f95f8c988d00767c69bf1a8d8ef3771 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:12:05 -0400 Subject: [PATCH 07/14] MAINT: Remove unused test algos. --- zipline/test_algorithms.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index d646147c02..3d6a06b12d 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -626,17 +626,6 @@ def handle_data(context, data): order(symbol('TEST'), 1) """ -call_order_in_init = """ -from zipline.api import (sid, order) - -def initialize(context): - order(sid(0), 10) - pass - -def handle_data(context, data): - pass -""" - access_portfolio_in_init = """ def initialize(context): var = context.portfolio.cash @@ -655,27 +644,6 @@ def handle_data(context, data): pass """ -call_all_order_methods = """ -from zipline.api import (order, - order_value, - order_percent, - order_target, - order_target_value, - order_target_percent, - sid) - -def initialize(context): - pass - -def handle_data(context, data): - order(sid(0), 10) - order_value(sid(0), 300) - order_percent(sid(0), .1) - order_target(sid(0), 100) - order_target_value(sid(0), 100) - order_target_percent(sid(0), .2) -""" - record_variables = """ from zipline.api import record From 4a365cd292854aa940d0fc1d154f67b0820ab6de Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:15:36 -0400 Subject: [PATCH 08/14] MAINT: Remove unused factory functions. --- zipline/utils/factory.py | 71 ---------------------------------------- 1 file changed, 71 deletions(-) diff --git a/zipline/utils/factory.py b/zipline/utils/factory.py index db8f418ff3..84ee64bc4c 100644 --- a/zipline/utils/factory.py +++ b/zipline/utils/factory.py @@ -21,13 +21,9 @@ import numpy as np from datetime import timedelta, datetime -from zipline.assets import Asset -from zipline.finance.transaction import Transaction -from zipline.protocol import Event, DATASOURCE_TYPE from zipline.sources import SpecificEquityTrades from zipline.finance.trading import SimulationParameters from zipline.sources.test_source import create_trade -from zipline.utils.input_validation import expect_types from zipline.utils.calendars import get_calendar @@ -106,73 +102,6 @@ def create_trade_history(sid, prices, amounts, interval, sim_params, return trades -def create_dividend(sid, payment, declared_date, ex_date, pay_date): - div = Event({ - 'sid': sid, - 'gross_amount': payment, - 'net_amount': payment, - 'payment_sid': None, - 'ratio': None, - 'declared_date': pd.tslib.normalize_date(declared_date), - 'ex_date': pd.tslib.normalize_date(ex_date), - 'pay_date': pd.tslib.normalize_date(pay_date), - 'type': DATASOURCE_TYPE.DIVIDEND, - 'source_id': 'MockDividendSource' - }) - return div - - -def create_stock_dividend(sid, payment_sid, ratio, declared_date, - ex_date, pay_date): - return Event({ - 'sid': sid, - 'payment_sid': payment_sid, - 'ratio': ratio, - 'net_amount': None, - 'gross_amount': None, - 'dt': pd.tslib.normalize_date(declared_date), - 'ex_date': pd.tslib.normalize_date(ex_date), - 'pay_date': pd.tslib.normalize_date(pay_date), - 'type': DATASOURCE_TYPE.DIVIDEND, - 'source_id': 'MockDividendSource' - }) - - -def create_split(sid, ratio, date): - return Event({ - 'sid': sid, - 'ratio': ratio, - 'dt': date.replace(hour=0, minute=0, second=0, microsecond=0), - 'type': DATASOURCE_TYPE.SPLIT, - 'source_id': 'MockSplitSource' - }) - - -@expect_types(asset=Asset) -def create_txn(asset, price, amount, datetime, order_id): - return Transaction( - asset=asset, - price=price, - amount=amount, - dt=datetime, - order_id=order_id, - ) - - -@expect_types(asset=Asset) -def create_txn_history(asset, priceList, amtList, interval, sim_params, - trading_calendar): - txns = [] - current = sim_params.first_open - - for price, amount in zip(priceList, amtList): - dt = get_next_trading_dt(current, interval, trading_calendar) - - txns.append(create_txn(asset, price, amount, dt, None)) - current = current + interval - return txns - - def create_returns_from_range(sim_params): return pd.Series(index=sim_params.sessions, data=np.random.rand(len(sim_params.sessions))) From 1ddf6ef9075022d3936009b87dea0186cd49c2e4 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:29:56 -0400 Subject: [PATCH 09/14] MAINT: Put back test for ordering in initialize. --- tests/test_ordering.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_ordering.py b/tests/test_ordering.py index 165e0cf079..47d25a96bb 100644 --- a/tests/test_ordering.py +++ b/tests/test_ordering.py @@ -316,6 +316,17 @@ def test_invalid_order_parameters(self): sids=[1], ) + def test_cant_order_in_initialize(self): + algotext = """ +from zipline.api import (sid, order) + +def initialize(context): + order(sid(1), 10)""" + + algo = self.make_algo(script=algotext) + with self.assertRaises(ze.OrderDuringInitialize): + algo.run() + class TestOrderRounding(zf.ZiplineTestCase): From 68f0b9bb4a84d4397ffd8584567b0d38d5334904 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:40:30 -0400 Subject: [PATCH 10/14] MAINT: Use WithMakeAlgo in TestCapitalChanges. --- tests/test_algorithm.py | 127 +++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 64ef0efbb2..b3e314f7ad 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -1922,69 +1922,73 @@ def handle_data(algo, data): ) -class TestCapitalChanges(WithLogger, - WithDataPortal, - WithSimParams, - ZiplineTestCase): +class TestCapitalChanges(zf.WithMakeAlgo, zf.ZiplineTestCase): - sids = 0, 1 + START_DATE = pd.Timestamp('2006-01-03', tz='UTC') + END_DATE = pd.Timestamp('2006-01-09', tz='UTC') - @classmethod - def make_equity_info(cls): - data = make_simple_equity_info( - cls.sids, - pd.Timestamp('2006-01-03', tz='UTC'), - pd.Timestamp('2006-01-09', tz='UTC'), - ) - return data + # XXX: This suite only has daily data for sid 0 and only has minutely data + # for sid 1. + sids = ASSET_FINDER_EQUITY_SIDS = (0, 1) + DAILY_SID = 0 + MINUTELY_SID = 1 + + # FIXME: Pass a benchmark source explicitly here. + BENCHMARK_SID = None @classmethod def make_equity_minute_bar_data(cls): minutes = cls.trading_calendar.minutes_in_range( - pd.Timestamp('2006-01-03', tz='UTC'), - pd.Timestamp('2006-01-09', tz='UTC') + cls.START_DATE, + cls.END_DATE, ) - return trades_by_sid_to_dfs( - { - 1: factory.create_trade_history( - 1, - np.arange(100.0, 100.0 + len(minutes), 1), - [10000] * len(minutes), - timedelta(minutes=1), - cls.sim_params, - cls.trading_calendar), + closes = np.arange(100, 100 + len(minutes), 1) + opens = closes + highs = closes + 5 + lows = closes - 5 + + frame = pd.DataFrame( + index=minutes, + data={ + 'open': opens, + 'high': highs, + 'low': lows, + 'close': closes, + 'volume': 10000, }, - index=pd.DatetimeIndex(minutes), ) + yield cls.MINUTELY_SID, frame + @classmethod def make_equity_daily_bar_data(cls): days = cls.trading_calendar.sessions_in_range( - pd.Timestamp('2006-01-03', tz='UTC'), - pd.Timestamp('2006-01-09', tz='UTC') + cls.START_DATE, + cls.END_DATE, ) - return trades_by_sid_to_dfs( - { - 0: factory.create_trade_history( - 0, - np.arange(10.0, 10.0 + len(days), 1.0), - [10000] * len(days), - timedelta(days=1), - cls.sim_params, - cls.trading_calendar), + + closes = np.arange(10.0, 10.0 + len(days), 1.0) + opens = closes + highs = closes + 0.5 + lows = closes - 0.5 + + frame = pd.DataFrame( + index=days, + data={ + 'open': opens, + 'high': highs, + 'low': lows, + 'close': closes, + 'volume': 10000, }, - index=pd.DatetimeIndex(days), ) + yield cls.DAILY_SID, frame + @parameterized.expand([ ('target', 151000.0), ('delta', 50000.0) ]) def test_capital_changes_daily_mode(self, change_type, value): - sim_params = factory.create_simulation_parameters( - start=pd.Timestamp('2006-01-03', tz='UTC'), - end=pd.Timestamp('2006-01-09', tz='UTC') - ) - capital_changes = { pd.Timestamp('2006-01-06', tz='UTC'): {'type': change_type, 'value': value} @@ -2002,15 +2006,18 @@ def initialize(context): def order_stuff(context, data): order(sid(0), 1000) """ - - algo = TradingAlgorithm( + algo = self.make_algo( script=algocode, - sim_params=sim_params, - env=self.env, - data_portal=self.data_portal, capital_changes=capital_changes, + sim_params=SimulationParameters( + start_session=self.START_DATE, + end_session=self.END_DATE, + trading_calendar=self.nyse_calendar, + ) ) + # We call get_generator rather than `run()` here because we care about + # the raw capital change packets. gen = algo.get_generator() results = list(gen) @@ -2142,11 +2149,12 @@ def order_stuff(context, data): def test_capital_changes_minute_mode_daily_emission(self, change, values): change_loc, change_type = change.split('_') - sim_params = factory.create_simulation_parameters( - start=pd.Timestamp('2006-01-03', tz='UTC'), - end=pd.Timestamp('2006-01-05', tz='UTC'), + sim_params = SimulationParameters( + start_session=pd.Timestamp('2006-01-03', tz='UTC'), + end_session=pd.Timestamp('2006-01-05', tz='UTC'), data_frequency='minute', - capital_base=1000.0 + capital_base=1000.0, + trading_calendar=self.nyse_calendar, ) capital_changes = { @@ -2170,11 +2178,9 @@ def order_stuff(context, data): order(sid(1), 1) """ - algo = TradingAlgorithm( + algo = self.make_algo( script=algocode, sim_params=sim_params, - env=self.env, - data_portal=self.data_portal, capital_changes=capital_changes ) @@ -2313,12 +2319,13 @@ def order_stuff(context, data): def test_capital_changes_minute_mode_minute_emission(self, change, values): change_loc, change_type = change.split('_') - sim_params = factory.create_simulation_parameters( - start=pd.Timestamp('2006-01-03', tz='UTC'), - end=pd.Timestamp('2006-01-05', tz='UTC'), + sim_params = SimulationParameters( + start_session=pd.Timestamp('2006-01-03', tz='UTC'), + end_session=pd.Timestamp('2006-01-05', tz='UTC'), data_frequency='minute', emission_rate='minute', - capital_base=1000.0 + capital_base=1000.0, + trading_calendar=self.nyse_calendar, ) capital_changes = {pd.Timestamp(val[0], tz='UTC'): { @@ -2337,11 +2344,9 @@ def order_stuff(context, data): order(sid(1), 1) """ - algo = TradingAlgorithm( + algo = self.make_algo( script=algocode, sim_params=sim_params, - env=self.env, - data_portal=self.data_portal, capital_changes=capital_changes ) From 5e09bbd1f883755dafb4428990a86c9178489ebf Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:42:26 -0400 Subject: [PATCH 11/14] MAINT: Use WithMakeAlgo in TestGetDatetime. --- tests/test_algorithm.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index b3e314f7ad..43f00a0340 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -2549,16 +2549,16 @@ def order_stuff(context, data): ) -class TestGetDatetime(WithLogger, - WithSimParams, - WithDataPortal, - ZiplineTestCase): +class TestGetDatetime(zf.WithMakeAlgo, zf.ZiplineTestCase): SIM_PARAMS_DATA_FREQUENCY = 'minute' START_DATE = to_utc('2014-01-02 9:31') END_DATE = to_utc('2014-01-03 9:31') ASSET_FINDER_EQUITY_SIDS = 0, 1 + # FIXME: Pass a benchmark source explicitly here. + BENCHMARK_SID = None + @parameterized.expand( [ ('default', None,), @@ -2591,12 +2591,8 @@ def handle_data(context, data): """.format(tz=repr(tz)) ) - algo = TradingAlgorithm( - script=algo, - sim_params=self.sim_params, - env=self.env, - ) - algo.run(self.data_portal) + algo = self.make_algo(script=algo) + algo.run() self.assertFalse(algo.first_bar) From 524d0ea72b9a9addcca3813dfe96afba41f6c3b2 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:53:11 -0400 Subject: [PATCH 12/14] MAINT: Use WithMakeAlgo for TestOrderAfterDelist. --- tests/test_algorithm.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 43f00a0340..dd70215eb9 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -4324,12 +4324,15 @@ def transactions_for_date(date): ) -class TestOrderAfterDelist(WithTradingEnvironment, ZiplineTestCase): +class TestOrderAfterDelist(zf.WithMakeAlgo, zf.ZiplineTestCase): start = pd.Timestamp('2016-01-05', tz='utc') day_1 = pd.Timestamp('2016-01-06', tz='utc') day_4 = pd.Timestamp('2016-01-11', tz='utc') end = pd.Timestamp('2016-01-15', tz='utc') + # FIXME: Pass a benchmark source here. + BENCHMARK_SID = None + @classmethod def make_equity_info(cls): return pd.DataFrame.from_dict( @@ -4354,10 +4357,11 @@ def make_equity_info(cls): orient='index', ) - @classmethod - def init_class_fixtures(cls): - super(TestOrderAfterDelist, cls).init_class_fixtures() - cls.data_portal = FakeDataPortal(cls.env) + # XXX: This suite doesn't use the data in its DataPortal; it uses a + # FakeDataPortal with different mock data. + def init_instance_fixtures(self): + super(TestOrderAfterDelist, self).init_instance_fixtures() + self.data_portal = FakeDataPortal(self.env) @parameterized.expand([ ('auto_close_after_end_date', 1), @@ -4390,9 +4394,8 @@ def handle_data(context, data): """).format(sid=sid) # run algo from 1/6 to 1/7 - algo = TradingAlgorithm( + algo = self.make_algo( script=algo_code, - env=self.env, sim_params=SimulationParameters( start_session=pd.Timestamp("2016-01-06", tz='UTC'), end_session=pd.Timestamp("2016-01-07", tz='UTC'), @@ -4400,9 +4403,8 @@ def handle_data(context, data): data_frequency="minute" ) ) - with make_test_handler(self) as log_catcher: - algo.run(self.data_portal) + algo.run() warnings = [r for r in log_catcher.records if r.level == logbook.WARNING] From acca9065309275237edf21467a64deb124d10cf9 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 02:54:39 -0400 Subject: [PATCH 13/14] MAINT: Drop TestPanelData. - We're going to drop support for this feature soon, so it's not worth refactoring the test suite. --- tests/test_algorithm.py | 138 ---------------------------------------- 1 file changed, 138 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index dd70215eb9..f517476aa1 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -4450,141 +4450,3 @@ def analyze(context, results): env=self.env, **{method: lambda *args, **kwargs: None} ) - - -class TestPanelData(WithTradingEnvironment, ZiplineTestCase): - - def create_panel(self, sids, trading_calendar, start_dt, end_dt, - create_df_for_asset, prev_close_column=False): - dfs = {} - for sid in sids: - dfs[sid] = create_df_for_asset(trading_calendar, - start_dt, end_dt, interval=sid) - if prev_close_column: - dfs[sid]['prev_close'] = dfs[sid]['close'].shift(1) - return pd.Panel(dfs) - - @parameterized.expand([ - ('daily', - pd.Timestamp('2015-12-23', tz='UTC'), - pd.Timestamp('2016-01-05', tz='UTC'),), - ('minute', - pd.Timestamp('2015-12-23', tz='UTC'), - pd.Timestamp('2015-12-24', tz='UTC'),), - ]) - def test_panel_data(self, data_frequency, start_dt, end_dt): - trading_calendar = get_calendar('NYSE') - if data_frequency == 'daily': - history_freq = '1d' - create_df_for_asset = create_daily_df_for_asset - dt_transform = trading_calendar.minute_to_session_label - elif data_frequency == 'minute': - history_freq = '1m' - create_df_for_asset = create_minute_df_for_asset - - def dt_transform(dt): - return dt - else: - raise AssertionError('Unexpected data_frequency: %s' % - data_frequency) - - sids = range(1, 3) - panel = self.create_panel(sids, trading_calendar, start_dt, end_dt, - create_df_for_asset, prev_close_column=True) - - price_record = pd.Panel(items=sids, - major_axis=panel.major_axis, - minor_axis=['current', 'previous']) - - def initialize(algo): - algo.first_bar = True - algo.equities = [algo.sid(sid) for sid in sids] - - def handle_data(algo, data): - price_record.loc[:, dt_transform(algo.get_datetime()), - 'current'] = ( - data.current(algo.equities, 'price') - ) - if algo.first_bar: - algo.first_bar = False - else: - price_record.loc[:, dt_transform(algo.get_datetime()), - 'previous'] = ( - data.history(algo.equities, 'price', - 2, history_freq).iloc[0] - ) - - def check_panels(): - np.testing.assert_array_equal( - price_record.values.astype('float64'), - panel.loc[:, :, ['close', - 'prev_close']].values.astype('float64') - ) - - with tmp_trading_env(load=self.make_load_function()) as env: - trading_algo = TradingAlgorithm(initialize=initialize, - handle_data=handle_data, - env=env) - trading_algo.run(data=panel) - check_panels() - price_record.loc[:] = np.nan - - with tmp_dir() as tmpdir: - root = tmpdir.getpath('example_data/root') - copy_market_data(self.MARKET_DATA_DIR, root) - - run_algorithm( - start=start_dt, - end=end_dt, - capital_base=1, - initialize=initialize, - handle_data=handle_data, - data_frequency=data_frequency, - data=panel, - environ={'ZIPLINE_ROOT': root}, - ) - check_panels() - - def test_minute_panel_daily_history(self): - sids = range(1, 3) - trading_calendar = get_calendar('NYSE') - start_dt = pd.Timestamp('2015-12-23', tz='UTC') - end_dt = pd.Timestamp('2015-12-30', tz='UTC') - - panel = self.create_panel( - sids, - trading_calendar, - start_dt, - end_dt, - create_minute_df_for_asset, - ) - - def check_open_price(algo, data): - if algo.first_day: - algo.first_day = False - else: - np.testing.assert_array_equal( - algo.last_open, - data.history( - algo.equities, - 'open', - 2, - '1d', - ).iloc[0] - ) - algo.last_open = data.current(algo.equities, 'open') - - def initialize(algo): - algo.first_day = True - algo.equities = [algo.sid(sid) for sid in sids] - - algo.schedule_function( - check_open_price, - date_rules.every_day(), - time_rules.market_open(), - ) - - with tmp_trading_env(load=self.make_load_function()) as env: - trading_algo = TradingAlgorithm(initialize=initialize, - env=env) - trading_algo.run(data=panel) From 246785662f3ea24600d871fb5a8f588938c36eca Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Fri, 4 May 2018 07:40:14 -0400 Subject: [PATCH 14/14] MAINT: Unbreak remaining test_algorithm suites. --- tests/test_algorithm.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index f517476aa1..ff0c456f07 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -33,8 +33,8 @@ import pytz from pandas.core.common import PerformanceWarning +from zipline.algorithm import TradingAlgorithm import zipline.api -from zipline import run_algorithm from zipline.api import FixedSlippage from zipline.assets import Equity, Future, Asset from zipline.assets.continuous_futures import ContinuousFuture @@ -77,7 +77,6 @@ ) from zipline.testing import ( FakeDataPortal, - copy_market_data, create_daily_df_for_asset, create_data_portal, create_data_portal_from_trade_history, @@ -89,7 +88,6 @@ tmp_trading_env, to_utc, trades_by_sid_to_dfs, - tmp_dir, ) from zipline.testing import RecordBatchBlotter import zipline.testing.fixtures as zf @@ -2596,7 +2594,9 @@ def handle_data(context, data): self.assertFalse(algo.first_bar) -class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): +class TestTradingControls(zf.WithSimParams, + zf.WithDataPortal, + zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-03', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') @@ -3070,7 +3070,9 @@ def test_asset_date_bounds(self): algo.run(data_portal) -class TestAccountControls(WithDataPortal, WithSimParams, ZiplineTestCase): +class TestAccountControls(zf.WithDataPortal, + zf.WithSimParams, + zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-03', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') @@ -3263,7 +3265,9 @@ def handle_data(algo, data): # format(i, actual_position, expected_positions[i])) -class TestFutureFlip(WithDataPortal, WithSimParams, ZiplineTestCase): +class TestFutureFlip(zf.WithDataPortal, + zf.WithSimParams, + zf.ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-09', tz='utc') END_DATE = pd.Timestamp('2006-01-10', tz='utc') sid, = ASSET_FINDER_EQUITY_SIDS = (1,) @@ -3324,7 +3328,9 @@ def check_algo_positions(self, results, expected_positions): format(i, actual_position, expected_positions[i])) -class TestFuturesAlgo(WithDataPortal, WithSimParams, ZiplineTestCase): +class TestFuturesAlgo(zf.WithDataPortal, + zf.WithSimParams, + zf.ZiplineTestCase): START_DATE = pd.Timestamp('2016-01-06', tz='utc') END_DATE = pd.Timestamp('2016-01-07', tz='utc') FUTURE_MINUTE_BAR_START_DATE = pd.Timestamp('2016-01-05', tz='UTC') @@ -3523,7 +3529,8 @@ def test_volume_contract_slippage(self): self.assertEqual(txn['price'], expected_price) -class TestTradingAlgorithm(WithTradingEnvironment, ZiplineTestCase): +class TestTradingAlgorithm(zf.WithTradingEnvironment, + zf.ZiplineTestCase): def test_analyze_called(self): self.perf_ref = None @@ -3549,9 +3556,9 @@ def analyze(context, perf): self.assertIs(results, self.perf_ref) -class TestOrderCancelation(WithDataPortal, - WithSimParams, - ZiplineTestCase): +class TestOrderCancelation(zf.WithDataPortal, + zf.WithSimParams, + zf.ZiplineTestCase): START_DATE = pd.Timestamp('2016-01-05', tz='utc') END_DATE = pd.Timestamp('2016-01-07', tz='utc') @@ -3744,7 +3751,9 @@ def test_eod_order_cancel_daily(self): self.assertFalse(log_catcher.has_warnings) -class TestEquityAutoClose(WithTradingEnvironment, WithTmpDir, ZiplineTestCase): +class TestEquityAutoClose(zf.WithTradingEnvironment, + zf.WithTmpDir, + zf.ZiplineTestCase): """ Tests if delisted equities are properly removed from a portfolio holding positions in said equities. @@ -4421,7 +4430,8 @@ def handle_data(context, data): self.assertEqual(expected_message, w.message) -class AlgoInputValidationTestCase(WithTradingEnvironment, ZiplineTestCase): +class AlgoInputValidationTestCase(zf.WithTradingEnvironment, + zf.ZiplineTestCase): def test_reject_passing_both_api_methods_and_script(self): script = dedent(