From d463a9855b1996c56cbf5207799c6657e8bbb0b9 Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Thu, 1 Sep 2016 11:14:42 -0400 Subject: [PATCH] TST/BUG: Cover all reindex session public methods. Increase coverage on `ReindexSessionBarReader` so that all methods which are considered part of the interface are covered by `test_resample`. Fix bug in `get_value`, exposed by increased coverage, where the `NoDataOnDate` exception was bubbling from the bcolz reader all the way up when a session which was a holidy on the underlying reader was passed to the reindex reader. (The reindex reader should return nan/0 in that case.) Also, move location of data index exceptions so that they are agnostic to bcolz/us_equity_pricing; since the exception is now used by the resample module to fix aforementioned bug. --- .coveragerc | 2 + tests/data/test_resample.py | 98 ++++++++++++++++++++++++++++++- zipline/data/resample.py | 14 ++++- zipline/data/session_bars.py | 15 +++++ zipline/data/us_equity_pricing.py | 22 ++----- 5 files changed, 131 insertions(+), 20 deletions(-) diff --git a/.coveragerc b/.coveragerc index 812fc3b139..4a7894f0aa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,3 +2,5 @@ omit = */python?.?/* */site-packages/nose/* +exclude_lines = + raise NotImplementedError diff --git a/tests/data/test_resample.py b/tests/data/test_resample.py index 9f8a839c1a..a5a09ef01f 100644 --- a/tests/data/test_resample.py +++ b/tests/data/test_resample.py @@ -616,8 +616,15 @@ class TestReindexSessionBars(WithBcolzEquityDailyBarReader, # Dates are chosen to span Thanksgiving, which is not a Holiday on # us_futures. - START_DATE = pd.Timestamp('2015-11-01', tz='UTC') + START_DATE = pd.Timestamp('2015-11-02', tz='UTC') END_DATE = pd.Timestamp('2015-11-30', tz='UTC') + # November 2015 + # Su Mo Tu We Th Fr Sa + # 1 2 3 4 5 6 7 + # 8 9 10 11 12 13 14 + # 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 def init_instance_fixtures(self): super(TestReindexSessionBars, self).init_instance_fixtures() @@ -651,6 +658,18 @@ def test_load_raw_arrays(self): "The reindexed result after dropping nans should have 20 days, " "because Thanksgiving is a NYSE holiday.") + tday = pd.Timestamp('2015-11-26', tz='UTC') + + # Thanksgiving, 2015-11-26. + # Is a holiday in NYSE, but not in us_futures. + tday_loc = outer_sessions.get_loc(tday) + + assert_almost_equal( + nan, + opens[1][tday_loc], + err_msg="2015-11-26 should be `nan`, since Thanksgiving is a " + "holiday in the reader's calendar.") + # Thanksgiving, 2015-11-26. # Is a holiday in NYSE, but not in us_futures. tday_loc = outer_sessions.get_loc(pd.Timestamp('2015-11-26', tz='UTC')) @@ -661,6 +680,65 @@ def test_load_raw_arrays(self): err_msg="2015-11-26 should be `nan`, since Thanksgiving is a " "holiday in the reader's calendar.") + def test_load_raw_arrays_holiday_start(self): + tday = pd.Timestamp('2015-11-26', tz='UTC') + outer_sessions = self.trading_calendar.sessions_in_range( + tday, self.END_DATE) + + result = self.reader.load_raw_arrays( + OHLCV, tday, self.END_DATE, [1, 2]) + + opens = DataFrame(data=result[0], index=outer_sessions, + columns=[1, 2]) + opens_with_price = opens.dropna() + + self.assertEqual( + 3, + len(opens), + "The reindexed result should have 3 days, which is the number of " + "business days in from Thanksgiving to end of 2015-11.") + self.assertEqual( + 2, + len(opens_with_price), + "The reindexed result after dropping nans should have 2 days, " + "because Thanksgiving is a NYSE holiday.") + + def test_load_raw_arrays_holiday_end(self): + tday = pd.Timestamp('2015-11-26', tz='UTC') + outer_sessions = self.trading_calendar.sessions_in_range( + self.START_DATE, tday) + + result = self.reader.load_raw_arrays( + OHLCV, self.START_DATE, tday, [1, 2]) + + opens = DataFrame(data=result[0], index=outer_sessions, + columns=[1, 2]) + opens_with_price = opens.dropna() + + self.assertEqual( + 19, + len(opens), + "The reindexed result should have 19 days, which is the number of " + "business days in from start of 2015-11 up to Thanksgiving.") + self.assertEqual( + 18, + len(opens_with_price), + "The reindexed result after dropping nans should have 18 days, " + "because Thanksgiving is a NYSE holiday.") + + def test_get_value(self): + assert_almost_equal(self.reader.get_value(1, self.START_DATE, 'open'), + 10.0, + err_msg="The open of the fixture data on the " + "first session should be 10.") + tday = pd.Timestamp('2015-11-26', tz='UTC') + assert_almost_equal(self.reader.get_value(1, tday, 'close'), nan, + err_msg="Thanksgiving is a NYSE holiday, but " + "futures trading is open. Result should be nan.") + assert_almost_equal(self.reader.get_value(1, tday, 'volume'), 0, + err_msg="Thanksgiving is a NYSE holiday, but " + "futures trading is open. Result should be 0.") + def test_last_availabe_dt(self): self.assertEqual(self.reader.last_available_dt, self.END_DATE) @@ -669,3 +747,21 @@ def test_get_last_traded_dt(self): self.assertEqual(self.reader.get_last_traded_dt(asset, self.END_DATE), self.END_DATE) + + def test_sessions(self): + sessions = self.reader.sessions + self.assertEqual(21, len(sessions), + "There should be 21 sessions in 2015-11.") + self.assertEqual(pd.Timestamp('2015-11-02', tz='UTC'), + sessions[0]) + self.assertEqual(pd.Timestamp('2015-11-30', tz='UTC'), + sessions[-1]) + + def test_first_trading_day(self): + self.assertEqual(self.reader.first_trading_day, self.START_DATE) + + def test_trading_calendar(self): + self.assertEqual('us_futures', + self.reader.trading_calendar.name, + "The calendar for the reindex reader should be the " + "specified futures calendar.") diff --git a/zipline/data/resample.py b/zipline/data/resample.py index 2fd3b7f807..671ccdae02 100644 --- a/zipline/data/resample.py +++ b/zipline/data/resample.py @@ -15,11 +15,13 @@ from abc import ABCMeta, abstractmethod import numpy as np +from numpy import nan import pandas as pd from pandas import DataFrame from six import with_metaclass from zipline.data.minute_bars import MinuteBarReader +from zipline.data.us_equity_pricing import NoDataOnDate from zipline.data.session_bars import SessionBarReader from zipline.utils.memoize import lazyval @@ -584,15 +586,21 @@ def first_trading_day(self): return self._reader.first_trading_day def get_value(self, sid, dt, field): - return self._reader.get_value(sid, dt, field) + try: + return self._reader.get_value(sid, dt, field) + except NoDataOnDate: + if field == 'volume': + return 0 + else: + return nan @abstractmethod def _outer_dts(self, start_dt, end_dt): - pass + raise NotImplementedError @abstractmethod def _inner_dts(self, start_dt, end_dt): - pass + raise NotImplementedError @property def trading_calendar(self): diff --git a/zipline/data/session_bars.py b/zipline/data/session_bars.py index 82d9c5339e..fb0585bfd2 100644 --- a/zipline/data/session_bars.py +++ b/zipline/data/session_bars.py @@ -15,6 +15,21 @@ from six import with_metaclass +class NoDataOnDate(Exception): + """ + Raised when a spot price can be found for the sid and date. + """ + pass + + +class NoDataBeforeDate(Exception): + pass + + +class NoDataAfterDate(Exception): + pass + + class SessionBarReader(with_metaclass(ABCMeta)): """ Reader for OHCLV pricing data at a session frequency. diff --git a/zipline/data/us_equity_pricing.py b/zipline/data/us_equity_pricing.py index 9b8dcfe467..dc76dc3d64 100644 --- a/zipline/data/us_equity_pricing.py +++ b/zipline/data/us_equity_pricing.py @@ -49,7 +49,12 @@ string_types, ) -from zipline.data.session_bars import SessionBarReader +from zipline.data.session_bars import ( + SessionBarReader, + NoDataAfterDate, + NoDataBeforeDate, + NoDataOnDate, +) from zipline.utils.calendars import get_calendar from zipline.utils.functional import apply from zipline.utils.preprocess import call @@ -99,21 +104,6 @@ UINT32_MAX = iinfo(uint32).max -class NoDataOnDate(Exception): - """ - Raised when a spot price can be found for the sid and date. - """ - pass - - -class NoDataBeforeDate(Exception): - pass - - -class NoDataAfterDate(Exception): - pass - - def check_uint32_safe(value, colname): if value >= UINT32_MAX: raise ValueError(