diff --git a/tests/modelling/test_engine.py b/tests/modelling/test_engine.py index 710a0de4..7804e55b 100644 --- a/tests/modelling/test_engine.py +++ b/tests/modelling/test_engine.py @@ -40,7 +40,7 @@ from zipline.data.ffc.loaders.us_equity_pricing import ( ) from zipline.finance.trading import TradingEnvironment from zipline.modelling.engine import SimpleFFCEngine -from zipline.modelling.factor import TestingFactor +from zipline.modelling.factor import CustomFactor from zipline.modelling.factor.technical import ( MaxDrawdown, SimpleMovingAverage, @@ -54,12 +54,12 @@ from zipline.utils.test_utils import ( ) -class RollingSumDifference(TestingFactor): +class RollingSumDifference(CustomFactor): window_length = 3 inputs = [USEquityPricing.open, USEquityPricing.close] - def from_windows(self, open, close): - return (open - close).sum(axis=0) + def compute(self, today, assets, out, open, close): + out[:] = (open - close).sum(axis=0) def assert_product(case, index, *levels): diff --git a/tests/modelling/test_factor.py b/tests/modelling/test_factor.py index 8b0f14ad..a35ca584 100644 --- a/tests/modelling/test_factor.py +++ b/tests/modelling/test_factor.py @@ -7,13 +7,13 @@ from numpy import ( ) from zipline.errors import UnknownRankMethod -from zipline.modelling.factor import TestingFactor +from zipline.modelling.factor import Factor from zipline.utils.test_utils import check_arrays from .base import BaseFFCTestCase -class F(TestingFactor): +class F(Factor): inputs = () window_length = 0 diff --git a/tests/modelling/test_filter.py b/tests/modelling/test_filter.py index 00085e74..74a1b088 100644 --- a/tests/modelling/test_filter.py +++ b/tests/modelling/test_filter.py @@ -17,7 +17,7 @@ from numpy import ( ) from zipline.errors import BadPercentileBounds -from zipline.modelling.factor import TestingFactor +from zipline.modelling.factor import Factor from zipline.utils.test_utils import check_arrays from .base import BaseFFCTestCase @@ -51,12 +51,12 @@ def rowwise_rank(array): return argsort(argsort(array)) -class SomeFactor(TestingFactor): +class SomeFactor(Factor): inputs = () window_length = 0 -class SomeOtherFactor(TestingFactor): +class SomeOtherFactor(Factor): inputs = () window_length = 0 diff --git a/tests/modelling/test_numerical_expression.py b/tests/modelling/test_numerical_expression.py index 04d52159..4c3ba24a 100644 --- a/tests/modelling/test_numerical_expression.py +++ b/tests/modelling/test_numerical_expression.py @@ -28,21 +28,21 @@ from zipline.modelling.expression import ( NumericalExpression, NUMEXPR_MATH_FUNCS, ) -from zipline.modelling.factor import TestingFactor +from zipline.modelling.factor import Factor from zipline.utils.test_utils import check_arrays -class F(TestingFactor): +class F(Factor): inputs = () window_length = 0 -class G(TestingFactor): +class G(Factor): inputs = () window_length = 0 -class H(TestingFactor): +class H(Factor): inputs = () window_length = 0 @@ -63,7 +63,7 @@ class NumericalExpressionTestCase(TestCase): self.mask = DataFrame(True, index=self.dates, columns=self.assets) def check_output(self, expr, expected): - result = expr.compute_from_arrays( + result = expr._compute( [self.fake_raw_data[input_] for input_ in expr.inputs], self.mask, ) diff --git a/zipline/modelling/engine.py b/zipline/modelling/engine.py index 82cbba55..decfeaad 100644 --- a/zipline/modelling/engine.py +++ b/zipline/modelling/engine.py @@ -317,11 +317,7 @@ class SimpleFFCEngine(object): for loaded_term, adj_array in zip_longest(to_load, loaded): workspace[loaded_term] = adj_array else: - if term.windowed: - compute = term.compute_from_windows - else: - compute = term.compute_from_arrays - workspace[term] = compute( + workspace[term] = term._compute( self._inputs_for_term(term, workspace, extra_rows), base_mask_for_term, ) diff --git a/zipline/modelling/expression.py b/zipline/modelling/expression.py index 6468b67e..cdbcf982 100644 --- a/zipline/modelling/expression.py +++ b/zipline/modelling/expression.py @@ -228,7 +228,7 @@ class NumericalExpression(Term): ) return super(NumericalExpression, self)._validate() - def compute_from_arrays(self, arrays, mask): + def _compute(self, arrays, mask): """ Compute our stored expression string with numexpr. """ diff --git a/zipline/modelling/factor/__init__.py b/zipline/modelling/factor/__init__.py index 4d55df50..6eab0719 100644 --- a/zipline/modelling/factor/__init__.py +++ b/zipline/modelling/factor/__init__.py @@ -1,11 +1,23 @@ from .factor import ( Factor, - TestingFactor, CustomFactor, ) +from .latest import Latest +from .technical import ( + MaxDrawdown, + RSI, + SimpleMovingAverage, + VWAP, + WeightedAverageValue, +) __all__ = [ - 'Factor', - 'TestingFactor', 'CustomFactor', + 'Factor', + 'Latest', + 'MaxDrawdown', + 'RSI', + 'SimpleMovingAverage', + 'VWAP', + 'WeightedAverageValue', ] diff --git a/zipline/modelling/factor/factor.py b/zipline/modelling/factor/factor.py index 105925bc..4b5c3b0c 100644 --- a/zipline/modelling/factor/factor.py +++ b/zipline/modelling/factor/factor.py @@ -19,7 +19,6 @@ from zipline.modelling.term import ( RequiredWindowLengthMixin, SingleInputMixin, Term, - TestingTermMixin, ) from zipline.modelling.expression import ( BadBinaryOperator, @@ -393,7 +392,7 @@ class Rank(SingleInputMixin, Factor): ) return super(Rank, self)._validate() - def compute_from_arrays(self, arrays, mask): + def _compute(self, arrays, mask): """ For each row in the input, compute a like-shaped array of per-row ranks. @@ -441,11 +440,3 @@ class CustomFactor(RequiredWindowLengthMixin, CustomTermMixin, Factor): if self.dtype != float64: raise UnsupportedDataType(self.dtype) return super(CustomFactor, self)._validate() - - -class TestingFactor(TestingTermMixin, Factor): - """ - Base class for testing engines that asserts all inputs are correctly - shaped. - """ - pass diff --git a/zipline/modelling/filter.py b/zipline/modelling/filter.py index 53a7f43b..04d0fef4 100644 --- a/zipline/modelling/filter.py +++ b/zipline/modelling/filter.py @@ -16,7 +16,6 @@ from zipline.errors import ( from zipline.modelling.term import ( SingleInputMixin, Term, - TestingTermMixin, ) from zipline.modelling.expression import ( BadBinaryOperator, @@ -124,11 +123,11 @@ class NumExprFilter(NumericalExpression, Filter): A Filter computed from a numexpr expression. """ - def compute_from_arrays(self, arrays, mask): + def _compute(self, arrays, mask): """ Compute our result with numexpr, then apply `mask`. """ - return super(NumExprFilter, self).compute_from_arrays( + return super(NumExprFilter, self)._compute( arrays, mask, ) & mask.values @@ -181,7 +180,7 @@ class PercentileFilter(SingleInputMixin, Filter): ) return super(PercentileFilter, self)._validate() - def compute_from_arrays(self, arrays, mask): + def _compute(self, arrays, mask): """ For each row in the input, compute a mask of all values falling between the given percentiles. @@ -261,21 +260,13 @@ class SequencedFilter(Filter): then, ) - def compute_from_arrays(self, arrays, mask): + def _compute(self, arrays, mask): """ Call our second filter on its inputs, masking out any inputs rejected by our first filter. """ first_result, then_inputs = arrays[0], arrays[1:] - return self._then.compute_from_arrays( + return self._then._compute( then_inputs, mask & first_result, ) - - -class TestingFilter(TestingTermMixin, Filter): - """ - Base class for testing engines that asserts all inputs are correctly - shaped. - """ - pass diff --git a/zipline/modelling/term.py b/zipline/modelling/term.py index 17f880b2..e7b18798 100644 --- a/zipline/modelling/term.py +++ b/zipline/modelling/term.py @@ -172,17 +172,12 @@ class Term(object): """ return max(0, self.window_length - 1) - def compute_from_windows(self, windows, mask): + def _compute(self, inputs, mask): """ - Subclasses should implement this for computations requiring moving - windows of continually-adjusting data. - """ - raise NotImplementedError() + Subclasses should implement this to perform actual computation. - def compute_from_arrays(self, arrays, mask): - """ - Subclasses should implement this for computations that can be expressed - directly as array computations. + This is `_compute` rather than just `compute` because `compute` is + reserved for user-supplied functions in CustomFactor. """ raise NotImplementedError() @@ -235,7 +230,7 @@ class CustomTermMixin(object): """ raise NotImplementedError() - def compute_from_windows(self, windows, mask): + def _compute(self, windows, mask): """ Call the user's `compute` function on each window with a pre-built output array. @@ -256,37 +251,3 @@ class CustomTermMixin(object): ) out[~mask.values] = nan return out - - -class TestingTermMixin(object): - """ - Mixin for Term subclasses testing engines that asserts all inputs are - correctly shaped. - - Used by TestingTerm, TestingFilter, TestingClassifier, etc. - """ - def compute_from_windows(self, windows, mask): - assert self.window_length > 0 - dates, assets = mask.index, mask.columns - outbuf = empty(mask.shape, dtype=self.dtype) - for idx, _ in enumerate(dates): - result = self.from_windows(*(next(w) for w in windows)) - assert result.shape == (len(assets),) - outbuf[idx] = result - - for window in windows: - try: - next(window) - except StopIteration: - pass - else: - raise AssertionError("window %s was not exhausted" % window) - return outbuf - - def compute_from_arrays(self, arrays, mask): - assert self.window_length == 0 - outbuf = empty(mask.shape, dtype=self.dtype) - for array in arrays: - assert array.shape == outbuf.shape - outbuf[:] = self.from_arrays(*arrays) - return outbuf diff --git a/zipline/utils/lazyval.py b/zipline/utils/lazyval.py deleted file mode 100644 index 8db2f2e3..00000000 --- a/zipline/utils/lazyval.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -An immutable, lazily loaded value descriptor. -""" - - -from weakref import WeakKeyDictionary - - -class lazyval(object): - """ - Decorator that marks that an attribute should not be computed until - needed, and that the value should be memoized. - - Example - ------- - - >>> from zipline.utils.lazyval import lazyval - >>> class C(object): - ... def __init__(self): - ... self.count = 0 - ... @lazyval - ... def val(self): - ... self.count += 1 - ... return "val" - ... - >>> c = C() - >>> c.count - 0 - >>> c.val, c.count - ('val', 1) - >>> c.val, c.count - ('val', 1) - """ - def __init__(self, get): - self._get = get - self._cache = WeakKeyDictionary() - - def __get__(self, instance, owner): - if instance is None: - return self - - try: - return self._cache[instance] - except KeyError: - self._cache[instance] = val = self._get(instance) - return val