From 8876092d29c57b15756e873ecfb1cd50bbe79aa3 Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Thu, 27 Oct 2016 16:48:34 -0400 Subject: [PATCH] BUG: Protect against contract offset at end of range. (#1564) This boundary case was exposed with internal fixture data which used a continuous future with a contract chain of size one. --- tests/test_continuous_futures.py | 22 ++++++++++++++++++++++ zipline/assets/continuous_futures.pyx | 12 ++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/test_continuous_futures.py b/tests/test_continuous_futures.py index 282ac624..a2df825a 100644 --- a/tests/test_continuous_futures.py +++ b/tests/test_continuous_futures.py @@ -835,6 +835,28 @@ def record_current_contract(algo, data): class OrderedContractsTestCase(ZiplineTestCase): + def test_contract_at_offset(self): + contract_sids = array([1, 2, 3, 4], dtype=int64) + start_dates = pd.date_range('2015-01-01', periods=4, tz="UTC") + auto_close_dates = pd.date_range('2016-04-01', periods=4, tz="UTC") + + oc = OrderedContracts('FO', + contract_sids, + start_dates.astype('int64'), + auto_close_dates.astype('int64')) + + self.assertEquals(1, + oc.contract_at_offset(1, 0, start_dates[-1].value), + "Offset of 0 should return provided sid") + + self.assertEquals(2, + oc.contract_at_offset(1, 1, start_dates[-1].value), + "Offset of 1 should return next sid in chain.") + + self.assertEquals(None, + oc.contract_at_offset(4, 1, start_dates[-1].value), + "Offset at end of chain should not crash.") + def test_active_chain(self): contract_sids = array([1, 2, 3, 4], dtype=int64) start_dates = pd.date_range('2015-01-01', periods=4, tz="UTC") diff --git a/zipline/assets/continuous_futures.pyx b/zipline/assets/continuous_futures.pyx index 572c3754..f8eeae34 100644 --- a/zipline/assets/continuous_futures.pyx +++ b/zipline/assets/continuous_futures.pyx @@ -302,16 +302,20 @@ cdef class OrderedContracts(object): Get the sid which is the given sid plus the offset distance. An offset of 0 should be reflexive. """ - cdef Py_ssize_t i + cdef Py_ssize_t i, j cdef long_t[:] sids sids = self.contract_sids start_dates = self.start_dates - for i in range(self._size): + i = 0 + j = i + offset + while j < self._size: if sid == sids[i]: - if start_dates[i + offset] < start_cap: - return sids[i + offset] + if start_dates[j] < start_cap: + return sids[j] else: return None + i += 1 + j += 1 cpdef long_t[:] active_chain(self, long_t starting_sid, long_t dt_value): cdef Py_ssize_t left, right, i, j