From 7049d11c1f2983017f95c0f69ff910b866b0f0ff Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Mon, 3 Oct 2016 14:27:42 -0400 Subject: [PATCH 1/5] MAINT: Perspective offset for load adjustments. Add a perspective offset to `AdjustedArrayWindow` and `AdjustedArray`, so that `HistoryLoader` does not need to twiddle with offsets to support viewing the data from the bar after end of the window, (Which is the case when a '1d' history window is retrieved in minute mode, which is explained in the docstring for `HistoryLoader.history`) Presently, this simplifies the logic in `HistoryLoader._get_adjustments_in_range`, and other incoming AdjustmentReader's, (e.g. the roll based adjustment reader for continous futures.) This patch should also make it easier for history and pipeline to converge on a singular `load_adjustments` method. --- tests/pipeline/test_adjusted_array.py | 96 +++++++++++++++++++++++++++ zipline/data/history_loader.py | 23 ++----- zipline/lib/_windowtemplate.pxi | 12 ++-- zipline/lib/adjusted_array.py | 11 ++- 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/tests/pipeline/test_adjusted_array.py b/tests/pipeline/test_adjusted_array.py index 730af952..0327bdd0 100644 --- a/tests/pipeline/test_adjusted_array.py +++ b/tests/pipeline/test_adjusted_array.py @@ -202,6 +202,85 @@ def _gen_multiplicative_adjustment_cases(dtype): ) +def _gen_multiplicative_adjustment_cases_with_perpsective_offset(dtype): + """ + Generate expected moving windows on a buffer with adjustments. + + We proceed by constructing, at each row, the view of the array we expect in + in all windows anchored on that row. + + In general, if we have an adjustment to be applied once we process the row + at index N, should see that adjustment applied to the underlying buffer for + any window containing the row at index N - 1. + + We then build all legal windows over these buffers. + """ + adjustment_type = { + float64_dtype: Float64Multiply, + }[dtype] + + nrows, ncols = 6, 3 + adjustments = {} + buffer_as_of = [None] * 6 + baseline = full((nrows, ncols), 1, dtype=dtype) + + # Note that row indices are inclusive! + adjustments[1] = [ + adjustment_type(0, 0, 0, 0, coerce_to_dtype(dtype, 2)), + ] + buffer_as_of[0] = array([[2, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1]], dtype=dtype) + + # No adjustment at index 2. + buffer_as_of[1] = buffer_as_of[0] + + adjustments[3] = [ + adjustment_type(1, 2, 1, 1, coerce_to_dtype(dtype, 3)), + adjustment_type(0, 1, 0, 0, coerce_to_dtype(dtype, 4)), + ] + buffer_as_of[2] = array([[8, 1, 1], + [4, 3, 1], + [1, 3, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1]], dtype=dtype) + + adjustments[4] = [ + adjustment_type(0, 3, 2, 2, coerce_to_dtype(dtype, 5)) + ] + buffer_as_of[3] = array([[8, 1, 5], + [4, 3, 5], + [1, 3, 5], + [1, 1, 5], + [1, 1, 1], + [1, 1, 1]], dtype=dtype) + + adjustments[5] = [ + adjustment_type(0, 4, 1, 1, coerce_to_dtype(dtype, 6)), + adjustment_type(2, 2, 2, 2, coerce_to_dtype(dtype, 7)), + ] + buffer_as_of[4] = array([[8, 6, 5], + [4, 18, 5], + [1, 18, 35], + [1, 6, 5], + [1, 6, 1], + [1, 1, 1]], dtype=dtype) + + buffer_as_of[5] = buffer_as_of[4] + + return _gen_expectations( + baseline, + default_missing_value_for_dtype(dtype), + adjustments, + buffer_as_of, + nrows, + ) + + def _gen_overwrite_adjustment_cases(dtype): """ Generate test cases for overwrite adjustments. @@ -527,6 +606,23 @@ class AdjustedArrayTestCase(TestCase): for yielded, expected_yield in zip_longest(window_iter, expected): check_arrays(yielded, expected_yield) + @parameterized.expand( + _gen_multiplicative_adjustment_cases_with_perpsective_offset( + float64_dtype)) + def test_multiplicative_adjustments_with_perspective_offset(self, + name, + data, + lookback, + adjustments, + missing_value, + expected): + array = AdjustedArray(data, NOMASK, adjustments, missing_value, 1) + for _ in range(2): # Iterate 2x ensure adjusted_arrays are re-usable. + window_iter = array.traverse(lookback) + for yielded, expected_yield in zip_longest(window_iter, expected): + print yielded + check_arrays(yielded, expected_yield) + @parameterized.expand( chain( _gen_overwrite_adjustment_cases(float64_dtype), diff --git a/zipline/data/history_loader.py b/zipline/data/history_loader.py index b53f2ced..df225ccc 100644 --- a/zipline/data/history_loader.py +++ b/zipline/data/history_loader.py @@ -107,8 +107,7 @@ class HistoryLoader(with_metaclass(ABCMeta)): def _array(self, start, end, assets, field): pass - def _get_adjustments_in_range(self, asset, dts, field, - is_perspective_after): + def _get_adjustments_in_range(self, asset, dts, field): """ Get the Float64Multiply objects to pass to an AdjustedArrayWindow. @@ -154,11 +153,6 @@ class HistoryLoader(with_metaclass(ABCMeta)): if start < dt <= end: end_loc = dts.searchsorted(dt) adj_loc = end_loc - if is_perspective_after: - # Set adjustment pop location so that it applies - # to last value if adjustment occurs immediately after - # the last slot. - adj_loc -= 1 mult = Float64Multiply(0, end_loc - 1, 0, @@ -175,11 +169,6 @@ class HistoryLoader(with_metaclass(ABCMeta)): if start < dt <= end: end_loc = dts.searchsorted(dt) adj_loc = end_loc - if is_perspective_after: - # Set adjustment pop location so that it applies - # to last value if adjustment occurs immediately after - # the last slot. - adj_loc -= 1 mult = Float64Multiply(0, end_loc - 1, 0, @@ -200,11 +189,6 @@ class HistoryLoader(with_metaclass(ABCMeta)): ratio = s[1] end_loc = dts.searchsorted(dt) adj_loc = end_loc - if is_perspective_after: - # Set adjustment pop location so that it applies - # to last value if adjustment occurs immediately after - # the last slot. - adj_loc -= 1 mult = Float64Multiply(0, end_loc - 1, 0, @@ -284,7 +268,7 @@ class HistoryLoader(with_metaclass(ABCMeta)): for i, asset in enumerate(needed_assets): if self._adjustments_reader: adjs = self._get_adjustments_in_range( - asset, prefetch_dts, field, is_perspective_after) + asset, prefetch_dts, field) else: adjs = {} window = window_type( @@ -292,7 +276,8 @@ class HistoryLoader(with_metaclass(ABCMeta)): view_kwargs, adjs, offset, - size + size, + int(is_perspective_after) ) sliding_window = SlidingWindow(window, size, start_ix, offset) asset_windows[asset] = sliding_window diff --git a/zipline/lib/_windowtemplate.pxi b/zipline/lib/_windowtemplate.pxi index fb50736d..0dbd141c 100644 --- a/zipline/lib/_windowtemplate.pxi +++ b/zipline/lib/_windowtemplate.pxi @@ -35,6 +35,7 @@ cdef class AdjustedArrayWindow: readonly dict view_kwargs readonly Py_ssize_t window_length Py_ssize_t anchor, next_anchor, max_anchor, next_adj + Py_ssize_t perspective_offset dict adjustments list adjustment_indices ndarray last_out @@ -44,14 +45,15 @@ cdef class AdjustedArrayWindow: dict view_kwargs not None, dict adjustments not None, Py_ssize_t offset, - Py_ssize_t window_length): - + Py_ssize_t window_length, + Py_ssize_t perspective_offset): self.data = data self.view_kwargs = view_kwargs self.adjustments = adjustments self.adjustment_indices = sorted(adjustments, reverse=True) self.window_length = window_length self.anchor = window_length + offset + self.perspective_offset = perspective_offset self.next_anchor = self.anchor self.max_anchor = data.shape[0] @@ -65,7 +67,7 @@ cdef class AdjustedArrayWindow: if len(self.adjustment_indices) > 0: return self.adjustment_indices.pop() else: - return self.max_anchor + return -1 def __iter__(self): return self @@ -84,7 +86,9 @@ cdef class AdjustedArrayWindow: # Apply any adjustments that occured before our current anchor. # Equivalently, apply any adjustments known **on or before** the date # for which we're calculating a window. - while self.next_adj < anchor: + while (self.next_adj != -1 + and + self.next_adj - self.perspective_offset < anchor): for adjustment in self.adjustments[self.next_adj]: adjustment.mutate(self.data) diff --git a/zipline/lib/adjusted_array.py b/zipline/lib/adjusted_array.py index 44645acd..cc4d6407 100644 --- a/zipline/lib/adjusted_array.py +++ b/zipline/lib/adjusted_array.py @@ -152,16 +152,22 @@ class AdjustedArray(object): missing_value : object A value to use to fill missing data in yielded windows. Should be a value coercible to `data.dtype`. + perspective_offset : int + The number of rows after the current end of the window, from which the + data is being viewed. This value is used so that adjustments that occur + between the end of the window and the vantage point are applied. """ __slots__ = ( '_data', '_view_kwargs', 'adjustments', 'missing_value', + 'perspective_offset', '__weakref__', ) - def __init__(self, data, mask, adjustments, missing_value): + def __init__(self, data, mask, adjustments, missing_value, + perspective_offset=0): self._data, self._view_kwargs = _normalize_array(data, missing_value) self.adjustments = adjustments @@ -177,6 +183,8 @@ class AdjustedArray(object): ) self._data[~mask] = self.missing_value + self.perspective_offset = perspective_offset + @lazyval def data(self): """ @@ -220,6 +228,7 @@ class AdjustedArray(object): self.adjustments, offset, window_length, + self.perspective_offset ) def inspect(self): From 9738c142715b0e38022687db88673737555df9a9 Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Wed, 5 Oct 2016 12:44:33 -0400 Subject: [PATCH 2/5] MAINT: Use perspective_offset in more tests. - Refactor `test_adjusted_array` to test a range of perspective_offsets in all tests. - Make perspective_offset a parameter to `AdjustedArray.traverse` instead of `AdjustedArray`. --- tests/pipeline/test_adjusted_array.py | 219 +++++++++++--------------- zipline/lib/adjusted_array.py | 22 ++- 2 files changed, 102 insertions(+), 139 deletions(-) diff --git a/tests/pipeline/test_adjusted_array.py b/tests/pipeline/test_adjusted_array.py index 0327bdd0..5f4b4def 100644 --- a/tests/pipeline/test_adjusted_array.py +++ b/tests/pipeline/test_adjusted_array.py @@ -1,7 +1,8 @@ """ Tests for chunked adjustments. """ -from itertools import chain +from collections import namedtuple +from itertools import chain, product from textwrap import dedent from unittest import TestCase @@ -95,6 +96,20 @@ bytes_dtype = dtype('S3') unicode_dtype = dtype('U3') +AdjustmentCase = namedtuple( + 'AdjustmentCase', + [ + 'name', + 'baseline', + 'window_length', + 'adjustments', + 'missing_value', + 'perspective_offset', + 'expected_result', + ] +) + + def _gen_unadjusted_cases(name, make_input, make_expected_output, @@ -112,13 +127,14 @@ def _gen_unadjusted_cases(name, windowlen, nrows ) - yield ( - "%s_length_%d" % (name, windowlen), - input_array, - windowlen, - {}, - missing_value, - [ + yield AdjustmentCase( + name="%s_length_%d" % (name, windowlen), + baseline=input_array, + window_length=windowlen, + adjustments={}, + missing_value=missing_value, + perspective_offset=0, + expected_result=[ expected_output_array[offset:offset + windowlen] for offset in range(num_legal_windows) ], @@ -199,85 +215,7 @@ def _gen_multiplicative_adjustment_cases(dtype): adjustments, buffer_as_of, nrows, - ) - - -def _gen_multiplicative_adjustment_cases_with_perpsective_offset(dtype): - """ - Generate expected moving windows on a buffer with adjustments. - - We proceed by constructing, at each row, the view of the array we expect in - in all windows anchored on that row. - - In general, if we have an adjustment to be applied once we process the row - at index N, should see that adjustment applied to the underlying buffer for - any window containing the row at index N - 1. - - We then build all legal windows over these buffers. - """ - adjustment_type = { - float64_dtype: Float64Multiply, - }[dtype] - - nrows, ncols = 6, 3 - adjustments = {} - buffer_as_of = [None] * 6 - baseline = full((nrows, ncols), 1, dtype=dtype) - - # Note that row indices are inclusive! - adjustments[1] = [ - adjustment_type(0, 0, 0, 0, coerce_to_dtype(dtype, 2)), - ] - buffer_as_of[0] = array([[2, 1, 1], - [1, 1, 1], - [1, 1, 1], - [1, 1, 1], - [1, 1, 1], - [1, 1, 1]], dtype=dtype) - - # No adjustment at index 2. - buffer_as_of[1] = buffer_as_of[0] - - adjustments[3] = [ - adjustment_type(1, 2, 1, 1, coerce_to_dtype(dtype, 3)), - adjustment_type(0, 1, 0, 0, coerce_to_dtype(dtype, 4)), - ] - buffer_as_of[2] = array([[8, 1, 1], - [4, 3, 1], - [1, 3, 1], - [1, 1, 1], - [1, 1, 1], - [1, 1, 1]], dtype=dtype) - - adjustments[4] = [ - adjustment_type(0, 3, 2, 2, coerce_to_dtype(dtype, 5)) - ] - buffer_as_of[3] = array([[8, 1, 5], - [4, 3, 5], - [1, 3, 5], - [1, 1, 5], - [1, 1, 1], - [1, 1, 1]], dtype=dtype) - - adjustments[5] = [ - adjustment_type(0, 4, 1, 1, coerce_to_dtype(dtype, 6)), - adjustment_type(2, 2, 2, 2, coerce_to_dtype(dtype, 7)), - ] - buffer_as_of[4] = array([[8, 6, 5], - [4, 18, 5], - [1, 18, 35], - [1, 6, 5], - [1, 6, 1], - [1, 1, 1]], dtype=dtype) - - buffer_as_of[5] = buffer_as_of[4] - - return _gen_expectations( - baseline, - default_missing_value_for_dtype(dtype), - adjustments, - buffer_as_of, - nrows, + perspective_offsets=(0, 1, 2, 1000), ) @@ -380,6 +318,7 @@ def _gen_overwrite_adjustment_cases(dtype): adjustments, buffer_as_of, nrows=6, + perspective_offsets=(0, 1, 2, 1000), ) @@ -476,13 +415,13 @@ def _gen_overwrite_1d_array_adjustment_case(dtype): [2, 4, 5], [2, 5, 2], [2, 2, 2]]) - return _gen_expectations( baseline, missing_value, adjustments, buffer_as_of, nrows=6, + perspective_offsets=(0, 1, 2, 1000), ) @@ -490,30 +429,63 @@ def _gen_expectations(baseline, missing_value, adjustments, buffer_as_of, - nrows): - - for windowlen in valid_window_lengths(nrows): + nrows, + perspective_offsets): + for windowlen, perspective_offset in product(valid_window_lengths(nrows), + perspective_offsets): + # How long an an iterator of length-N windows on this buffer? + # For example, for a window of length 3 on a buffer of length 6, there + # are four valid windows. num_legal_windows = num_windows_of_length_M_on_buffers_of_length_N( windowlen, nrows ) - yield ( - "dtype_%s_length_%d" % (baseline.dtype, windowlen), - baseline, + # Build the sequence of regions in the underlying buffer we expect to + # see. For example, with a window length of 3 on a buffer of length 6, + # we expect to see: + # (buffer[0:3], buffer[1:4], buffer[2:5], buffer[3:6]) + # + slices = [slice(i, i + windowlen) for i in range(num_legal_windows)] + + # The sequence of perspectives we expect to take on the underlying + # data. For example, with a window length of 3 and a perspective offset + # of 1, we expect to see: + # (buffer_as_of[3], buffer_as_of[4], buffer_as_of[5], buffer_as_of[5]) + # + initial_perspective = windowlen + perspective_offset - 1 + perspectives = range( + initial_perspective, + initial_perspective + num_legal_windows + ) + + def as_of(p): + # perspective_offset can push us past the end of the underlying + # buffer/adjustments. When it does, we should always see the latest + # version of the buffer. + if p >= len(buffer_as_of): + return buffer_as_of[-1] + return buffer_as_of[p] + + expected_iterator_results = [ + as_of(perspective)[slice_] + for slice_, perspective in zip(slices, perspectives) + ] + + test_name = "dtype_{}_length_{}_perpective_offset_{}".format( + baseline.dtype, windowlen, - adjustments, - missing_value, - [ - # This is a nasty expression... - # - # Reading from right to left: we want a slice of length - # 'windowlen', starting at 'offset', from the buffer on which - # we've applied all adjustments corresponding to the last row - # of the data, which will be (offset + windowlen - 1). - buffer_as_of[offset + windowlen - 1][offset:offset + windowlen] - for offset in range(num_legal_windows) - ], + perspective_offset, + ) + + yield AdjustmentCase( + name=test_name, + baseline=baseline, + window_length=windowlen, + adjustments=adjustments, + missing_value=missing_value, + perspective_offset=perspective_offset, + expected_result=expected_iterator_results ) @@ -583,6 +555,7 @@ class AdjustedArrayTestCase(TestCase): lookback, adjustments, missing_value, + perspective_offset, expected_output): array = AdjustedArray(data, NOMASK, adjustments, missing_value) @@ -598,31 +571,18 @@ class AdjustedArrayTestCase(TestCase): lookback, adjustments, missing_value, + perspective_offset, expected): array = AdjustedArray(data, NOMASK, adjustments, missing_value) for _ in range(2): # Iterate 2x ensure adjusted_arrays are re-usable. - window_iter = array.traverse(lookback) + window_iter = array.traverse( + lookback, + perspective_offset=perspective_offset, + ) for yielded, expected_yield in zip_longest(window_iter, expected): check_arrays(yielded, expected_yield) - @parameterized.expand( - _gen_multiplicative_adjustment_cases_with_perpsective_offset( - float64_dtype)) - def test_multiplicative_adjustments_with_perspective_offset(self, - name, - data, - lookback, - adjustments, - missing_value, - expected): - array = AdjustedArray(data, NOMASK, adjustments, missing_value, 1) - for _ in range(2): # Iterate 2x ensure adjusted_arrays are re-usable. - window_iter = array.traverse(lookback) - for yielded, expected_yield in zip_longest(window_iter, expected): - print yielded - check_arrays(yielded, expected_yield) - @parameterized.expand( chain( _gen_overwrite_adjustment_cases(float64_dtype), @@ -680,14 +640,19 @@ class AdjustedArrayTestCase(TestCase): ) def test_overwrite_adjustment_cases(self, name, - data, + baseline, lookback, adjustments, missing_value, + perspective_offset, expected): - array = AdjustedArray(data, NOMASK, adjustments, missing_value) + array = AdjustedArray(baseline, NOMASK, adjustments, missing_value) + for _ in range(2): # Iterate 2x ensure adjusted_arrays are re-usable. - window_iter = array.traverse(lookback) + window_iter = array.traverse( + lookback, + perspective_offset=perspective_offset, + ) for yielded, expected_yield in zip_longest(window_iter, expected): check_arrays(yielded, expected_yield) diff --git a/zipline/lib/adjusted_array.py b/zipline/lib/adjusted_array.py index cc4d6407..b88b1cbb 100644 --- a/zipline/lib/adjusted_array.py +++ b/zipline/lib/adjusted_array.py @@ -152,22 +152,16 @@ class AdjustedArray(object): missing_value : object A value to use to fill missing data in yielded windows. Should be a value coercible to `data.dtype`. - perspective_offset : int - The number of rows after the current end of the window, from which the - data is being viewed. This value is used so that adjustments that occur - between the end of the window and the vantage point are applied. """ __slots__ = ( '_data', '_view_kwargs', 'adjustments', 'missing_value', - 'perspective_offset', '__weakref__', ) - def __init__(self, data, mask, adjustments, missing_value, - perspective_offset=0): + def __init__(self, data, mask, adjustments, missing_value): self._data, self._view_kwargs = _normalize_array(data, missing_value) self.adjustments = adjustments @@ -183,8 +177,6 @@ class AdjustedArray(object): ) self._data[~mask] = self.missing_value - self.perspective_offset = perspective_offset - @lazyval def data(self): """ @@ -208,7 +200,10 @@ class AdjustedArray(object): return LabelWindow return CONCRETE_WINDOW_TYPES[self._data.dtype] - def traverse(self, window_length, offset=0): + def traverse(self, + window_length, + offset=0, + perspective_offset=0): """ Produce an iterator rolling windows rows over our data. Each emitted window will have `window_length` rows. @@ -218,7 +213,10 @@ class AdjustedArray(object): window_length : int The number of rows in each emitted window. offset : int, optional - Number of rows to skip before the first window. + Number of rows to skip before the first window. Default is 0. + perspective_offset : int, optional + Number of rows past the end of the current window from which to + "view" the underlying data. """ data = self._data.copy() _check_window_params(data, window_length) @@ -228,7 +226,7 @@ class AdjustedArray(object): self.adjustments, offset, window_length, - self.perspective_offset + perspective_offset, ) def inspect(self): From 0244f034110ebdd24e9e0e4fe1ad50530dcc99cf Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Wed, 5 Oct 2016 13:05:33 -0400 Subject: [PATCH 3/5] DOC: Fix typo in comment. --- tests/pipeline/test_adjusted_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pipeline/test_adjusted_array.py b/tests/pipeline/test_adjusted_array.py index 5f4b4def..cc6d05e9 100644 --- a/tests/pipeline/test_adjusted_array.py +++ b/tests/pipeline/test_adjusted_array.py @@ -434,7 +434,7 @@ def _gen_expectations(baseline, for windowlen, perspective_offset in product(valid_window_lengths(nrows), perspective_offsets): - # How long an an iterator of length-N windows on this buffer? + # How long is an iterator of length-N windows on this buffer? # For example, for a window of length 3 on a buffer of length 6, there # are four valid windows. num_legal_windows = num_windows_of_length_M_on_buffers_of_length_N( From 554bc015395605fafea98a3cad42f9fca86c1e6d Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Wed, 5 Oct 2016 13:15:11 -0400 Subject: [PATCH 4/5] MAINT: Alternate AdjustedArray boundary conditions. Avoids the need for a special sentinel value, and means that we only have to have one branch instead of two in the inner loop. --- zipline/lib/_windowtemplate.pxi | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zipline/lib/_windowtemplate.pxi b/zipline/lib/_windowtemplate.pxi index 0dbd141c..40f900a5 100644 --- a/zipline/lib/_windowtemplate.pxi +++ b/zipline/lib/_windowtemplate.pxi @@ -67,7 +67,7 @@ cdef class AdjustedArrayWindow: if len(self.adjustment_indices) > 0: return self.adjustment_indices.pop() else: - return -1 + return self.max_anchor + self.perspective_offset def __iter__(self): return self @@ -86,9 +86,7 @@ cdef class AdjustedArrayWindow: # Apply any adjustments that occured before our current anchor. # Equivalently, apply any adjustments known **on or before** the date # for which we're calculating a window. - while (self.next_adj != -1 - and - self.next_adj - self.perspective_offset < anchor): + while self.next_adj < anchor + self.perspective_offset: for adjustment in self.adjustments[self.next_adj]: adjustment.mutate(self.data) From a1a99dd9aabdc4726a0a25d5f9536323907f85ac Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Mon, 17 Oct 2016 15:08:11 -0400 Subject: [PATCH 5/5] MAINT: Limit perspective offset. Limit the perspective offset to 1. There is a possibility that if a consumer of the AdjustedArrayWindow does not fetch adjustments between the end of the data window and the vantage points beyond the end of the window. Until that case has a solution, e.g. having the consumer of the AdjustedArrayWindow include the perspective offset when calculating the query for adjustments, limit the offsets to 1. --- tests/pipeline/test_adjusted_array.py | 6 +++--- zipline/lib/_windowtemplate.pxi | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/pipeline/test_adjusted_array.py b/tests/pipeline/test_adjusted_array.py index cc6d05e9..57afb44b 100644 --- a/tests/pipeline/test_adjusted_array.py +++ b/tests/pipeline/test_adjusted_array.py @@ -215,7 +215,7 @@ def _gen_multiplicative_adjustment_cases(dtype): adjustments, buffer_as_of, nrows, - perspective_offsets=(0, 1, 2, 1000), + perspective_offsets=(0, 1), ) @@ -318,7 +318,7 @@ def _gen_overwrite_adjustment_cases(dtype): adjustments, buffer_as_of, nrows=6, - perspective_offsets=(0, 1, 2, 1000), + perspective_offsets=(0, 1), ) @@ -421,7 +421,7 @@ def _gen_overwrite_1d_array_adjustment_case(dtype): adjustments, buffer_as_of, nrows=6, - perspective_offsets=(0, 1, 2, 1000), + perspective_offsets=(0, 1), ) diff --git a/zipline/lib/_windowtemplate.pxi b/zipline/lib/_windowtemplate.pxi index 40f900a5..245df436 100644 --- a/zipline/lib/_windowtemplate.pxi +++ b/zipline/lib/_windowtemplate.pxi @@ -53,6 +53,15 @@ cdef class AdjustedArrayWindow: self.adjustment_indices = sorted(adjustments, reverse=True) self.window_length = window_length self.anchor = window_length + offset + if perspective_offset > 1: + # Limit perspective_offset to 1. + # To support an offset greater than 1, work must be done to + # ensure that adjustments are retrieved for the datetimes between + # the end of the window and the vantage point defined by the + # perspective offset. + raise Exception("perspective_offset should not exceed 1, value " + "is perspective_offset={0}".format( + perspective_offset)) self.perspective_offset = perspective_offset self.next_anchor = self.anchor self.max_anchor = data.shape[0]