diff --git a/tests/test_continuous_futures.py b/tests/test_continuous_futures.py index a63778b6..fe3aaf8c 100644 --- a/tests/test_continuous_futures.py +++ b/tests/test_continuous_futures.py @@ -176,18 +176,20 @@ class ContinuousFuturesTestCase(WithCreateBarData, vol_stop_session = sid_to_vol_stop_session[i] m_open = tc.open_and_close_for_session(vol_stop_session)[0] loc = dts.searchsorted(m_open) - # Add a little bit of noise to roll. So that checks for exacly - # 0 do not work, since there may be stragglers after a roll. + # Add a little bit of noise to roll. So that predicates that + # check for exactly 0 do not work, since there may be + # stragglers after a roll. df.volume.values[loc] = 1000 df.volume.values[loc + 1:] = 0 j = i - 1 if j in sid_to_vol_stop_session: - non_primary_end = sid_to_vol_stop_session[j] - sessions.freq + non_primary_end = sid_to_vol_stop_session[j] m_close = tc.open_and_close_for_session(non_primary_end)[1] - loc = dts.searchsorted(m_close) - # Add some volume before a roll, since a contracted may be - # entered earlier than when it is the primary. - df.volume.values[:loc] = 2000 + if m_close > dts[0]: + loc = dts.get_loc(m_close) + # Add some volume before a roll, since a contract may be + # entered earlier than when it is the primary. + df.volume.values[:loc + 1] = 10 yield i, df def test_create_continuous_future(self): @@ -311,14 +313,14 @@ class ContinuousFuturesTestCase(WithCreateBarData, lambda: pd.Timestamp('2016-01-26', tz='UTC')) contract = bar_data.current(cf_primary, 'contract') - self.assertEqual(contract.symbol, 'FOG16') + self.assertEqual(contract.symbol, 'FOF16') bar_data = self.create_bardata( - lambda: pd.Timestamp('2016-01-26', tz='UTC')) + lambda: pd.Timestamp('2016-01-27', tz='UTC')) contract = bar_data.current(cf_primary, 'contract') self.assertEqual(contract.symbol, 'FOG16', - 'Auto close at beginning of session. FOG16 remains ' + 'Auto close at beginning of session. FOG16 is now ' 'the current contract.') bar_data = self.create_bardata( @@ -599,12 +601,12 @@ def record_current_contract(algo, data): # Volume cuts out for FOF16 on 2016-01-25 self.assertEqual(window.loc['2016-01-26', cf], - 1, - "Should be FOG16 at beginning of window.") + 0, + "Should be FOF16 at beginning of window.") self.assertEqual(window.loc['2016-01-27', cf], 1, - "Should have remained FOG16.") + "Should have rolled to FOG16.") self.assertEqual(window.loc['2016-02-25', cf], 1, @@ -630,24 +632,24 @@ def record_current_contract(algo, data): self.assertEqual(window.loc['2016-02-26', cf], 2, - "Should be FOH16 on session with roll.") + "Should be FOH16 on roll session.") self.assertEqual(window.loc['2016-02-29', cf], 2, - "Should be FOH16 on session after roll.") + "Should remain FOH16.") self.assertEqual(window.loc['2016-03-17', cf], 2, "Should be FOH16 on session before volume cuts out.") self.assertEqual(window.loc['2016-03-18', cf], - 3, - "Should be FOJ16 on session where the volume of " - "FOH16 cuts out.") + 2, + "Should be FOH16 on session where the volume of " + "FOH16 cuts out, the roll is upcoming.") self.assertEqual(window.loc['2016-03-24', cf], 3, - "Should have remained FOJ16.") + "Should have rolled to FOJ16.") self.assertEqual(window.loc['2016-03-28', cf], 3, diff --git a/zipline/assets/roll_finder.py b/zipline/assets/roll_finder.py index c4633b72..679ce9c5 100644 --- a/zipline/assets/roll_finder.py +++ b/zipline/assets/roll_finder.py @@ -96,23 +96,21 @@ class RollFinder(with_metaclass(ABCMeta, object)): i -= 1 else: i -= 2 - auto_close_date = Timestamp(oc.auto_close_dates[i], tz='UTC') - while auto_close_date > start and i > -1: - session_loc = sessions.searchsorted(auto_close_date) + curr = sessions[-1] + while curr > start and i > -1: + session_loc = sessions.searchsorted(curr) front = oc.contract_sids[i] back = oc.contract_sids[i + 1] - while session_loc > -1: + while session_loc > 0: session = sessions[session_loc] - if back != self._active_contract(oc, front, back, session): + prev = sessions[session_loc - 1] + if back != self._active_contract(oc, front, back, prev): + rolls.insert(0, (oc.contract_sids[i + offset], session)) break session_loc -= 1 - roll_session = sessions[session_loc + 1] - if roll_session > start: - rolls.insert(0, (oc.contract_sids[i + offset], - roll_session)) i -= 1 - auto_close_date = Timestamp(oc.auto_close_dates[i], - tz='UTC') + curr = Timestamp(oc.auto_close_dates[i], + tz='UTC') return rolls @@ -131,8 +129,8 @@ class CalendarRollFinder(RollFinder): if sid == front: break auto_close_date = Timestamp(oc.auto_close_dates[i], tz='UTC') - before_auto_close = dt < auto_close_date - return front if before_auto_close else back + auto_closed = dt >= auto_close_date + return back if auto_closed else front class VolumeRollFinder(RollFinder): @@ -149,7 +147,15 @@ class VolumeRollFinder(RollFinder): self.session_reader = session_reader def _active_contract(self, oc, front, back, dt): - # FIXME: Possible vector for look ahead bias. - front_vol = self.session_reader.get_value(front, dt, 'volume') - back_vol = self.session_reader.get_value(back, dt, 'volume') - return back if back_vol > front_vol else front + prev = dt - self.trading_calendar.day + front_vol = self.session_reader.get_value(front, prev, 'volume') + back_vol = self.session_reader.get_value(back, prev, 'volume') + if back_vol > front_vol: + return back + else: + for i, sid in enumerate(oc.contract_sids): + if sid == front: + break + auto_close_date = Timestamp(oc.auto_close_dates[i], tz='UTC') + auto_closed = dt >= auto_close_date + return back if auto_closed else front