From 915c8e800f1b39e74687405ca95f693780242f5d Mon Sep 17 00:00:00 2001 From: jfkirk Date: Mon, 19 Oct 2015 11:37:56 -0400 Subject: [PATCH] MAINT: Removes unneeded knowledge_date logic from future_chain --- tests/test_assets.py | 77 ++++++++++++++++++++------------------- zipline/assets/assets.py | 16 +------- zipline/assets/futures.py | 10 ++--- 3 files changed, 45 insertions(+), 58 deletions(-) diff --git a/tests/test_assets.py b/tests/test_assets.py index 00a06665..10ed9f2d 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -627,7 +627,7 @@ class AssetFinderTestCase(TestCase): def test_lookup_future_chain(self): metadata = { # Notice day is today, so should be valid. - 2: { + 0: { 'symbol': 'ADN15', 'root_symbol': 'AD', 'asset_type': 'future', @@ -644,7 +644,7 @@ class AssetFinderTestCase(TestCase): 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, # Starts trading today, so should be valid. - 0: { + 2: { 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', @@ -666,45 +666,51 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADZ16', 'root_symbol': 'AD', 'asset_type': 'future', - 'notice_date': pd.Timestamp('2015-11-25', tz='UTC'), + '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') }, + # This contract has no start date and also this contract should be + # last in all chains + 5: { + 'symbol': 'ADZ20', + 'root_symbol': 'AD', + 'asset_type': 'future', + 'notice_date': pd.Timestamp('2020-11-25', tz='UTC'), + 'expiration_date': pd.Timestamp('2020-11-16', tz='UTC') + }, } self.env.write_data(futures_data=metadata) finder = AssetFinder(self.env.engine) dt = pd.Timestamp('2015-05-14', tz='UTC') - last_year = pd.Timestamp('2014-01-01', tz='UTC') - first_day = pd.Timestamp('2015-01-01', tz='UTC') - dt_2 = pd.Timestamp('2016-11-17', tz='UTC') + dt_2 = pd.Timestamp('2015-10-14', tz='UTC') + dt_3 = pd.Timestamp('2016-11-17', tz='UTC') # Check that we get the expected number of contracts, in the # right order - ad_contracts = finder.lookup_future_chain('AD', dt, dt) - self.assertEqual(len(ad_contracts), 3) - self.assertEqual(ad_contracts[0].sid, 2) + ad_contracts = finder.lookup_future_chain('AD', dt) + self.assertEqual(len(ad_contracts), 6) + self.assertEqual(ad_contracts[0].sid, 0) self.assertEqual(ad_contracts[1].sid, 1) + self.assertEqual(ad_contracts[5].sid, 5) - # Check that pd.NaT for knowledge_date uses the value of as_of_date - ad_contracts = finder.lookup_future_chain('AD', dt, pd.NaT) - self.assertEqual(len(ad_contracts), 3) + # Check that, when some contracts have expired, the chain has advanced + # properly to the next contracts + ad_contracts = finder.lookup_future_chain('AD', dt_2) + self.assertEqual(len(ad_contracts), 4) + self.assertEqual(ad_contracts[0].sid, 2) + self.assertEqual(ad_contracts[3].sid, 5) - # Check that we get nothing if our knowledge date is last year - ad_contracts = finder.lookup_future_chain('AD', dt, last_year) - self.assertEqual(len(ad_contracts), 0) - - # Check that we get things that start on the knowledge date - ad_contracts = finder.lookup_future_chain('AD', dt, first_day) - self.assertEqual(len(ad_contracts), 2) + # Check that when the expiration_date has passed but the + # notice_date hasn't, contract is still considered invalid. + ad_contracts = finder.lookup_future_chain('AD', dt_3) + self.assertEqual(len(ad_contracts), 1) + self.assertEqual(ad_contracts[0].sid, 5) # Check that pd.NaT for as_of_date gives the whole chain - ad_contracts = finder.lookup_future_chain('AD', pd.NaT, first_day) - self.assertEqual(len(ad_contracts), 5) - - # Check that when the expiration_date has past but the - # notice_date hasn't, contract is still considered invalid. - ad_contracts = finder.lookup_future_chain('AD', dt_2, dt_2) - self.assertEqual(len(ad_contracts), 0) + ad_contracts = finder.lookup_future_chain('AD', pd.NaT) + self.assertEqual(len(ad_contracts), 6) + self.assertEqual(ad_contracts[5].sid, 5) def test_map_identifier_index_to_sids(self): # Build an empty finder and some Assets @@ -843,21 +849,18 @@ class TestFutureChain(TestCase): def test_len(self): """ Test the __len__ method of FutureChain. """ - # None of the contracts have started yet. - cl = FutureChain(self.asset_finder, lambda: '2005-11-30', 'CL') - self.assertEqual(len(cl), 0) - - # Sids 0, 1, & 2 have started, 3 has not yet started. + # Sids 0, 1, & 2 have started, 3 has not yet started, but all are in + # the chain cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL') - self.assertEqual(len(cl), 3) + self.assertEqual(len(cl), 4) - # Sid 0 is still valid its notice date. + # Sid 0 is still valid on its notice date. cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL') - self.assertEqual(len(cl), 3) + self.assertEqual(len(cl), 4) - # Sid 0 is now invalid, leaving only Sids 1 & 2 valid. + # Sid 0 is now invalid, leaving Sids 1 & 2 valid (and 3 not started). cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL') - self.assertEqual(len(cl), 2) + self.assertEqual(len(cl), 3) # Sid 3 has started, so 1, 2, & 3 are now valid. cl = FutureChain(self.asset_finder, lambda: '2006-02-01', 'CL') @@ -874,8 +877,6 @@ class TestFutureChain(TestCase): self.assertEqual(cl[0], 0) self.assertEqual(cl[1], 1) self.assertEqual(cl[2], 2) - with self.assertRaises(IndexError): - cl[3] cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL') self.assertEqual(cl[0], 0) diff --git a/zipline/assets/assets.py b/zipline/assets/assets.py index 12eb68d1..b141e10c 100644 --- a/zipline/assets/assets.py +++ b/zipline/assets/assets.py @@ -343,26 +343,20 @@ class AssetFinder(object): return future - def lookup_future_chain(self, root_symbol, as_of_date, knowledge_date): + def lookup_future_chain(self, root_symbol, as_of_date): """ Return the futures chain for a given root symbol. Parameters ---------- root_symbol : str Root symbol of the desired future. - as_of_date : pd.Timestamp or pd.NaT + as_of_date : pd.Timestamp or pd.NaT Date at which the chain determination is rooted. I.e. the existing contract whose notice date/expiration date is first after this date is the primary contract, etc. If NaT is given, the chain is unbounded, and all contracts for this root symbol are returned. - knowledge_date : pd.Timestamp or pd.NaT - Date for determining which contracts exist for inclusion in - this chain. Contracts exist only if they have a start_date - on or before this date. If NaT is given and as_of_date is - is not NaT, the value of as_of_date is used for - knowledge_date. Returns ------- @@ -391,17 +385,11 @@ class AssetFinder(object): ).execute().fetchall())) else: as_of_date = as_of_date.value - if knowledge_date is pd.NaT: - # If knowledge_date is NaT, default to using as_of_date - knowledge_date = as_of_date - else: - knowledge_date = knowledge_date.value sids = list(map( itemgetter('sid'), sa.select((fc_cols.sid,)).where( (fc_cols.root_symbol == root_symbol) & - (fc_cols.start_date <= knowledge_date) & # Filter to contracts that are still valid. If both # exist, use the one that comes first in time (i.e. diff --git a/zipline/assets/futures.py b/zipline/assets/futures.py index a45270b2..7d61dfda 100644 --- a/zipline/assets/futures.py +++ b/zipline/assets/futures.py @@ -126,15 +126,13 @@ class FutureChain(object): list The up-to-date current chain, a list of Future objects. """ - dt = self._get_datetime() - - if (self._last_updated is None) or (self._last_updated != dt): + if (self._last_updated is None)\ + or (self._last_updated != self.as_of_date): self._current_chain = self._asset_finder.lookup_future_chain( self.root_symbol, - self.as_of_date, - dt + self.as_of_date ) - self._last_updated = dt + self._last_updated = self.as_of_date return self._current_chain