Merge pull request #782 from quantopian/future-chain-start-date

MAINT: Removes unneeded knowledge_date logic from future_chain
This commit is contained in:
James Kirk
2015-10-19 11:53:43 -04:00
3 changed files with 45 additions and 58 deletions
+39 -38
View File
@@ -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)
+2 -14
View File
@@ -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.
+4 -6
View File
@@ -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