From 6a2ee7c417440d59c59bc9430ef4394384cc912e Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Tue, 24 Jun 2014 20:32:07 -0400 Subject: [PATCH] BUG: History no longer fails on length-1 '1m' price-only HistorySpecs The bug occurred because there is a special case in the initial window setup code for handling the case where only a length-1 history is used for a given frequency. Previously, the code was incorrectly calculating the period end using a hard-coded expression for the end of the day (the correct behavior for a length-1 '1d' history), and then using the frequency object to calculate the period start for the window. In the case of length 1 '1m' data, this resulted in an initial window whose start and end was the last minute of the day rather than the first minute of the day. For non-price fields, this error doesn't matter, because the window is only used for rolling digests (which doesn't happen when there's only a length-1 history), and for the forward-filling logic (which only happens on price fields). For a length-1 '1m' price, however, the incorrect window causes us to attempt to forward-fill an empty panel, resulting in an IndexError when we do an iloc[0] on a length-0 axis. --- tests/history_cases.py | 52 ++++++++++++++++++++++++++++ zipline/history/history_container.py | 12 +++---- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/tests/history_cases.py b/tests/history_cases.py index 0496ed5d..cd2c42d8 100644 --- a/tests/history_cases.py +++ b/tests/history_cases.py @@ -44,6 +44,9 @@ def mixed_frequency_expected_data(count, frequency): MIXED_FREQUENCY_MINUTES = TradingEnvironment.instance().market_minute_window( to_utc('2013-07-03 9:31AM'), 600, ) +ONE_MINUTE_PRICE_ONLY_SPECS = [ + HistorySpec(1, '1m', 'price', True), +] DAILY_OPEN_CLOSE_SPECS = [ HistorySpec(3, '1d', 'open_price', False), HistorySpec(3, '1d', 'close_price', False), @@ -77,6 +80,55 @@ HISTORY_CONTAINER_TEST_CASES = { # 23 24 25 26 27 28 29 # 30 + 'test one minute price only': { + # A list of HistorySpec objects. + 'specs': ONE_MINUTE_PRICE_ONLY_SPECS, + # Sids for the test. + 'sids': [1], + # Start date for test. + 'dt': to_utc('2013-06-21 9:31AM'), + # Sequency of updates to the container + 'updates': [ + BarData( + { + 1: { + 'price': 5, + 'dt': to_utc('2013-06-21 9:31AM'), + }, + }, + ), + BarData( + { + 1: { + 'price': 6, + 'dt': to_utc('2013-06-21 9:32AM'), + }, + }, + ), + ], + # Expected results + 'expected': { + ONE_MINUTE_PRICE_ONLY_SPECS[0].key_str: [ + pd.DataFrame( + data={ + 1: [5], + }, + index=[ + to_utc('2013-06-21 9:31AM'), + ], + ), + pd.DataFrame( + data={ + 1: [6], + }, + index=[ + to_utc('2013-06-21 9:32AM'), + ], + ), + ], + }, + }, + 'test daily open close': { # A list of HistorySpec objects. 'specs': DAILY_OPEN_CLOSE_SPECS, diff --git a/zipline/history/history_container.py b/zipline/history/history_container.py index 49cc9a49..4c77a343 100644 --- a/zipline/history/history_container.py +++ b/zipline/history/history_container.py @@ -23,7 +23,6 @@ from . history import ( index_at_dt, ) -from zipline.finance import trading from zipline.utils.data import RollingPanel @@ -206,13 +205,14 @@ class HistoryContainer(object): # requiring the largest number of bars. largest_spec = specs[-1] if largest_spec.bar_count == 1: + # No need to allocate a digest panel; this frequency will only # ever use data drawn from self.buffer_panel. - env = trading.environment - first_window_closes[freq] = \ - env.get_open_and_close(initial_dt)[1] - first_window_starts[freq] = \ - freq.window_open(first_window_closes[freq]) + first_window_starts[freq] = freq.window_open(initial_dt) + first_window_closes[freq] = freq.window_close( + first_window_starts[freq] + ) + continue initial_dates = index_at_dt(largest_spec, initial_dt)