diff --git a/tests/calendars/test_trading_calendar.py b/tests/calendars/test_trading_calendar.py index fa57225e..4745b513 100644 --- a/tests/calendars/test_trading_calendar.py +++ b/tests/calendars/test_trading_calendar.py @@ -56,13 +56,13 @@ class CalendarRegistrationTestCase(TestCase): dummy_cal = self.dummy_cal_type('DMY') # Try to register and retrieve the calendar - register_calendar(dummy_cal) + register_calendar('DMY', dummy_cal) retr_cal = get_calendar('DMY') self.assertEqual(dummy_cal, retr_cal) # Try to register again, expecting a name collision with self.assertRaises(CalendarNameCollision): - register_calendar(dummy_cal) + register_calendar('DMY', dummy_cal) # Deregister the calendar and ensure that it is removed deregister_calendar('DMY') @@ -76,7 +76,7 @@ class CalendarRegistrationTestCase(TestCase): real_nyse = get_calendar('NYSE') # Force a registration of the dummy NYSE - register_calendar(dummy_nyse, force=True) + register_calendar("NYSE", dummy_nyse, force=True) # Ensure that the dummy overwrote the real calendar retr_cal = get_calendar('NYSE') diff --git a/tests/finance/test_cancel_policy.py b/tests/finance/test_cancel_policy.py index f93b78b8..5a199bf7 100644 --- a/tests/finance/test_cancel_policy.py +++ b/tests/finance/test_cancel_policy.py @@ -17,7 +17,7 @@ from unittest import TestCase from zipline.finance.cancel_policy import NeverCancel, EODCancel from zipline.gens.sim_engine import ( BAR, - DAY_END + SESSION_END ) @@ -25,10 +25,10 @@ class CancelPolicyTestCase(TestCase): def test_eod_cancel(self): cancel_policy = EODCancel() - self.assertTrue(cancel_policy.should_cancel(DAY_END)) + self.assertTrue(cancel_policy.should_cancel(SESSION_END)) self.assertFalse(cancel_policy.should_cancel(BAR)) def test_never_cancel(self): cancel_policy = NeverCancel() - self.assertFalse(cancel_policy.should_cancel(DAY_END)) + self.assertFalse(cancel_policy.should_cancel(SESSION_END)) self.assertFalse(cancel_policy.should_cancel(BAR)) diff --git a/tests/pipeline/test_slice.py b/tests/pipeline/test_slice.py index b778ff13..f6728bb7 100644 --- a/tests/pipeline/test_slice.py +++ b/tests/pipeline/test_slice.py @@ -165,7 +165,7 @@ class SliceTestCase(WithSeededRandomPipelineEngine, ZiplineTestCase): Test that indexing into a term with a non-existent asset raises the proper exception. """ - my_asset = Asset(0) + my_asset = Asset(0, exchange="TEST") returns = Returns(window_length=2, inputs=[self.col]) returns_slice = returns[my_asset] diff --git a/tests/pipeline/test_statistical.py b/tests/pipeline/test_statistical.py index 0e0e5926..afd0671e 100644 --- a/tests/pipeline/test_statistical.py +++ b/tests/pipeline/test_statistical.py @@ -314,7 +314,7 @@ class StatisticalBuiltInsTestCase(WithTradingEnvironment, ZiplineTestCase): `RollingLinearRegressionOfReturns` raise the proper exception when given a nonexistent target asset. """ - my_asset = Equity(0) + my_asset = Equity(0, exchange="TEST") start_date = self.pipeline_start_date end_date = self.pipeline_end_date run_pipeline = self.run_pipeline diff --git a/tests/pipeline/test_term.py b/tests/pipeline/test_term.py index 386f14e4..ecf31ce2 100644 --- a/tests/pipeline/test_term.py +++ b/tests/pipeline/test_term.py @@ -290,7 +290,7 @@ class ObjectIdentityTestCase(TestCase): self.assertIs(beta, multiple_outputs.beta) def test_instance_caching_of_slices(self): - my_asset = Asset(1) + my_asset = Asset(1, exchange="TEST") f = GenericCustomFactor() f_slice = f[my_asset] diff --git a/tests/resources/example_data.tar.gz b/tests/resources/example_data.tar.gz index 23e13495..443387c2 100644 Binary files a/tests/resources/example_data.tar.gz and b/tests/resources/example_data.tar.gz differ diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 781d6008..0968df6f 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -12,11 +12,11 @@ # 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 warnings from collections import namedtuple import datetime from datetime import timedelta from textwrap import dedent -import warnings from unittest import skip from copy import deepcopy @@ -32,11 +32,10 @@ from testfixtures import TempDirectory import numpy as np import pandas as pd import pytz +from pandas.io.common import PerformanceWarning -from zipline import ( - run_algorithm, - TradingAlgorithm, -) +from zipline import run_algorithm +from zipline import TradingAlgorithm from zipline.api import FixedSlippage from zipline.assets import Equity, Future from zipline.assets.synthetic import ( @@ -164,7 +163,7 @@ from zipline.test_algorithms import ( no_handle_data, ) from zipline.utils.api_support import ZiplineAPI, set_algo_instance -from zipline.utils.calendars import get_calendar +from zipline.utils.calendars import get_calendar, register_calendar from zipline.utils.context_tricks import CallbackManager from zipline.utils.control_flow import nullctx import zipline.utils.events @@ -211,10 +210,12 @@ class TestMiscellaneousAPI(WithLogger, pd.DataFrame.from_dict( {3: {'symbol': 'PLAY', 'start_date': '2002-01-01', - 'end_date': '2004-01-01'}, + 'end_date': '2004-01-01', + 'exchange': 'TEST'}, 4: {'symbol': 'PLAY', 'start_date': '2005-01-01', - 'end_date': '2006-01-01'}}, + 'end_date': '2006-01-01', + 'exchange': 'TEST'}}, orient='index', ), )) @@ -228,25 +229,33 @@ class TestMiscellaneousAPI(WithLogger, 'root_symbol': 'CL', 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2005-12-20', tz='UTC'), - 'expiration_date': pd.Timestamp('2006-01-20', tz='UTC')}, + 'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'), + 'exchange': 'TEST' + }, 6: { 'root_symbol': 'CL', 'symbol': 'CLK06', 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-03-20', tz='UTC'), - 'expiration_date': pd.Timestamp('2006-04-20', tz='UTC')}, + 'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'), + 'exchange': 'TEST', + }, 7: { 'symbol': 'CLQ06', 'root_symbol': 'CL', 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-06-20', tz='UTC'), - 'expiration_date': pd.Timestamp('2006-07-20', tz='UTC')}, + 'expiration_date': pd.Timestamp('2006-07-20', tz='UTC'), + 'exchange': 'TEST', + }, 8: { 'symbol': 'CLX06', 'root_symbol': 'CL', 'start_date': pd.Timestamp('2006-02-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-09-20', tz='UTC'), - 'expiration_date': pd.Timestamp('2006-10-20', tz='UTC')} + 'expiration_date': pd.Timestamp('2006-10-20', tz='UTC'), + 'exchange': 'TEST', + } }, orient='index', ) @@ -738,6 +747,7 @@ def handle_data(context, data): 'symbol': 'DUP', 'start_date': date.value, 'end_date': (date + timedelta(days=1)).value, + 'exchange': 'TEST', } for i, date in enumerate(dates) ] @@ -781,10 +791,13 @@ class TestTransformAlgorithm(WithLogger, @classmethod def make_futures_info(cls): - return pd.DataFrame.from_dict( - {3: {'multiplier': 10, 'symbol': 'F'}}, - orient='index', - ) + return pd.DataFrame.from_dict({ + 3: { + 'multiplier': 10, + 'symbol': 'F', + 'exchange': 'TEST' + } + }, orient='index') @classmethod def make_equity_daily_bar_data(cls): @@ -990,7 +1003,8 @@ def before_trading_start(context, data): period_end = pd.Timestamp('2002-1-4', tz='UTC') equities = pd.DataFrame([{ 'start_date': start_session, - 'end_date': period_end + timedelta(days=1) + 'end_date': period_end + timedelta(days=1), + 'exchange': "TEST", }] * 2) equities['symbol'] = ['A', 'B'] with TempDirectory() as tempdir, \ @@ -1439,6 +1453,8 @@ class TestAlgoScript(WithLogger, @classmethod def make_equity_info(cls): + register_calendar("TEST", get_calendar("NYSE"), force=True) + data = make_simple_equity_info( cls.sids, cls.START_DATE, @@ -1773,6 +1789,7 @@ def handle_data(context, data): 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'), @@ -1961,6 +1978,8 @@ def handle_data(context, data): """) with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) + algo = TradingAlgorithm( script=algocode, sim_params=sim_params, @@ -1968,18 +1987,19 @@ def handle_data(context, data): ) algo.run(self.data_portal) - self.assertEqual(len(w), 2) - for i, warning in enumerate(w): - self.assertIsInstance(warning.message, UserWarning) - self.assertEqual( - warning.message.args[0], - 'Got a time rule for the second positional argument ' - 'date_rule. You should use keyword argument ' - 'time_rule= when calling schedule_function without ' - 'specifying a date_rule' - ) - # The warnings come from line 13 and 14 in the algocode - self.assertEqual(warning.lineno, 13 + i) + self.assertEqual(len(w), 2) + + for i, warning in enumerate(w): + self.assertIsInstance(warning.message, UserWarning) + self.assertEqual( + warning.message.args[0], + 'Got a time rule for the second positional argument ' + 'date_rule. You should use keyword argument ' + 'time_rule= when calling schedule_function without ' + 'specifying a date_rule' + ) + # The warnings come from line 13 and 14 in the algocode + self.assertEqual(warning.lineno, 13 + i) self.assertEqual( algo.done_at_open, @@ -2855,7 +2875,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): 1: { 'symbol': 'SYM', 'start_date': start, - 'end_date': start + timedelta(days=6) + 'end_date': start + timedelta(days=6), + 'exchange': "TEST", }, }, orient='index', @@ -2984,6 +3005,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): 'symbol': 'SYM', 'start_date': self.sim_params.start_session, 'end_date': '2020-01-01', + 'exchange': "TEST", + 'sid': 999, }]) with TempDirectory() as tempdir, \ tmp_trading_env(equities=metadata) as env: @@ -2995,7 +3018,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): env.asset_finder, tempdir, self.sim_params, - [0], + [999], self.trading_calendar, ) algo.run(data_portal) @@ -3004,6 +3027,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): 'symbol': 'SYM', 'start_date': '1989-01-01', 'end_date': '1990-01-01', + 'exchange': "TEST", + 'sid': 999, }]) with TempDirectory() as tempdir, \ tmp_trading_env(equities=metadata) as env: @@ -3011,7 +3036,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): env.asset_finder, tempdir, self.sim_params, - [0], + [999], self.trading_calendar, ) algo = SetAssetDateBoundsAlgorithm( @@ -3025,6 +3050,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): 'symbol': 'SYM', 'start_date': '2020-01-01', 'end_date': '2021-01-01', + 'exchange': "TEST", + 'sid': 999, }]) with TempDirectory() as tempdir, \ tmp_trading_env(equities=metadata) as env: @@ -3032,7 +3059,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase): env.asset_finder, tempdir, self.sim_params, - [0], + [999], self.trading_calendar, ) algo = SetAssetDateBoundsAlgorithm( @@ -4054,7 +4081,8 @@ class TestOrderAfterDelist(WithTradingEnvironment, ZiplineTestCase): 'start_date': cls.start, 'end_date': cls.day_1, 'auto_close_date': cls.day_4, - 'symbol': "ASSET1" + 'symbol': "ASSET1", + 'exchange': "TEST", }, }, orient='index', diff --git a/tests/test_api_shim.py b/tests/test_api_shim.py index cb9a09c8..634aa2f9 100644 --- a/tests/test_api_shim.py +++ b/tests/test_api_shim.py @@ -3,6 +3,7 @@ import warnings from mock import patch import numpy as np import pandas as pd +from pandas.io.common import PerformanceWarning from zipline import TradingAlgorithm from zipline.finance.trading import SimulationParameters @@ -291,6 +292,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): deprecation warning. """ with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) algo = self.create_algo(sid_accessor_algo) algo.run(self.data_portal) @@ -319,6 +321,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): in `data` is deprecated. """ with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) algo = self.create_algo(data_items_algo) algo.run(self.data_portal) @@ -343,6 +346,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): def test_iterate_data(self): with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) algo = self.create_algo(simple_algo) @@ -373,6 +377,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): def test_history(self): with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) sim_params = self.sim_params.create_new( @@ -414,6 +419,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): def test_simple_transforms(self): with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) sim_params = SimulationParameters( @@ -484,6 +490,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase): def test_manipulation(self): with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore", PerformanceWarning) warnings.simplefilter("default", ZiplineDeprecationWarning) algo = self.create_algo(simple_algo) diff --git a/tests/test_assets.py b/tests/test_assets.py index 13f19e75..f1ba7991 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -110,21 +110,21 @@ def build_lookup_generic_cases(asset_finder_type): 'symbol': 'duplicated', 'start_date': dupe_0_start.value, 'end_date': dupe_0_end.value, - 'exchange': '', + 'exchange': 'TEST', }, { 'sid': 1, 'symbol': 'duplicated', 'start_date': dupe_1_start.value, 'end_date': dupe_1_end.value, - 'exchange': '', + 'exchange': 'TEST', }, { 'sid': 2, 'symbol': 'unique', 'start_date': unique_start.value, 'end_date': unique_end.value, - 'exchange': '', + 'exchange': 'TEST', }, ], index='sid') @@ -194,15 +194,21 @@ class AssetTestCase(TestCase): exchange='THE MOON', ) + asset3 = Asset(3, exchange="test") + asset4 = Asset(4, exchange="test") + asset5 = Asset(5, exchange="still testing") + def test_asset_object(self): - self.assertEquals({5061: 'foo'}[Asset(5061)], 'foo') - self.assertEquals(Asset(5061), 5061) - self.assertEquals(5061, Asset(5061)) + the_asset = Asset(5061, exchange="bar") - self.assertEquals(Asset(5061), Asset(5061)) - self.assertEquals(int(Asset(5061)), 5061) + self.assertEquals({5061: 'foo'}[the_asset], 'foo') + self.assertEquals(the_asset, 5061) + self.assertEquals(5061, the_asset) - self.assertEquals(str(Asset(5061)), 'Asset(5061)') + self.assertEquals(the_asset, the_asset) + self.assertEquals(int(the_asset), 5061) + + self.assertEquals(str(the_asset), 'Asset(5061)') def test_to_and_from_dict(self): asset_from_dict = Asset.from_dict(self.asset.to_dict()) @@ -220,8 +226,8 @@ class AssetTestCase(TestCase): def test_asset_comparisons(self): - s_23 = Asset(23) - s_24 = Asset(24) + s_23 = Asset(23, exchange="test") + s_24 = Asset(24, exchange="test") self.assertEqual(s_23, s_23) self.assertEqual(s_23, 23) @@ -250,39 +256,39 @@ class AssetTestCase(TestCase): self.assertGreater(s_24, s_23) def test_lt(self): - self.assertTrue(Asset(3) < Asset(4)) - self.assertFalse(Asset(4) < Asset(4)) - self.assertFalse(Asset(5) < Asset(4)) + self.assertTrue(self.asset3 < self.asset4) + self.assertFalse(self.asset4 < self.asset4) + self.assertFalse(self.asset5 < self.asset4) def test_le(self): - self.assertTrue(Asset(3) <= Asset(4)) - self.assertTrue(Asset(4) <= Asset(4)) - self.assertFalse(Asset(5) <= Asset(4)) + self.assertTrue(self.asset3 <= self.asset4) + self.assertTrue(self.asset4 <= self.asset4) + self.assertFalse(self.asset5 <= self.asset4) def test_eq(self): - self.assertFalse(Asset(3) == Asset(4)) - self.assertTrue(Asset(4) == Asset(4)) - self.assertFalse(Asset(5) == Asset(4)) + self.assertFalse(self.asset3 == self.asset4) + self.assertTrue(self.asset4 == self.asset4) + self.assertFalse(self.asset5 == self.asset4) def test_ge(self): - self.assertFalse(Asset(3) >= Asset(4)) - self.assertTrue(Asset(4) >= Asset(4)) - self.assertTrue(Asset(5) >= Asset(4)) + self.assertFalse(self.asset3 >= self.asset4) + self.assertTrue(self.asset4 >= self.asset4) + self.assertTrue(self.asset5 >= self.asset4) def test_gt(self): - self.assertFalse(Asset(3) > Asset(4)) - self.assertFalse(Asset(4) > Asset(4)) - self.assertTrue(Asset(5) > Asset(4)) + self.assertFalse(self.asset3 > self.asset4) + self.assertFalse(self.asset4 > self.asset4) + self.assertTrue(self.asset5 > self.asset4) def test_type_mismatch(self): if sys.version_info.major < 3: - self.assertIsNotNone(Asset(3) < 'a') - self.assertIsNotNone('a' < Asset(3)) + self.assertIsNotNone(self.asset3 < 'a') + self.assertIsNotNone('a' < self.asset3) else: with self.assertRaises(TypeError): - Asset(3) < 'a' + self.asset3 < 'a' with self.assertRaises(TypeError): - 'a' < Asset(3) + 'a' < self.asset3 class TestFuture(WithAssetFinder, ZiplineTestCase): @@ -298,6 +304,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase): 'auto_close_date': pd.Timestamp('2014-01-18', tz='UTC'), 'tick_size': .01, 'multiplier': 500.0, + 'exchange': "TEST", }, 0: { 'symbol': 'CLG06', @@ -306,6 +313,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase): 'notice_date': pd.Timestamp('2005-12-20', tz='UTC'), 'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'), 'multiplier': 1.0, + 'exchange': 'TEST', }, }, orient='index', @@ -423,6 +431,7 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'TEST.%d' % sid, 'start_date': as_of.value, 'end_date': as_of.value, + 'exchange': uuid.uuid4().hex } for sid in sids ] @@ -471,9 +480,9 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): def test_lookup_symbol_fuzzy(self): metadata = pd.DataFrame.from_records([ - {'symbol': 'PRTY_HRD'}, - {'symbol': 'BRKA'}, - {'symbol': 'BRK_A'}, + {'symbol': 'PRTY_HRD', 'exchange': "TEST"}, + {'symbol': 'BRKA', 'exchange': "TEST"}, + {'symbol': 'BRK_A', 'exchange': "TEST"}, ]) self.write_assets(equities=metadata) finder = self.asset_finder @@ -516,11 +525,13 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'A', 'start_date': T('2014-01-01'), 'end_date': T('2014-01-05'), + 'exchange': "TEST", }, { 'symbol': 'B', 'start_date': T('2014-01-06'), 'end_date': T('2014-01-10'), + 'exchange': "TEST", }, # sid 1 @@ -528,11 +539,13 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'C', 'start_date': T('2014-01-01'), 'end_date': T('2014-01-05'), + 'exchange': "TEST", }, { 'symbol': 'A', # claiming the unused symbol 'A' 'start_date': T('2014-01-06'), 'end_date': T('2014-01-10'), + 'exchange': "TEST", }, ], index=[0, 0, 1, 1], @@ -697,14 +710,14 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'real', 'start_date': pd.Timestamp('2013-1-1', tz='UTC'), 'end_date': pd.Timestamp('2014-1-1', tz='UTC'), - 'exchange': '', + 'exchange': 'TEST', }, { 'sid': 1, 'symbol': 'also_real', 'start_date': pd.Timestamp('2013-1-1', tz='UTC'), 'end_date': pd.Timestamp('2014-1-1', tz='UTC'), - 'exchange': '', + 'exchange': 'TEST', }, # Sid whose end date is before our query date. We should # still correctly find it. @@ -713,7 +726,7 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'real_but_old', 'start_date': pd.Timestamp('2002-1-1', tz='UTC'), 'end_date': pd.Timestamp('2003-1-1', tz='UTC'), - 'exchange': '', + 'exchange': 'TEST', }, # Sid whose start_date is **after** our query date. We should # **not** find it. @@ -749,7 +762,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): # Build an asset with an end_date eq_end = pd.Timestamp('2012-01-01', tz='UTC') - equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end) + equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end, + exchange="TEST") # Catch all warnings with warnings.catch_warnings(record=True) as w: @@ -772,14 +786,16 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2015-06-14', tz='UTC'), 'expiration_date': pd.Timestamp('2015-08-14', tz='UTC'), - 'start_date': pd.Timestamp('2015-01-01', tz='UTC') + 'start_date': pd.Timestamp('2015-01-01', tz='UTC'), + 'exchange': "TEST", }, { 'symbol': 'ADV15', 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2015-05-14', tz='UTC'), 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'), - 'start_date': pd.Timestamp('2015-01-01', tz='UTC') + 'start_date': pd.Timestamp('2015-01-01', tz='UTC'), + 'exchange': "TEST", }, # Starts trading today, so should be valid. { @@ -787,7 +803,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2015-11-16', tz='UTC'), 'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'), - 'start_date': pd.Timestamp('2015-05-14', tz='UTC') + 'start_date': pd.Timestamp('2015-05-14', tz='UTC'), + 'exchange': "TEST", }, # Starts trading in August, so not valid. { @@ -795,7 +812,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2015-11-16', tz='UTC'), 'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'), - 'start_date': pd.Timestamp('2015-08-01', tz='UTC') + 'start_date': pd.Timestamp('2015-08-01', tz='UTC'), + 'exchange': "TEST", }, # Notice date comes after expiration { @@ -803,7 +821,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2016-11-25', tz='UTC'), 'expiration_date': pd.Timestamp('2016-11-16', tz='UTC'), - 'start_date': pd.Timestamp('2015-08-01', tz='UTC') + 'start_date': pd.Timestamp('2015-08-01', tz='UTC'), + 'exchange': "TEST", }, # This contract has no start date and also this contract should be # last in all chains @@ -811,7 +830,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): 'symbol': 'ADZ20', 'root_symbol': 'AD', 'notice_date': pd.Timestamp('2020-11-25', tz='UTC'), - 'expiration_date': pd.Timestamp('2020-11-16', tz='UTC') + 'expiration_date': pd.Timestamp('2020-11-16', tz='UTC'), + 'exchange': "TEST", }, ]) self.write_assets(futures=metadata) @@ -850,10 +870,10 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase): # Build an empty finder and some Assets dt = pd.Timestamp('2014-01-01', tz='UTC') finder = self.asset_finder - asset1 = Equity(1, symbol="AAPL") - asset2 = Equity(2, symbol="GOOG") - asset200 = Future(200, symbol="CLK15") - asset201 = Future(201, symbol="CLM15") + asset1 = Equity(1, symbol="AAPL", exchange="TEST") + asset2 = Equity(2, symbol="GOOG", exchange="TEST") + asset200 = Future(200, symbol="CLK15", exchange="TEST") + asset201 = Future(201, symbol="CLM15", exchange="TEST") # Check for correct mapping and types pre_map = [asset1, asset2, asset200, asset201] @@ -1103,6 +1123,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase): 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2005-12-20', tz='UTC'), 'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'), + 'exchange': "TEST", }, { 'root_symbol': 'CL', @@ -1110,6 +1131,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase): 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-03-20', tz='UTC'), 'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'), + 'exchange': "TEST", }, { 'symbol': 'CLQ06', @@ -1117,6 +1139,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase): 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-06-20', tz='UTC'), 'expiration_date': pd.Timestamp('2006-07-20', tz='UTC'), + 'exchange': "TEST", }, { 'symbol': 'CLX06', @@ -1124,6 +1147,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase): 'start_date': pd.Timestamp('2006-02-01', tz='UTC'), 'notice_date': pd.Timestamp('2006-09-20', tz='UTC'), 'expiration_date': pd.Timestamp('2006-10-20', tz='UTC'), + 'exchange': "TEST", } ]) diff --git a/tests/test_bar_data.py b/tests/test_bar_data.py index f3d253cb..3abc5b9d 100644 --- a/tests/test_bar_data.py +++ b/tests/test_bar_data.py @@ -131,6 +131,30 @@ class TestMinuteBarData(WithBarDataChecks, 50, ) + @classmethod + def make_futures_info(cls): + return pd.DataFrame.from_dict( + { + 6: { + 'symbol': 'CLG06', + 'root_symbol': 'CL', + 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), + 'notice_date': pd.Timestamp('2005-12-20', tz='UTC'), + 'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'), + 'exchange': 'ICEUS', + }, + 7: { + 'symbol': 'CLK06', + 'root_symbol': 'CL', + 'start_date': pd.Timestamp('2005-12-01', tz='UTC'), + 'notice_date': pd.Timestamp('2006-03-20', tz='UTC'), + 'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'), + 'exchange': 'ICEUS', + }, + }, + orient='index', + ) + @classmethod def make_splits_data(cls): return pd.DataFrame([ @@ -438,7 +462,7 @@ class TestMinuteBarData(WithBarDataChecks, bd.current(self.HILARIOUSLY_ILLIQUID_ASSET, "volume") ) - def test_can_trade_at_midnight(self): + def test_can_trade_during_non_market_hours(self): # make sure that if we use `can_trade` at midnight, we don't pretend # we're in the previous day's last minute the_day_after = self.trading_calendar.next_session_label( @@ -453,19 +477,66 @@ class TestMinuteBarData(WithBarDataChecks, with handle_non_market_minutes(bar_data): self.assertFalse(bar_data.can_trade(asset)) - # but make sure it works when the assets are alive + # NYSE is closed at midnight, so even if the asset is alive, can_trade + # should return False bar_data2 = BarData( self.data_portal, lambda: self.equity_minute_bar_days[1], "minute", ) for asset in [self.ASSET1, self.HILARIOUSLY_ILLIQUID_ASSET]: - self.assertTrue(bar_data2.can_trade(asset)) + self.assertFalse(bar_data2.can_trade(asset)) with handle_non_market_minutes(bar_data2): - self.assertTrue(bar_data2.can_trade(asset)) + self.assertFalse(bar_data2.can_trade(asset)) - def test_is_stale_at_midnight(self): + def test_can_trade_exchange_closed(self): + nyse_asset = self.asset_finder.retrieve_asset(1) + ice_asset = self.asset_finder.retrieve_asset(6) + + # minutes we're going to check (to verify that that the same bardata + # can check multiple exchange calendars, all times Eastern): + # 2016-01-05: + # 20:00 (minute before ICE opens) + # 20:01 (first minute of ICE session) + # 20:02 (second minute of ICE session) + # 00:00 (Cinderella's ride becomes a pumpkin) + # 2016-01-06: + # 9:30 (minute before NYSE opens) + # 9:31 (first minute of NYSE session) + # 9:32 (second minute of NYSE session) + # 15:59 (second-to-last minute of NYSE session) + # 16:00 (last minute of NYSE session) + # 16:01 (minute after NYSE closed) + # 17:59 (second-to-last minute of ICE session) + # 18:00 (last minute of ICE session) + # 18:01 (minute after ICE closed) + + # each row is dt, whether-nyse-is-open, whether-ice-is-open + minutes_to_check = [ + (pd.Timestamp("2016-01-05 20:00", tz="US/Eastern"), False, False), + (pd.Timestamp("2016-01-05 20:01", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-05 20:02", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 00:00", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 9:30", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 9:31", tz="US/Eastern"), True, True), + (pd.Timestamp("2016-01-06 9:32", tz="US/Eastern"), True, True), + (pd.Timestamp("2016-01-06 15:59", tz="US/Eastern"), True, True), + (pd.Timestamp("2016-01-06 16:00", tz="US/Eastern"), True, True), + (pd.Timestamp("2016-01-06 16:01", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 17:59", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 18:00", tz="US/Eastern"), False, True), + (pd.Timestamp("2016-01-06 18:01", tz="US/Eastern"), False, False), + ] + + for info in minutes_to_check: + bar_data = BarData(self.data_portal, lambda: info[0], "minute") + series = bar_data.can_trade([nyse_asset, ice_asset]) + + self.assertEqual(info[1], series.loc[nyse_asset]) + self.assertEqual(info[2], series.loc[ice_asset]) + + def test_is_stale_during_non_market_hours(self): bar_data = BarData( self.data_portal, lambda: self.equity_minute_bar_days[1], @@ -644,13 +715,20 @@ class TestDailyBarData(WithBarDataChecks, ) cls.ASSETS = [cls.ASSET1, cls.ASSET2] + def get_last_minute_of_session(self, session_label): + return self.trading_calendar.open_and_close_for_session( + session_label + )[1] + def test_day_before_assets_trading(self): # use the day before self.bcolz_daily_bar_days[0] - day = self.trading_calendar.previous_session_label( - self.equity_daily_bar_days[0] + minute = self.get_last_minute_of_session( + self.trading_calendar.previous_session_label( + self.equity_daily_bar_days[0] + ) ) - bar_data = BarData(self.data_portal, lambda: day, "daily") + bar_data = BarData(self.data_portal, lambda: minute, "daily") self.check_internal_consistency(bar_data) self.assertFalse(bar_data.can_trade(self.ASSET1)) @@ -674,7 +752,9 @@ class TestDailyBarData(WithBarDataChecks, # on self.equity_daily_bar_days[0], only asset1 has data bar_data = BarData( self.data_portal, - lambda: self.equity_daily_bar_days[0], + lambda: self.get_last_minute_of_session( + self.equity_daily_bar_days[0] + ), "daily", ) self.check_internal_consistency(bar_data) @@ -709,7 +789,9 @@ class TestDailyBarData(WithBarDataChecks, def test_fully_active_day(self): bar_data = BarData( self.data_portal, - lambda: self.equity_daily_bar_days[1], + lambda: self.get_last_minute_of_session( + self.equity_daily_bar_days[1] + ), "daily", ) self.check_internal_consistency(bar_data) @@ -733,7 +815,9 @@ class TestDailyBarData(WithBarDataChecks, def test_last_active_day(self): bar_data = BarData( self.data_portal, - lambda: self.equity_daily_bar_days[-1], + lambda: self.get_last_minute_of_session( + self.equity_daily_bar_days[-1] + ), "daily", ) self.check_internal_consistency(bar_data) @@ -751,11 +835,13 @@ class TestDailyBarData(WithBarDataChecks, def test_after_assets_dead(self): # both assets end on self.day[-1], so let's try the next day - next_day = self.trading_calendar.next_session_label( - self.equity_daily_bar_days[-1] + minute = self.get_last_minute_of_session( + self.trading_calendar.next_session_label( + self.equity_daily_bar_days[-1] + ) ) - bar_data = BarData(self.data_portal, lambda: next_day, "daily") + bar_data = BarData(self.data_portal, lambda: minute, "daily") self.check_internal_consistency(bar_data) for asset in self.ASSETS: diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 2742643d..950c0229 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -47,22 +47,26 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingCalendar, 1: { 'symbol': 'A', 'start_date': cls.START_DATE, - 'end_date': cls.END_DATE + pd.Timedelta(days=1) + 'end_date': cls.END_DATE + pd.Timedelta(days=1), + "exchange": "TEST", }, 2: { 'symbol': 'B', 'start_date': cls.START_DATE, - 'end_date': cls.END_DATE + pd.Timedelta(days=1) + 'end_date': cls.END_DATE + pd.Timedelta(days=1), + "exchange": "TEST", }, 3: { 'symbol': 'C', 'start_date': pd.Timestamp('2006-05-26', tz='utc'), - 'end_date': pd.Timestamp('2006-08-09', tz='utc') + 'end_date': pd.Timestamp('2006-08-09', tz='utc'), + "exchange": "TEST", }, 4: { 'symbol': 'D', 'start_date': cls.START_DATE, - 'end_date': cls.END_DATE + pd.Timedelta(days=1) + 'end_date': cls.END_DATE + pd.Timedelta(days=1), + "exchange": "TEST", }, }, orient='index', diff --git a/tests/test_blotter.py b/tests/test_blotter.py index e27df4dd..eed7ae91 100644 --- a/tests/test_blotter.py +++ b/tests/test_blotter.py @@ -25,7 +25,7 @@ from zipline.finance.execution import ( StopOrder, ) -from zipline.gens.sim_engine import DAY_END, BAR +from zipline.gens.sim_engine import SESSION_END, BAR from zipline.finance.cancel_policy import EODCancel, NeverCancel from zipline.finance.slippage import ( DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT, @@ -143,7 +143,7 @@ class BlotterTestCase(WithLogger, self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) - blotter.execute_cancel_policy(DAY_END) + blotter.execute_cancel_policy(SESSION_END) for order_id in order_ids: order = blotter.orders[order_id] self.assertEqual(order.status, ORDER_STATUS.CANCELLED) @@ -161,7 +161,7 @@ class BlotterTestCase(WithLogger, blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) - blotter.execute_cancel_policy(DAY_END) + blotter.execute_cancel_policy(SESSION_END) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) def test_order_rejection(self): diff --git a/tests/test_clock.py b/tests/test_clock.py new file mode 100644 index 00000000..fa81b63c --- /dev/null +++ b/tests/test_clock.py @@ -0,0 +1,180 @@ +from datetime import time +from unittest import TestCase +import pandas as pd +from zipline.gens.sim_engine import ( + MinuteSimulationClock, + SESSION_START, + BEFORE_TRADING_START_BAR, + BAR, + SESSION_END +) + +from zipline.utils.calendars import get_calendar +from zipline.utils.calendars.trading_calendar import days_at_time + + +class TestClock(TestCase): + @classmethod + def setUpClass(cls): + cls.nyse_calendar = get_calendar("NYSE") + + # july 15 is friday, so there are 3 sessions in this range (15, 18, 19) + cls.sessions = cls.nyse_calendar.sessions_in_range( + pd.Timestamp("2016-07-15"), + pd.Timestamp("2016-07-19") + ) + + trading_o_and_c = cls.nyse_calendar.schedule.ix[cls.sessions] + cls.opens = trading_o_and_c['market_open'] + cls.closes = trading_o_and_c['market_close'] + + def test_bts_before_session(self): + clock = MinuteSimulationClock( + self.sessions, + self.opens, + self.closes, + days_at_time(self.sessions, time(6, 17), "US/Eastern"), + False + ) + + all_events = list(clock) + + def _check_session_bts_first(session_label, events, bts_dt): + minutes = self.nyse_calendar.minutes_for_session(session_label) + + self.assertEqual(393, len(events)) + + self.assertEqual(events[0], (session_label, SESSION_START)) + self.assertEqual(events[1], (bts_dt, BEFORE_TRADING_START_BAR)) + for i in range(2, 392): + self.assertEqual(events[i], (minutes[i - 2], BAR)) + self.assertEqual(events[392], (minutes[-1], SESSION_END)) + + _check_session_bts_first( + self.sessions[0], + all_events[0:393], + pd.Timestamp("2016-07-15 6:17", tz='US/Eastern') + ) + + _check_session_bts_first( + self.sessions[1], + all_events[393:786], + pd.Timestamp("2016-07-18 6:17", tz='US/Eastern') + ) + + _check_session_bts_first( + self.sessions[2], + all_events[786:], + pd.Timestamp("2016-07-19 6:17", tz='US/Eastern') + ) + + def test_bts_during_session(self): + self.verify_bts_during_session( + time(11, 45), [ + pd.Timestamp("2016-07-15 11:45", tz='US/Eastern'), + pd.Timestamp("2016-07-18 11:45", tz='US/Eastern'), + pd.Timestamp("2016-07-19 11:45", tz='US/Eastern') + ], + 135 + ) + + def test_bts_on_first_minute(self): + self.verify_bts_during_session( + time(9, 30), [ + pd.Timestamp("2016-07-15 9:30", tz='US/Eastern'), + pd.Timestamp("2016-07-18 9:30", tz='US/Eastern'), + pd.Timestamp("2016-07-19 9:30", tz='US/Eastern') + ], + 1 + ) + + def test_bts_on_last_minute(self): + self.verify_bts_during_session( + time(16, 00), [ + pd.Timestamp("2016-07-15 16:00", tz='US/Eastern'), + pd.Timestamp("2016-07-18 16:00", tz='US/Eastern'), + pd.Timestamp("2016-07-19 16:00", tz='US/Eastern') + ], + 390 + ) + + def verify_bts_during_session(self, bts_time, bts_session_times, bts_idx): + def _check_session_bts_during(session_label, events, bts_dt): + minutes = self.nyse_calendar.minutes_for_session(session_label) + + self.assertEqual(393, len(events)) + + self.assertEqual(events[0], (session_label, SESSION_START)) + + for i in range(1, bts_idx): + self.assertEqual(events[i], (minutes[i - 1], BAR)) + + self.assertEqual( + events[bts_idx], + (bts_dt, BEFORE_TRADING_START_BAR) + ) + + for i in range(bts_idx + 1, 391): + self.assertEqual(events[i], (minutes[i - 2], BAR)) + + self.assertEqual(events[392], (minutes[-1], SESSION_END)) + + clock = MinuteSimulationClock( + self.sessions, + self.opens, + self.closes, + days_at_time(self.sessions, bts_time, "US/Eastern"), + False + ) + + all_events = list(clock) + + _check_session_bts_during( + self.sessions[0], + all_events[0:393], + bts_session_times[0] + ) + + _check_session_bts_during( + self.sessions[1], + all_events[393:786], + bts_session_times[1] + ) + + _check_session_bts_during( + self.sessions[2], + all_events[786:], + bts_session_times[2] + ) + + def test_bts_after_session(self): + clock = MinuteSimulationClock( + self.sessions, + self.opens, + self.closes, + days_at_time(self.sessions, time(19, 5), "US/Eastern"), + False + ) + + all_events = list(clock) + + # since 19:05 Eastern is after the NYSE is closed, we don't emit + # BEFORE_TRADING_START. therefore, each day has SESSION_START, + # 390 BARs, and then SESSION_END + + def _check_session_bts_after(session_label, events): + minutes = self.nyse_calendar.minutes_for_session(session_label) + + self.assertEqual(392, len(events)) + self.assertEqual(events[0], (session_label, SESSION_START)) + + for i in range(1, 391): + self.assertEqual(events[i], (minutes[i - 1], BAR)) + + self.assertEqual(events[-1], (minutes[389], SESSION_END)) + + for i in range(0, 2): + _check_session_bts_after( + self.sessions[i], + all_events[(i * 392): ((i + 1) * 392)] + ) diff --git a/tests/test_daily_history_aggregator.py b/tests/test_daily_history_aggregator.py index 2a234d31..628e4d59 100644 --- a/tests/test_daily_history_aggregator.py +++ b/tests/test_daily_history_aggregator.py @@ -120,6 +120,7 @@ class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader, self.equity_daily_aggregator = DailyHistoryAggregator( self.trading_calendar.schedule.market_open, self.bcolz_equity_minute_bar_reader, + self.trading_calendar ) @parameterized.expand([ diff --git a/tests/test_history.py b/tests/test_history.py index 1824aa24..5714b1c7 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -121,42 +121,50 @@ class WithHistory(WithDataPortal): 1: { 'start_date': pd.Timestamp('2014-01-03', tz='UTC'), 'end_date': cls.TRADING_END_DT, - 'symbol': 'ASSET1' + 'symbol': 'ASSET1', + 'exchange': "TEST", }, 2: { 'start_date': jan_5_2015, 'end_date': day_after_12312015, - 'symbol': 'ASSET2' + 'symbol': 'ASSET2', + 'exchange': "TEST", }, 3: { 'start_date': jan_5_2015, 'end_date': day_after_12312015, - 'symbol': 'ASSET3' + 'symbol': 'ASSET3', + 'exchange': "TEST", }, cls.SPLIT_ASSET_SID: { 'start_date': jan_5_2015, 'end_date': day_after_12312015, - 'symbol': 'SPLIT_ASSET' + 'symbol': 'SPLIT_ASSET', + 'exchange': "TEST", }, cls.DIVIDEND_ASSET_SID: { 'start_date': jan_5_2015, 'end_date': day_after_12312015, - 'symbol': 'DIVIDEND_ASSET' + 'symbol': 'DIVIDEND_ASSET', + 'exchange': "TEST", }, cls.MERGER_ASSET_SID: { 'start_date': jan_5_2015, 'end_date': day_after_12312015, - 'symbol': 'MERGER_ASSET' + 'symbol': 'MERGER_ASSET', + 'exchange': "TEST", }, cls.HALF_DAY_TEST_ASSET_SID: { 'start_date': pd.Timestamp('2014-07-02', tz='UTC'), 'end_date': day_after_12312015, - 'symbol': 'HALF_DAY_TEST_ASSET' + 'symbol': 'HALF_DAY_TEST_ASSET', + 'exchange': "TEST", }, cls.SHORT_ASSET_SID: { 'start_date': pd.Timestamp('2015-01-05', tz='UTC'), 'end_date': pd.Timestamp('2015-01-06', tz='UTC'), - 'symbol': 'SHORT_ASSET' + 'symbol': 'SHORT_ASSET', + 'exchange': "TEST", } }, orient='index', diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 076ec252..cc36b418 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -1046,6 +1046,7 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendar, 'start_date': start, 'end_date': end, 'multiplier': 100, + 'exchange': "TEST", } for sid in futures_sids }, @@ -2345,9 +2346,9 @@ class TestPositionTracker(WithTradingEnvironment, def make_futures_info(cls): return pd.DataFrame.from_dict( { - 3: {'multiplier': 1000}, - 4: {'multiplier': 1000}, - 1032201401: {'multiplier': 50}, + 3: {'multiplier': 1000, 'exchange': 'TEST'}, + 4: {'multiplier': 1000, 'exchange': 'TEST'}, + 1032201401: {'multiplier': 50, 'exchange': 'TEST'}, }, orient='index', ) diff --git a/tests/test_security_list.py b/tests/test_security_list.py index 756e2210..4d699ff7 100644 --- a/tests/test_security_list.py +++ b/tests/test_security_list.py @@ -85,7 +85,8 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase): equities=pd.DataFrame.from_records([{ 'start_date': cls.start, 'end_date': end, - 'symbol': symbol + 'symbol': symbol, + 'exchange': "TEST", } for symbol in symbols]), )) @@ -103,7 +104,8 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase): equities=pd.DataFrame.from_records([{ 'start_date': sp2.start_session, 'end_date': sp2.end_session, - 'symbol': symbol + 'symbol': symbol, + 'exchange': "TEST", } for symbol in symbols]), )) @@ -267,6 +269,7 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase): 'symbol': 'BZQ', 'start_date': sim_params.start_session, 'end_date': sim_params.end_session, + 'exchange': "TEST", }]) with TempDirectory() as new_tempdir, \ security_list_copy(), \ diff --git a/tests/test_tradesimulation.py b/tests/test_tradesimulation.py index 87499cef..d3a42fb6 100644 --- a/tests/test_tradesimulation.py +++ b/tests/test_tradesimulation.py @@ -12,6 +12,8 @@ # 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 datetime import time + import pandas as pd from mock import patch @@ -23,6 +25,7 @@ from zipline.sources.benchmark_source import BenchmarkSource from zipline.test_algorithms import NoopAlgorithm from zipline.utils import factory from zipline.testing.core import FakeDataPortal +from zipline.utils.calendars.trading_calendar import days_at_time class BeforeTradingAlgorithm(TradingAlgorithm): @@ -75,10 +78,18 @@ class TestTradeSimulation(TestCase): algo = BeforeTradingAlgorithm(sim_params=params) algo.run(FakeDataPortal()) - self.assertEqual(len(algo.perf_tracker.sim_params.sessions), - num_days) + self.assertEqual( + len(algo.perf_tracker.sim_params.sessions), + num_days + ) - self.assertTrue(params.sessions.equals( - pd.DatetimeIndex(algo.before_trading_at)), - "Expected %s but was %s." - % (params.sessions, algo.before_trading_at)) + bts_minutes = days_at_time( + params.sessions, time(8, 45), "US/Eastern" + ) + + self.assertTrue( + bts_minutes.equals( + pd.DatetimeIndex(algo.before_trading_at) + ), + "Expected %s but was %s." % (params.sessions, + algo.before_trading_at)) diff --git a/zipline/_protocol.pyx b/zipline/_protocol.pyx index 33d0df5b..6141d77e 100644 --- a/zipline/_protocol.pyx +++ b/zipline/_protocol.pyx @@ -24,7 +24,7 @@ from six import iteritems, PY2 from cpython cimport bool from collections import Iterable -from zipline.assets import Asset +from zipline.assets import Asset, Future from zipline.zipline_warnings import ZiplineDeprecationWarning @@ -426,9 +426,11 @@ cdef class BarData: @check_parameters(('assets',), (Asset,)) def can_trade(self, assets): """ - For the given asset or iterable of assets, returns true if the asset - is alive at the current simulation time and there is a known last - price. + For the given asset or iterable of assets, returns true if all of the + following are true: + - the asset is alive at the current simulation time + - the asset's exchange is open at the current simulation time + - there is a known last price for the asset. Parameters ---------- @@ -460,15 +462,25 @@ cdef class BarData: }) cdef bool _can_trade_for_asset(self, asset, dt, adjusted_dt, data_portal): - if asset._is_alive(dt, False): - # is there a last price? - return not np.isnan( - data_portal.get_spot_value( - asset, "price", adjusted_dt, self.data_frequency - ) - ) + session_label = normalize_date(dt) # FIXME + if not asset.is_alive_for_session(session_label): + # asset isn't alive + return False - return False + if not asset.is_exchange_open(dt): + # exchange isn't open + return False + + if isinstance(asset, Future): + # FIXME: this will get removed once we can get prices for futures + return True + + # is there a last price? + return not np.isnan( + data_portal.get_spot_value( + asset, "price", adjusted_dt, self.data_frequency + ) + ) @check_parameters(('assets',), (Asset,)) def is_stale(self, assets): @@ -511,7 +523,9 @@ cdef class BarData: }) cdef bool _is_stale_for_asset(self, asset, dt, adjusted_dt, data_portal): - if not asset._is_alive(dt, False): + session_label = normalize_date(dt) # FIXME + + if not asset.is_alive_for_session(session_label): return False current_volume = data_portal.get_spot_value( diff --git a/zipline/algorithm.py b/zipline/algorithm.py index d163e34e..8827eff8 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -15,7 +15,7 @@ from copy import copy import operator as op import warnings -from datetime import tzinfo +from datetime import tzinfo, time import logbook import pytz import pandas as pd @@ -94,9 +94,9 @@ from zipline.utils.api_support import ( require_not_initialized, ZiplineAPI, disallowed_in_before_trading_start) - from zipline.utils.input_validation import ensure_upper_case, error_keywords, \ expect_types, optional, coerce_string +from zipline.utils.calendars.trading_calendar import days_at_time from zipline.utils.cache import CachedObject, Expired from zipline.utils.calendars import get_calendar @@ -496,29 +496,32 @@ class TradingAlgorithm(object): """ trading_o_and_c = self.trading_calendar.schedule.ix[ self.sim_params.sessions] - market_closes = trading_o_and_c['market_close'].values.astype(np.int64) + market_closes = trading_o_and_c['market_close'] + minutely_emission = False if self.sim_params.data_frequency == 'minute': - market_opens = trading_o_and_c['market_open'].values.astype( - np.int64) + market_opens = trading_o_and_c['market_open'] minutely_emission = self.sim_params.emission_rate == "minute" - - return MinuteSimulationClock( - self.sim_params.sessions, - market_opens, - market_closes, - minutely_emission - ) else: # in daily mode, we want to have one bar per session, timestamped # as the last minute of the session. - return MinuteSimulationClock( - self.sim_params.sessions, - market_closes, - market_closes, - False - ) + market_opens = market_closes + + # FIXME generalize these values + before_trading_start_minutes = days_at_time( + self.sim_params.sessions, + time(8, 45), + "US/Eastern" + ) + + return MinuteSimulationClock( + self.sim_params.sessions, + market_opens, + market_closes, + before_trading_start_minutes, + minute_emission=minutely_emission, + ) def _create_benchmark_source(self): return BenchmarkSource( @@ -1545,6 +1548,7 @@ class TradingAlgorithm(object): self.datetime, self._in_before_trading_start, self.data_portal) self._account = \ self.perf_tracker.get_account(self.performance_needs_update) + self.account_needs_update = False self.performance_needs_update = False return self._account diff --git a/zipline/assets/_assets.pyx b/zipline/assets/_assets.pyx index fb636c1d..1a7a3d25 100644 --- a/zipline/assets/_assets.pyx +++ b/zipline/assets/_assets.pyx @@ -34,14 +34,15 @@ from numpy cimport int64_t import warnings cimport numpy as np +from zipline.utils.calendars import get_calendar + # IMPORTANT NOTE: You must change this template if you change # Asset.__reduce__, or else we'll attempt to unpickle an old version of this # class -from pandas.tslib import normalize_date - CACHE_FILE_TEMPLATE = '/tmp/.%s-%s.v6.cache' + cdef class Asset: cdef readonly int sid @@ -71,13 +72,13 @@ cdef class Asset: def __init__(self, int sid, # sid is required + object exchange, # exchange is required object symbol="", object asset_name="", object start_date=None, object end_date=None, object first_traded=None, - object auto_close_date=None, - object exchange=""): + object auto_close_date=None): self.sid = sid self.sid_hash = hash(sid) @@ -157,13 +158,13 @@ cdef class Asset: be serialized/deserialized during pickling. """ return (self.__class__, (self.sid, + self.exchange, self.symbol, self.asset_name, self.start_date, self.end_date, self.first_traded, - self.auto_close_date, - self.exchange,)) + self.auto_close_date,)) cpdef to_dict(self): """ @@ -187,37 +188,40 @@ cdef class Asset: """ return cls(**dict_) - def _is_alive(self, dt, bool normalized): + def is_alive_for_session(self, session_label): """ Returns whether the asset is alive at the given dt. Parameters ---------- - dt: pd.Timestamp - The desired timestamp. - - normalized: boolean - Whether the date has already been normalized. If not, we need - to first normalize the date before doing the alive check. If the - date is already normalized, this method runs up to 80% faster. + session_label: pd.Timestamp + The desired session label to check. (midnight UTC) Returns ------- boolean: whether the asset is alive at the given dt. """ - cdef int64_t dt_value cdef int64_t ref_start cdef int64_t ref_end - if not normalized: - dt_value = normalize_date(dt).value - else: - dt_value = dt.value - ref_start = self.start_date.value ref_end = self.end_date.value - return ref_start <= dt_value <= ref_end + return ref_start <= session_label.value <= ref_end + + def is_exchange_open(self, dt_minute): + """ + Parameters + ---------- + dt_minute: pd.Timestamp (UTC, tz-aware) + The minute to check. + + Returns + ------- + boolean: whether the asset's exchange is open at the given minute. + """ + calendar = get_calendar(self.exchange) + return calendar.is_open_on_minute(dt_minute) cdef class Equity(Asset): @@ -291,6 +295,7 @@ cdef class Future(Asset): def __init__(self, int sid, # sid is required + object exchange, # exchange is required object symbol="", object root_symbol="", object asset_name="", @@ -300,19 +305,18 @@ cdef class Future(Asset): object expiration_date=None, object auto_close_date=None, object first_traded=None, - object exchange="", object tick_size="", float multiplier=1.0): super().__init__( sid, + exchange, symbol=symbol, asset_name=asset_name, start_date=start_date, end_date=end_date, first_traded=first_traded, auto_close_date=auto_close_date, - exchange=exchange, ) self.root_symbol = root_symbol self.notice_date = notice_date @@ -347,6 +351,7 @@ cdef class Future(Asset): be serialized/deserialized during pickling. """ return (self.__class__, (self.sid, + self.exchange, self.symbol, self.root_symbol, self.asset_name, @@ -356,7 +361,6 @@ cdef class Future(Asset): self.expiration_date, self.auto_close_date, self.first_traded, - self.exchange, self.tick_size, self.multiplier,)) diff --git a/zipline/assets/synthetic.py b/zipline/assets/synthetic.py index b071d3d0..c3f173dd 100644 --- a/zipline/assets/synthetic.py +++ b/zipline/assets/synthetic.py @@ -217,6 +217,7 @@ def make_future_info(first_sid, 'notice_date': notice_date_func(month_begin), 'expiration_date': notice_date_func(month_begin), 'multiplier': 500, + 'exchange': "TEST", }) return pd.DataFrame.from_records(contracts, index='sid').convert_objects() diff --git a/zipline/data/bundles/core.py b/zipline/data/bundles/core.py index 89064577..f9525e05 100644 --- a/zipline/data/bundles/core.py +++ b/zipline/data/bundles/core.py @@ -28,7 +28,7 @@ from zipline.utils.compat import mappingproxy from zipline.utils.input_validation import ensure_timestamp, optionally import zipline.utils.paths as pth from zipline.utils.preprocess import preprocess -from zipline.utils.calendars import get_calendar +from zipline.utils.calendars import get_calendar, register_calendar nyse_cal = get_calendar('NYSE') trading_days = nyse_cal.all_sessions @@ -564,3 +564,6 @@ def _make_bundle_core(): return BundleCore(bundles, register, unregister, ingest, load, clean) bundles, register, unregister, ingest, load, clean = _make_bundle_core() + +register_calendar("YAHOO", get_calendar("NYSE")) +register_calendar("QUANDL", get_calendar("NYSE")) diff --git a/zipline/data/bundles/quandl.py b/zipline/data/bundles/quandl.py index ba0526f5..f0343c92 100644 --- a/zipline/data/bundles/quandl.py +++ b/zipline/data/bundles/quandl.py @@ -123,7 +123,7 @@ def fetch_symbol_metadata_frame(api_key, # cut out all the other stuff in the name column # we need to escape the paren because it is actually splitting on a regex data.asset_name = data.asset_name.str.split(r' \(', 1).str.get(0) - data['exchange'] = 'quandl' + data['exchange'] = 'QUANDL' data['auto_close_date'] = data['end_date'] + pd.Timedelta(days=1) return data diff --git a/zipline/data/bundles/yahoo.py b/zipline/data/bundles/yahoo.py index db9ce298..4a317516 100644 --- a/zipline/data/bundles/yahoo.py +++ b/zipline/data/bundles/yahoo.py @@ -123,6 +123,11 @@ def yahoo_equities(symbols, start=None, end=None): daily_bar_writer.write(_pricing_iter(), show_progress=show_progress) symbol_map = pd.Series(metadata.symbol.index, metadata.symbol) + + # Hardcode the exchange to "YAHOO" for all assets and (elsewhere) + # register "YAHOO" to resolve to the NYSE calendar, because these are + # all equities and thus can use the NYSE calendar. + metadata['exchange'] = "YAHOO" asset_db_writer.write(equities=metadata) adjustments = [] diff --git a/zipline/data/daily_history_aggregator.py b/zipline/data/daily_history_aggregator.py index cb108004..1e710d7b 100644 --- a/zipline/data/daily_history_aggregator.py +++ b/zipline/data/daily_history_aggregator.py @@ -15,8 +15,6 @@ import numpy as np import pandas as pd -from pandas.tslib import normalize_date - class DailyHistoryAggregator(object): """ @@ -32,9 +30,10 @@ class DailyHistoryAggregator(object): """ - def __init__(self, market_opens, minute_reader): + def __init__(self, market_opens, minute_reader, trading_calendar): self._market_opens = market_opens self._minute_reader = minute_reader + self._trading_calendar = trading_calendar # The caches are structured as (date, market_open, entries), where # entries is a dict of asset -> (last_visited_dt, value) @@ -97,10 +96,10 @@ class DailyHistoryAggregator(object): market_open, prev_dt, dt_value, entries = self._prelude(dt, 'open') opens = [] - normalized_date = normalize_date(dt) + session_label = self._trading_calendar.minute_to_session_label(dt) for asset in assets: - if not asset._is_alive(normalized_date, True): + if not asset.is_alive_for_session(session_label): opens.append(np.NaN) continue @@ -166,10 +165,10 @@ class DailyHistoryAggregator(object): market_open, prev_dt, dt_value, entries = self._prelude(dt, 'high') highs = [] - normalized_date = normalize_date(dt) + session_label = self._trading_calendar.minute_to_session_label(dt) for asset in assets: - if not asset._is_alive(normalized_date, True): + if not asset.is_alive_for_session(session_label): highs.append(np.NaN) continue @@ -235,10 +234,10 @@ class DailyHistoryAggregator(object): market_open, prev_dt, dt_value, entries = self._prelude(dt, 'low') lows = [] - normalized_date = normalize_date(dt) + session_label = self._trading_calendar.minute_to_session_label(dt) for asset in assets: - if not asset._is_alive(normalized_date, True): + if not asset.is_alive_for_session(session_label): lows.append(np.NaN) continue @@ -305,10 +304,10 @@ class DailyHistoryAggregator(object): market_open, prev_dt, dt_value, entries = self._prelude(dt, 'close') closes = [] - normalized_dt = normalize_date(dt) + session_label = self._trading_calendar.minute_to_session_label(dt) for asset in assets: - if not asset._is_alive(normalized_dt, True): + if not asset.is_alive_for_session(session_label): closes.append(np.NaN) continue @@ -365,10 +364,10 @@ class DailyHistoryAggregator(object): market_open, prev_dt, dt_value, entries = self._prelude(dt, 'volume') volumes = [] - normalized_date = normalize_date(dt) + session_label = self._trading_calendar.minute_to_session_label(dt) for asset in assets: - if not asset._is_alive(normalized_date, True): + if not asset.is_alive_for_session(session_label): volumes.append(0) continue diff --git a/zipline/data/data_portal.py b/zipline/data/data_portal.py index 80b13988..54f26d50 100644 --- a/zipline/data/data_portal.py +++ b/zipline/data/data_portal.py @@ -150,7 +150,9 @@ class DataPortal(object): if self._equity_minute_reader is not None: self._equity_daily_aggregator = DailyHistoryAggregator( self.trading_calendar.schedule.market_open, - self._equity_minute_reader) + self._equity_minute_reader, + self.trading_calendar + ) self._equity_minute_history_loader = USEquityMinuteHistoryLoader( self.trading_calendar, self._equity_minute_reader, diff --git a/zipline/examples/__init__.py b/zipline/examples/__init__.py index 667fdd17..8a06f8a1 100644 --- a/zipline/examples/__init__.py +++ b/zipline/examples/__init__.py @@ -7,6 +7,8 @@ from zipline import run_algorithm # These are used by test_examples.py to discover the examples to run. +from zipline.utils.calendars import register_calendar, get_calendar + EXAMPLE_MODULES = {} for f in os.listdir(os.path.dirname(__file__)): if not f.endswith('.py') or f == '__init__.py': @@ -65,6 +67,9 @@ def run_example(example_name, environ): Run an example module from zipline.examples. """ mod = EXAMPLE_MODULES[example_name] + + register_calendar("YAHOO", get_calendar("NYSE"), force=True) + return run_algorithm( initialize=getattr(mod, 'initialize', None), handle_data=getattr(mod, 'handle_data', None), diff --git a/zipline/finance/cancel_policy.py b/zipline/finance/cancel_policy.py index 6a33cd87..77f7b363 100644 --- a/zipline/finance/cancel_policy.py +++ b/zipline/finance/cancel_policy.py @@ -17,7 +17,7 @@ import abc from abc import abstractmethod from six import with_metaclass -from zipline.gens.sim_engine import DAY_END +from zipline.gens.sim_engine import SESSION_END class CancelPolicy(with_metaclass(abc.ABCMeta)): @@ -58,7 +58,7 @@ class EODCancel(CancelPolicy): self.warn_on_cancel = warn_on_cancel def should_cancel(self, event): - return event == DAY_END + return event == SESSION_END class NeverCancel(CancelPolicy): diff --git a/zipline/gens/sim_engine.pyx b/zipline/gens/sim_engine.pyx index 5ad89682..aa3a9d51 100644 --- a/zipline/gens/sim_engine.pyx +++ b/zipline/gens/sim_engine.pyx @@ -24,63 +24,94 @@ NANOS_IN_MINUTE = _nanos_in_minute cpdef enum: BAR = 0 - DAY_START = 1 - DAY_END = 2 + SESSION_START = 1 + SESSION_END = 2 MINUTE_END = 3 + BEFORE_TRADING_START_BAR = 4 cdef class MinuteSimulationClock: - cdef object trading_days cdef bool minute_emission - cdef np.int64_t[:] market_opens, market_closes - cdef public dict minutes_by_day, minutes_to_day + cdef np.int64_t[:] market_opens_nanos, market_closes_nanos, bts_nanos, \ + sessions_nanos + cdef dict minutes_by_session def __init__(self, - trading_days, + sessions, market_opens, market_closes, + before_trading_start_minutes, minute_emission=False): self.minute_emission = minute_emission - self.market_opens = market_opens - self.market_closes = market_closes - self.trading_days = trading_days - self.minutes_by_day = self.calc_minutes_by_day() + + self.market_opens_nanos = market_opens.values.astype(np.int64) + self.market_closes_nanos = market_closes.values.astype(np.int64) + self.sessions_nanos = sessions.values.astype(np.int64) + self.bts_nanos = before_trading_start_minutes.values.astype(np.int64) + + self.minutes_by_session = self.calc_minutes_by_session() @cython.boundscheck(False) @cython.wraparound(False) - cdef np.ndarray[np.int64_t, ndim=1] market_minutes(self, np.intp_t i): - cdef np.int64_t[:] market_opens, market_closes + cdef dict calc_minutes_by_session(self): + cdef dict minutes_by_session + cdef int session_idx + cdef np.int64_t session_nano + cdef np.ndarray[np.int64_t, ndim=1] minutes_nanos - market_opens = self.market_opens - market_closes = self.market_closes - - return np.arange(market_opens[i], - market_closes[i] + _nanos_in_minute, - _nanos_in_minute) - - @cython.boundscheck(False) - @cython.wraparound(False) - cdef dict calc_minutes_by_day(self): - cdef dict minutes_by_day - cdef int day_idx - cdef object day - - minutes_by_day = {} - for day_idx, day in enumerate(self.trading_days): - minutes_by_day[day] = pd.to_datetime( - self.market_minutes(day_idx), utc=True, box=True) - return minutes_by_day + minutes_by_session = {} + for session_idx, session_nano in enumerate(self.sessions_nanos): + minutes_nanos = np.arange( + self.market_opens_nanos[session_idx], + self.market_closes_nanos[session_idx] + _nanos_in_minute, + _nanos_in_minute + ) + minutes_by_session[session_nano] = pd.to_datetime( + minutes_nanos, utc=True, box=True + ) + return minutes_by_session def __iter__(self): minute_emission = self.minute_emission - for day in self.trading_days: - yield day, DAY_START + for idx, session_nano in enumerate(self.sessions_nanos): + yield pd.Timestamp(session_nano, tz='UTC'), SESSION_START - minutes = self.minutes_by_day[day] + bts_minute = pd.Timestamp(self.bts_nanos[idx], tz='UTC') + regular_minutes = self.minutes_by_session[session_nano] - for minute in minutes: - yield minute, BAR - if minute_emission: - yield minute, MINUTE_END + if bts_minute > regular_minutes[-1]: + # before_trading_start is after the last close, + # so don't emit it + for minute, evt in self._get_minutes_for_list( + regular_minutes, + minute_emission + ): + yield minute, evt + else: + # we have to search anew every session, because there is no + # guarantee that any two session start on the same minute + bts_idx = regular_minutes.searchsorted(bts_minute) - yield minutes[-1], DAY_END + # emit all the minutes before bts_minute + for minute, evt in self._get_minutes_for_list( + regular_minutes[0:bts_idx], + minute_emission + ): + yield minute, evt + + yield bts_minute, BEFORE_TRADING_START_BAR + + # emit all the minutes after bts_minute + for minute, evt in self._get_minutes_for_list( + regular_minutes[bts_idx:], + minute_emission + ): + yield minute, evt + + yield regular_minutes[-1], SESSION_END + + def _get_minutes_for_list(self, minutes, minute_emission): + for minute in minutes: + yield minute, BAR + if minute_emission: + yield minute, MINUTE_END diff --git a/zipline/gens/tradesimulation.py b/zipline/gens/tradesimulation.py index cd5eb320..b8bbe2bb 100644 --- a/zipline/gens/tradesimulation.py +++ b/zipline/gens/tradesimulation.py @@ -21,9 +21,10 @@ from six import viewkeys from zipline.gens.sim_engine import ( BAR, - DAY_START, - DAY_END, - MINUTE_END + SESSION_START, + SESSION_END, + MINUTE_END, + BEFORE_TRADING_START_BAR ) log = Logger('Trade Simulation') @@ -181,9 +182,6 @@ class AlgorithmSimulator(object): algo.blotter.process_splits(splits) perf_tracker.position_tracker.handle_splits(splits) - # call before trading start - algo.before_trading_start(current_data) - def handle_benchmark(date, benchmark_source=self.benchmark_source): algo.perf_tracker.all_benchmark_returns[date] = \ benchmark_source.get_value(date) @@ -202,7 +200,7 @@ class AlgorithmSimulator(object): if algo.data_frequency == 'minute': def execute_order_cancellation_policy(): - algo.blotter.execute_cancel_policy(DAY_END) + algo.blotter.execute_cancel_policy(SESSION_END) def calculate_minute_capital_changes(dt): # process any capital changes that came between the last @@ -220,16 +218,20 @@ class AlgorithmSimulator(object): if action == BAR: for capital_change_packet in every_bar(dt): yield capital_change_packet - elif action == DAY_START: + elif action == SESSION_START: for capital_change_packet in once_a_day(dt): yield capital_change_packet - elif action == DAY_END: - # End of the day. + elif action == SESSION_END: + # End of the session. if emission_rate == 'daily': handle_benchmark(normalize_date(dt)) execute_order_cancellation_policy() yield self._get_daily_message(dt, algo, algo.perf_tracker) + elif action == BEFORE_TRADING_START_BAR: + # call before trading start + algo.on_dt_changed(dt) + algo.before_trading_start(self.current_data) elif action == MINUTE_END: handle_benchmark(dt) minute_msg = \ diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index 7ac81a06..5282b4b7 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -525,14 +525,14 @@ class SetLongOnlyAlgorithm(TradingAlgorithm): class SetAssetDateBoundsAlgorithm(TradingAlgorithm): """ - Algorithm that tries to order 1 share of sid 0 on every bar and has an + Algorithm that tries to order 1 share of sid 999 on every bar and has an AssetDateBounds() trading control in place. """ def initialize(self): self.register_trading_control(AssetDateBounds()) def handle_data(algo, data): - algo.order(algo.sid(0), 1) + algo.order(algo.sid(999), 1) class TestRegisterTransformAlgorithm(TradingAlgorithm): diff --git a/zipline/testing/fixtures.py b/zipline/testing/fixtures.py index bd8bc415..040ace99 100644 --- a/zipline/testing/fixtures.py +++ b/zipline/testing/fixtures.py @@ -37,7 +37,7 @@ from zipline.pipeline import SimplePipelineEngine from zipline.pipeline.loaders.testing import make_seeded_random_loader from zipline.utils.calendars import ( get_calendar, -) + register_calendar) class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)): @@ -336,6 +336,8 @@ class WithAssetFinder(WithDefaultDateBounds): @classmethod def make_equity_info(cls): + register_calendar("TEST", get_calendar("NYSE"), force=True) + return make_simple_equity_info( cls.ASSET_FINDER_EQUITY_SIDS, cls.ASSET_FINDER_EQUITY_START_DATE, diff --git a/zipline/utils/calendars/calendar_utils.py b/zipline/utils/calendars/calendar_utils.py index 16f2ca4f..fc872601 100644 --- a/zipline/utils/calendars/calendar_utils.py +++ b/zipline/utils/calendars/calendar_utils.py @@ -28,24 +28,25 @@ def get_calendar(name): The desired calendar. """ if name not in _static_calendars: - if name == 'NYSE': + if name in ["NYSE", "NASDAQ", "BATS"]: cal = NYSEExchangeCalendar() - elif name == 'CME': + elif name in ["CME", "CBOT", "COMEX", "NYMEX"]: cal = CMEExchangeCalendar() + elif name in ["ICEUS", "NYFE"]: + cal = ICEExchangeCalendar() + elif name == "CFE": + cal = CFEExchangeCalendar() elif name == 'BMF': cal = BMFExchangeCalendar() elif name == 'LSE': cal = LSEExchangeCalendar() elif name == 'TSX': cal = TSXExchangeCalendar() - elif name == "ICE": - cal = ICEExchangeCalendar() - elif name == "CFE": - cal = CFEExchangeCalendar() + else: raise InvalidCalendarName(calendar_name=name) - register_calendar(cal) + register_calendar(name, cal) return _static_calendars[name] @@ -72,13 +73,15 @@ def clear_calendars(): _static_calendars.clear() -def register_calendar(calendar, force=False): +def register_calendar(name, calendar, force=False): """ Registers a calendar for retrieval by the get_calendar method. Parameters ---------- - calendar : TradingCalendar + name: str + The key with which to register this calendar. + calendar: TradingCalendar The calendar to be registered for retrieval. force : bool, optional If True, old calendars will be overwritten on a name collision. @@ -92,10 +95,10 @@ def register_calendar(calendar, force=False): # If we are forcing the registration, remove an existing calendar with the # same name. if force: - deregister_calendar(calendar.name) + deregister_calendar(name) # Check if we are already holding a calendar with the same name - if calendar.name in _static_calendars: - raise CalendarNameCollision(calendar_name=calendar.name) + if name in _static_calendars: + raise CalendarNameCollision(calendar_name=name) - _static_calendars[calendar.name] = calendar + _static_calendars[name] = calendar diff --git a/zipline/utils/calendars/trading_calendar.py b/zipline/utils/calendars/trading_calendar.py index 6d782933..e02b2ffc 100644 --- a/zipline/utils/calendars/trading_calendar.py +++ b/zipline/utils/calendars/trading_calendar.py @@ -757,12 +757,18 @@ def days_at_time(days, t, tz, day_offset=0): day_offset : int The number of days we want to offset @days by """ + if len(days) == 0: + return days + # Offset days without tz to avoid timezone issues. days = DatetimeIndex(days).tz_localize(None) days_offset = days + DateOffset(days=day_offset) # Shift all days to the target time in the local timezone, then # convert to UTC. + + # FIXME: Once we're off Pandas 16, see if we can replace DateOffset with + # TimeDelta. return days_offset.shift( 1, freq=DateOffset(hour=t.hour, minute=t.minute, second=t.second) ).tz_localize(tz).tz_convert('UTC')