From a6ce1e5e8de4f586b4c0bd6a8a923a66b814b53a Mon Sep 17 00:00:00 2001 From: jfkirk Date: Thu, 13 Aug 2015 12:42:13 -0400 Subject: [PATCH] ENH: Adds auto_close_date field to Future objects --- tests/test_algorithm.py | 3 +-- tests/test_assets.py | 4 ++++ zipline/assets/_assets.pyx | 7 +++++- zipline/assets/asset_writer.py | 5 +++++ zipline/assets/assets.py | 4 ++++ .../finance/performance/position_tracker.py | 22 +++++++------------ 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 70e83536..94cf4d18 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -1702,8 +1702,7 @@ class TestClosePosAlgo(TestCase): def test_auto_close_future(self): metadata = {1: {'symbol': 'TEST', 'asset_type': 'future', - 'notice_date': self.days[3], - 'expiration_date': self.days[4]}} + 'auto_close_date': self.days[3]}} self.env.write_data(futures_data=metadata) self.algo = TestAlgorithm(sid=1, amount=1, order_count=1, instant_fill=True, commission=PerShare(0), diff --git a/tests/test_assets.py b/tests/test_assets.py index 098194e5..d2efde6d 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -239,6 +239,7 @@ class TestFuture(TestCase): root_symbol='OM', notice_date=pd.Timestamp('2014-01-20', tz='UTC'), expiration_date=pd.Timestamp('2014-02-20', tz='UTC'), + auto_close_date=pd.Timestamp('2014-01-18', tz='UTC'), contract_multiplier=500 ) @@ -256,6 +257,8 @@ class TestFuture(TestCase): "tz='UTC')") in reprd) self.assertTrue("expiration_date=Timestamp('2014-02-20 00:00:00+0000'" in reprd) + self.assertTrue("auto_close_date=Timestamp('2014-01-18 00:00:00+0000'" + in reprd) self.assertTrue("contract_multiplier=500" in reprd) def test_reduce(self): @@ -267,6 +270,7 @@ class TestFuture(TestCase): self.assertTrue('root_symbol' in dictd) self.assertTrue('notice_date' in dictd) self.assertTrue('expiration_date' in dictd) + self.assertTrue('auto_close_date' in dictd) self.assertTrue('contract_multiplier' in dictd) from_dict = Future.from_dict(dictd) diff --git a/zipline/assets/_assets.pyx b/zipline/assets/_assets.pyx index e2189b91..376f0cf5 100644 --- a/zipline/assets/_assets.pyx +++ b/zipline/assets/_assets.pyx @@ -227,6 +227,7 @@ cdef class Future(Asset): cdef readonly object root_symbol cdef readonly object notice_date cdef readonly object expiration_date + cdef readonly object auto_close_date cdef readonly float contract_multiplier def __cinit__(self, @@ -238,6 +239,7 @@ cdef class Future(Asset): object end_date=None, object notice_date=None, object expiration_date=None, + object auto_close_date=None, object first_traded=None, object exchange="", float contract_multiplier=1): @@ -245,6 +247,7 @@ cdef class Future(Asset): self.root_symbol = root_symbol self.notice_date = notice_date self.expiration_date = expiration_date + self.auto_close_date = auto_close_date self.contract_multiplier = contract_multiplier def __str__(self): @@ -256,7 +259,7 @@ cdef class Future(Asset): def __repr__(self): attrs = ('symbol', 'root_symbol', 'asset_name', 'exchange', 'start_date', 'end_date', 'first_traded', 'notice_date', - 'expiration_date', 'contract_multiplier') + 'expiration_date', 'auto_close_date', 'contract_multiplier') tuples = ((attr, repr(getattr(self, attr, None))) for attr in attrs) strings = ('%s=%s' % (t[0], t[1]) for t in tuples) @@ -278,6 +281,7 @@ cdef class Future(Asset): self.end_date, self.notice_date, self.expiration_date, + self.auto_close_date, self.first_traded, self.exchange, self.contract_multiplier,)) @@ -290,6 +294,7 @@ cdef class Future(Asset): super_dict['root_symbol'] = self.root_symbol super_dict['notice_date'] = self.notice_date super_dict['expiration_date'] = self.expiration_date + super_dict['auto_close_date'] = self.auto_close_date super_dict['contract_multiplier'] = self.contract_multiplier return super_dict diff --git a/zipline/assets/asset_writer.py b/zipline/assets/asset_writer.py index 526030fc..b3fd11e3 100644 --- a/zipline/assets/asset_writer.py +++ b/zipline/assets/asset_writer.py @@ -29,6 +29,7 @@ ASSET_TABLE_FIELDS = frozenset({ FUTURE_TABLE_FIELDS = ASSET_TABLE_FIELDS | { 'notice_date', 'expiration_date', + 'auto_close_date', 'contract_multiplier', } @@ -70,6 +71,7 @@ _futures_defaults = { 'exchange': None, 'notice_date': None, 'expiration_date': None, + 'auto_close_date': None, 'contract_multiplier': 1, } @@ -327,6 +329,7 @@ class AssetDBWriter(with_metaclass(ABCMeta)): ), sa.Column('notice_date', sa.Integer), sa.Column('expiration_date', sa.Integer), + sa.Column('auto_close_date', sa.Integer), sa.Column('contract_multiplier', sa.Float), ) self.asset_router = sa.Table( @@ -394,6 +397,8 @@ class AssetDBWriter(with_metaclass(ABCMeta)): futures_output['notice_date'].apply(self.convert_datetime) futures_output['expiration_date'] = \ futures_output['expiration_date'].apply(self.convert_datetime) + futures_output['auto_close_date'] = \ + futures_output['auto_close_date'].apply(self.convert_datetime) # Convert symbols and root_symbols to upper case. futures_output['symbol'] = futures_output.symbol.str.upper() diff --git a/zipline/assets/assets.py b/zipline/assets/assets.py index d9a25390..ded30371 100644 --- a/zipline/assets/assets.py +++ b/zipline/assets/assets.py @@ -259,6 +259,10 @@ class AssetFinder(object): data['expiration_date'] = pd.Timestamp( data['expiration_date'], tz='UTC') + if data['auto_close_date']: + data['auto_close_date'] = pd.Timestamp( + data['auto_close_date'], tz='UTC') + _convert_asset_str_fields(data) future = Future(**data) diff --git a/zipline/finance/performance/position_tracker.py b/zipline/finance/performance/position_tracker.py index 03149aa4..04e66a68 100644 --- a/zipline/finance/performance/position_tracker.py +++ b/zipline/finance/performance/position_tracker.py @@ -71,19 +71,12 @@ class PositionTracker(object): asset.contract_multiplier self._position_payout_multipliers[sid] = \ asset.contract_multiplier - # Futures are closed on their notice_date - if asset.notice_date: - self._insert_auto_close_position_date( - dt=asset.notice_date, - sid=sid - ) - # If the Future does not have a notice_date, it will be closed - # on its expiration_date - elif asset.expiration_date: - self._insert_auto_close_position_date( - dt=asset.expiration_date, - sid=sid - ) + # Futures auto-close timing is controlled by the Future's + # auto_close_date property + self._insert_auto_close_position_date( + dt=asset.auto_close_date, + sid=sid + ) def _insert_auto_close_position_date(self, dt, sid): """ @@ -97,7 +90,8 @@ class PositionTracker(object): sid : int The SID of the Asset to be auto-closed """ - self._auto_close_position_sids.setdefault(dt, set()).add(sid) + if dt is not None: + self._auto_close_position_sids.setdefault(dt, set()).add(sid) def auto_close_position_events(self, next_trading_day): """