From 7cde3939bf962f8cde12901a6009c0e5001ae059 Mon Sep 17 00:00:00 2001 From: Andrew Daniels Date: Thu, 9 Jul 2015 15:03:00 -0400 Subject: [PATCH] BUG: Determine valid future contracts with notice date Since most brokers will cease accepting trades by the notice date, contracts should not be considered valid after the notice date. This commit adjusts the lookup_future_chain method to consider all contracts with notice dates on or following the current date invalid. --- tests/test_assets.py | 23 +++++++++++------------ zipline/assets/assets.py | 6 +++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/test_assets.py b/tests/test_assets.py index e66c8331..1f014d51 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -542,19 +542,19 @@ class AssetFinderTestCase(TestCase): def test_lookup_future_chain(self): metadata = { - # Expires today, so should be valid + # Notice day is today, so not valid 2: { 'symbol': 'ADN15', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-06-15', tz='UTC'), + 'notice_date': pd.Timestamp('2015-05-14', 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'), + 'notice_date': pd.Timestamp('2015-08-14', tz='UTC'), 'start_date': pd.Timestamp('2015-01-01', tz='UTC') }, # Starts trading today, so should be valid. @@ -562,8 +562,8 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), - 'start_date': pd.Timestamp('2015-06-15', tz='UTC') + 'notice_date': pd.Timestamp('2015-11-16', tz='UTC'), + 'start_date': pd.Timestamp('2015-05-14', tz='UTC') }, # Copy of the above future, but starts trading in August, # so it isn't valid. @@ -571,24 +571,23 @@ class AssetFinderTestCase(TestCase): 'symbol': 'ADF16', 'root_symbol': 'AD', 'asset_type': 'future', - 'expiration_date': pd.Timestamp('2015-12-14', tz='UTC'), + 'notice_date': pd.Timestamp('2015-11-16', tz='UTC'), 'start_date': pd.Timestamp('2015-08-01', tz='UTC') }, } finder = AssetFinder(metadata=metadata) - dt = pd.Timestamp('2015-06-15', tz='UTC') + 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') # 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) - self.assertEqual(ad_contracts[1].sid, 1) - self.assertEqual(ad_contracts[2].sid, 0) + self.assertEqual(len(ad_contracts), 2) + self.assertEqual(ad_contracts[0].sid, 1) + self.assertEqual(ad_contracts[1].sid, 0) # Check that we get nothing if our knowledge date is last year ad_contracts = finder.lookup_future_chain('AD', dt, last_year) @@ -596,7 +595,7 @@ class AssetFinderTestCase(TestCase): # 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) + self.assertEqual(len(ad_contracts), 1) def test_map_identifier_index_to_sids(self): # Build an empty finder and some Assets diff --git a/zipline/assets/assets.py b/zipline/assets/assets.py index 1f26d9c9..6d5a702d 100644 --- a/zipline/assets/assets.py +++ b/zipline/assets/assets.py @@ -214,10 +214,10 @@ class AssetFinder(object): """ Sort by increasing expiration date the list of contracts for each root symbol in the future cache. """ - exp_key = operator.attrgetter('expiration_date') + notice_key = operator.attrgetter('notice_date') for root_symbol in self.future_chains_cache: - self.future_chains_cache[root_symbol].sort(key=exp_key) + self.future_chains_cache[root_symbol].sort(key=notice_key) def lookup_future_chain(self, root_symbol, as_of_date, knowledge_date): """ Return the futures chain for a given root symbol. @@ -241,7 +241,7 @@ class AssetFinder(object): """ try: return [c for c in self.future_chains_cache[root_symbol] - if c.expiration_date and (as_of_date <= c.expiration_date) + if c.notice_date and (as_of_date < c.notice_date) and c.start_date and (c.start_date <= knowledge_date)] except KeyError: raise RootSymbolNotFound(root_symbol=root_symbol)