diff --git a/zipline/assets/_assets.pyx b/zipline/assets/_assets.pyx index e3cac229..367dcbf1 100644 --- a/zipline/assets/_assets.pyx +++ b/zipline/assets/_assets.pyx @@ -37,7 +37,7 @@ cimport numpy as np # IMPORTANT NOTE: You must change this template if you change # Asset.__reduce__, or else we'll attempt to unpickle an old version of this # class -CACHE_FILE_TEMPLATE = '/tmp/.%s-%s.v5.cache' +CACHE_FILE_TEMPLATE = '/tmp/.%s-%s.v6.cache' cdef class Asset: @@ -51,6 +51,7 @@ cdef class Asset: cdef readonly object start_date cdef readonly object end_date cdef public object first_traded + cdef readonly object auto_close_date cdef readonly object exchange @@ -61,6 +62,7 @@ cdef class Asset: object start_date=None, object end_date=None, object first_traded=None, + object auto_close_date=None, object exchange="", *args, **kwargs): @@ -73,6 +75,7 @@ cdef class Asset: self.start_date = start_date self.end_date = end_date self.first_traded = first_traded + self.auto_close_date = auto_close_date def __int__(self): return self.sid @@ -127,7 +130,7 @@ cdef class Asset: def __repr__(self): attrs = ('symbol', 'asset_name', 'exchange', - 'start_date', 'end_date', 'first_traded') + 'start_date', 'end_date', 'first_traded', 'auto_close_date') tuples = ((attr, repr(getattr(self, attr, None))) for attr in attrs) strings = ('%s=%s' % (t[0], t[1]) for t in tuples) @@ -147,6 +150,7 @@ cdef class Asset: self.start_date, self.end_date, self.first_traded, + self.auto_close_date, self.exchange,)) cpdef to_dict(self): @@ -160,6 +164,7 @@ cdef class Asset: 'start_date': self.start_date, 'end_date': self.end_date, 'first_traded': self.first_traded, + 'auto_close_date': self.auto_close_date, 'exchange': self.exchange, } @@ -181,7 +186,7 @@ cdef class Equity(Asset): def __repr__(self): attrs = ('symbol', 'asset_name', 'exchange', - 'start_date', 'end_date', 'first_traded') + 'start_date', 'end_date', 'first_traded', 'auto_close_date') tuples = ((attr, repr(getattr(self, attr, None))) for attr in attrs) strings = ('%s=%s' % (t[0], t[1]) for t in tuples) @@ -227,10 +232,8 @@ 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 object tick_size cdef readonly float multiplier - cdef readonly object effective_expiration def __cinit__(self, int sid, # sid is required @@ -250,16 +253,16 @@ 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.tick_size = tick_size self.multiplier = multiplier - if notice_date is None: - self.effective_expiration = expiration_date - elif expiration_date is None: - self.effective_expiration = notice_date - else: - self.effective_expiration = min(notice_date, expiration_date) + if auto_close_date is None: + if notice_date is None: + self.auto_close_date = expiration_date + elif expiration_date is None: + self.auto_close_date = notice_date + else: + self.auto_close_date = min(notice_date, expiration_date) def __str__(self): if self.symbol: @@ -307,7 +310,6 @@ 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['tick_size'] = self.tick_size super_dict['multiplier'] = self.multiplier return super_dict diff --git a/zipline/gens/tradesimulation.py b/zipline/gens/tradesimulation.py index 3a8459c1..23ae30db 100644 --- a/zipline/gens/tradesimulation.py +++ b/zipline/gens/tradesimulation.py @@ -19,6 +19,7 @@ from contextlib2 import ExitStack from logbook import Logger, Processor from pandas.tslib import normalize_date +from zipline.errors import SidsNotFound from zipline.finance.trading import NoFurtherDataError from zipline.protocol import ( BarData, @@ -57,19 +58,32 @@ class AlgorithmSimulator(object): # Snapshot Setup # ============== - def _get_effective_expiration(sid, - finder=self.env.asset_finder, - default=self.sim_params.last_close - + timedelta(days=1)): - asset = finder.retrieve_asset(sid) - return getattr(asset, 'effective_expiration', None) or default + def _get_asset_close_date(sid, + finder=self.env.asset_finder, + default=self.sim_params.last_close + + timedelta(days=1)): + try: + asset = finder.retrieve_asset(sid) + except ValueError: + # Handle sid not an int, such as from a custom source. + # So that they don't compare equal to other sids, and we'd + # blow up comparing strings to ints, let's give them unique + # close dates. + return default + timedelta(microseconds=id(sid)) + except SidsNotFound: + return default + # Default is used when the asset has no auto close date, + # and is set to a time after the simulation ends, so that the + # relevant asset isn't removed from the universe at all + # (at least not for this reason). + return asset.auto_close_date or default - self._get_expiration = _get_effective_expiration + self._get_asset_close = _get_asset_close_date # The algorithm's data as of our most recent event. - # We want an object that will have empty objects as default - # values on missing keys. - self.current_data = BarData(SortedDict(self._get_expiration)) + # Maintain sids in order by asset close date, so that we can more + # efficiently remove them when their times come... + self.current_data = BarData(SortedDict(self._get_asset_close)) # We don't have a datetime for the current snapshot until we # receive a message. @@ -110,11 +124,11 @@ class AlgorithmSimulator(object): self.simulation_dt = date self.on_dt_changed(date) - expired = list(takewhile( - lambda asset_id: self._get_expiration(asset_id) < date, + closed = list(takewhile( + lambda asset_id: self._get_asset_close(asset_id) < date, self.current_data )) - for sid in expired: + for sid in closed: try: del self.current_data[sid] except KeyError: