diff --git a/tests/data/test_minute_bars.py b/tests/data/test_minute_bars.py index 514933bc..c8760452 100644 --- a/tests/data/test_minute_bars.py +++ b/tests/data/test_minute_bars.py @@ -46,6 +46,7 @@ from zipline.data.minute_bars import ( ) from zipline.testing.fixtures import ( + WithAssetFinder, WithInstanceTmpDir, WithTradingCalendars, ZiplineTestCase, @@ -59,9 +60,12 @@ TEST_CALENDAR_STOP = Timestamp('2015-12-31', tz='UTC') class BcolzMinuteBarTestCase(WithTradingCalendars, + WithAssetFinder, WithInstanceTmpDir, ZiplineTestCase): + ASSET_FINDER_EQUITY_SIDS = 1, 2 + @classmethod def init_class_fixtures(cls): super(BcolzMinuteBarTestCase, cls).init_class_fixtures() @@ -1040,3 +1044,64 @@ class BcolzMinuteBarTestCase(WithTradingCalendars, _, last_close = cal.open_and_close_for_session( self.test_calendar_start) self.assertEqual(self.reader.last_available_dt, last_close) + + def test_early_market_close(self): + # Date to test is 2015-11-30 9:31 + # Early close is 2015-11-27 18:00 + friday_after_tday = Timestamp('2015-11-27', tz='UTC') + friday_after_tday_close = self.market_closes[friday_after_tday] + + before_early_close = friday_after_tday_close - timedelta(minutes=8) + after_early_close = friday_after_tday_close + timedelta(minutes=8) + + monday_after_tday = Timestamp('2015-11-30', tz='UTC') + minute = self.market_opens[monday_after_tday] + + # Test condition where there is data written after the market + # close (ideally, this should not occur in datasets, but guards + # against consumers of the minute bar writer, which do not filter + # out after close minutes. + minutes = [ + before_early_close, + after_early_close, + minute, + ] + sid = 1 + data = DataFrame( + data={ + 'open': [10.0, 11.0, nan], + 'high': [20.0, 21.0, nan], + 'low': [30.0, 31.0, nan], + 'close': [40.0, 41.0, nan], + 'volume': [50, 51, 0] + }, + index=[minutes]) + self.writer.write_sid(sid, data) + + open_price = self.reader.get_value(sid, minute, 'open') + + assert_almost_equal(nan, open_price) + + high_price = self.reader.get_value(sid, minute, 'high') + + assert_almost_equal(nan, high_price) + + low_price = self.reader.get_value(sid, minute, 'low') + + assert_almost_equal(nan, low_price) + + close_price = self.reader.get_value(sid, minute, 'close') + + assert_almost_equal(nan, close_price) + + volume = self.reader.get_value(sid, minute, 'volume') + + self.assertEquals(0, volume) + + asset = self.asset_finder.retrieve_asset(sid) + last_traded_dt = self.reader.get_last_traded_dt(asset, minute) + + self.assertEquals(last_traded_dt, before_early_close, + "The last traded dt should be before the early " + "close, even when data is written between the early " + "close and the next open.") diff --git a/zipline/data/_minute_bar_internal.pyx b/zipline/data/_minute_bar_internal.pyx index ea39d72d..61818ae1 100644 --- a/zipline/data/_minute_bar_internal.pyx +++ b/zipline/data/_minute_bar_internal.pyx @@ -124,7 +124,7 @@ def find_last_traded_position_internal( ------- int: The position of the last traded minute, starting from `minute_val` """ - cdef Py_ssize_t minute_pos, current_minute + cdef Py_ssize_t minute_pos, current_minute, q minute_pos = int_min( find_position_of_minute(market_opens, market_closes, end_minute, @@ -137,6 +137,15 @@ def find_last_traded_position_internal( market_opens, minute_pos, minutes_per_day ) + q = cython.cdiv(minute_pos, minutes_per_day) + if current_minute > market_closes[q]: + minute_pos = find_position_of_minute(market_opens, + market_closes, + market_closes[q], + minutes_per_day, + False) + continue + if current_minute < start_minute: return -1