From 759f346c93034edf646c284c3342a5af5bf61832 Mon Sep 17 00:00:00 2001 From: Andrew Daniels Date: Mon, 29 Jun 2015 09:51:50 -0400 Subject: [PATCH] BUG: Fixes issues with AssetFinder future lookups Contracts must have been trading at the as_of_date to be considered valid, and a contract's position in the chain is now zero-indexed. --- tests/test_assets.py | 42 ++++++++++++++++++++++++++++------------ zipline/assets/assets.py | 10 +++++----- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/tests/test_assets.py b/tests/test_assets.py index 1c8f3df9..f2c6765a 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -581,19 +581,22 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADN15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC') + 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, 1: { 'symbol': 'ADV15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, 0: { 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, } @@ -602,13 +605,12 @@ class AssetFinderTestCase(TestCase): dt = pd.Timestamp('2015-06-19', tz='UTC') # Check that the primary and secondary contracts are as expected - primary = finder.lookup_future_in_chain('AD', dt, 1) - secondary = finder.lookup_future_in_chain('AD', dt, 2) + primary = finder.lookup_future_in_chain('AD', dt, 0) + secondary = finder.lookup_future_in_chain('AD', dt, 1) self.assertEqual(primary.sid, 1) self.assertEqual(secondary.sid, 0) # Check that we get None for an invalid contract num - self.assertIsNone(finder.lookup_future_in_chain('AD', dt, 0)) self.assertIsNone(finder.lookup_future_in_chain('AD', dt, -10)) self.assertIsNone(finder.lookup_future_in_chain('AD', dt, 10)) self.assertIsNone(finder.lookup_future_in_chain('CL', dt, 1)) @@ -619,19 +621,32 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADN15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC') + 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, 1: { 'symbol': 'ADV15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, + # Starts trading today, so should be valid. 0: { 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-06-19', tz='UTC') + }, + # Copy of the above future, but starts trading in August, + # so it isn't valid. + 3: { + 'symbol': 'ADF16', + 'root_symbol': 'AD', + 'asset_type': 'future', + 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-08-01', tz='UTC') }, } @@ -652,19 +667,22 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADN15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC') + 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, 1: { 'symbol': 'ADV15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, 0: { 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC') + 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), + 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, } diff --git a/zipline/assets/assets.py b/zipline/assets/assets.py index f100a2ec..c05960d4 100644 --- a/zipline/assets/assets.py +++ b/zipline/assets/assets.py @@ -247,7 +247,8 @@ class AssetFinder(object): """ try: return [c for c in self.future_chains_cache[root_symbol] - if c.expiration_date > as_of_date] + if c.expiration_date and (c.expiration_date > as_of_date) + and c.start_date and (c.start_date <= as_of_date)] except KeyError: return None @@ -269,7 +270,7 @@ class AssetFinder(object): as_of_date = normalize_date(as_of_date) return self._valid_contracts(root_symbol, as_of_date) - def lookup_future_in_chain(self, root_symbol, as_of_date, contract_num=1): + def lookup_future_in_chain(self, root_symbol, as_of_date, contract_num=0): """ Find a specific contract in the futures chain for a given root symbol. @@ -293,11 +294,10 @@ class AssetFinder(object): as_of_date = normalize_date(as_of_date) valid_contracts = self._valid_contracts(root_symbol, as_of_date) - contract_index = contract_num - 1 - if valid_contracts and contract_index >= 0: + if valid_contracts and contract_num >= 0: try: - return valid_contracts[contract_index] + return valid_contracts[contract_num] except IndexError: pass