mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-11 11:16:15 +08:00
MAINT: Clean up mixin usage.
- Use RestrictedDTypeMixin for dtype validation in
Filter/Factor/Classifier.
- Use new LatestMixin for Latest{Filter,Factor,Classifier} instead of
duplicating logic across all three.
- Always ignore return values in _validate.
- Consistently call super() first in validation mixins.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
from .classifier import Classifier, CustomClassifier, Everything
|
||||
from .latest import Latest
|
||||
from .classifier import Classifier, CustomClassifier, Everything, Latest
|
||||
|
||||
__all__ = [
|
||||
'Classifier',
|
||||
|
||||
@@ -3,26 +3,19 @@ classifier.py
|
||||
"""
|
||||
from numpy import zeros, where
|
||||
|
||||
from zipline.errors import UnsupportedDataType
|
||||
from zipline.pipeline.term import ComputableTerm
|
||||
from zipline.utils.numpy_utils import int64_dtype
|
||||
|
||||
from ..mixins import CustomTermMixin, PositiveWindowLengthMixin
|
||||
from ..mixins import (
|
||||
CustomTermMixin,
|
||||
LatestMixin,
|
||||
PositiveWindowLengthMixin,
|
||||
RestrictedDTypeMixin
|
||||
)
|
||||
|
||||
|
||||
class Classifier(ComputableTerm):
|
||||
|
||||
def _validate(self):
|
||||
# Run superclass validation first so that we handle `dtype not passed`
|
||||
# before this.
|
||||
retval = super(Classifier, self)._validate()
|
||||
# TODO: Support strings here.
|
||||
if self.dtype != int64_dtype:
|
||||
raise UnsupportedDataType(
|
||||
typename=type(self).__name__,
|
||||
dtype=self.dtype
|
||||
)
|
||||
return retval
|
||||
class Classifier(RestrictedDTypeMixin, ComputableTerm):
|
||||
ALLOWED_DTYPES = (int64_dtype,) # Used by RestrictedDTypeMixin
|
||||
|
||||
|
||||
class Everything(Classifier):
|
||||
@@ -52,3 +45,15 @@ class CustomClassifier(PositiveWindowLengthMixin, CustomTermMixin, Classifier):
|
||||
zipline.pipeline.CustomFilter
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Latest(LatestMixin, CustomClassifier):
|
||||
"""
|
||||
A classifier producing the latest value of an input.
|
||||
|
||||
See Also
|
||||
--------
|
||||
zipline.pipeline.data.dataset.BoundColumn.latest
|
||||
zipline.pipeline.factors.factor.Latest
|
||||
zipline.pipeline.filters.filter.Latest
|
||||
"""
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""
|
||||
Classifier that produces the most most recently-known value of a
|
||||
integer-valued column.
|
||||
"""
|
||||
from zipline.utils.numpy_utils import int64_dtype
|
||||
|
||||
from .classifier import CustomClassifier
|
||||
from ..mixins import SingleInputMixin
|
||||
|
||||
|
||||
class Latest(SingleInputMixin, CustomClassifier):
|
||||
"""
|
||||
Filter producing the most recently-known value of `inputs[0]` on each day.
|
||||
"""
|
||||
window_length = 1
|
||||
|
||||
def compute(self, today, assets, out, data):
|
||||
out[:] = data[-1]
|
||||
|
||||
def _validate(self):
|
||||
if self.inputs[0].dtype != int64_dtype:
|
||||
raise TypeError(
|
||||
"{name} expected an input of dtype int64, "
|
||||
"but got {not_bool} instead.".format(
|
||||
name=type(self).__name__,
|
||||
not_bool=self.inputs[0].dtype,
|
||||
)
|
||||
)
|
||||
super(Latest, self)._validate()
|
||||
@@ -223,7 +223,7 @@ class NumericalExpression(ComputableTerm):
|
||||
expected_indices, expr_indices,
|
||||
)
|
||||
)
|
||||
return super(NumericalExpression, self)._validate()
|
||||
super(NumericalExpression, self)._validate()
|
||||
|
||||
def _compute(self, arrays, dates, assets, mask):
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from .factor import (
|
||||
Factor,
|
||||
CustomFactor,
|
||||
Factor,
|
||||
Latest
|
||||
)
|
||||
from .latest import Latest
|
||||
from .events import (
|
||||
BusinessDaysSinceCashBuybackAuth,
|
||||
BusinessDaysUntilNextEarnings,
|
||||
|
||||
@@ -8,16 +8,15 @@ from numbers import Number
|
||||
from numpy import inf, where, nanstd
|
||||
from toolz import curry
|
||||
|
||||
from zipline.errors import (
|
||||
UnknownRankMethod,
|
||||
UnsupportedDataType,
|
||||
)
|
||||
from zipline.errors import UnknownRankMethod
|
||||
from zipline.lib.normalize import naive_grouped_rowwise_apply
|
||||
from zipline.lib.rank import masked_rankdata_2d
|
||||
from zipline.pipeline.classifiers import Classifier, Everything
|
||||
from zipline.pipeline.mixins import (
|
||||
CustomTermMixin,
|
||||
LatestMixin,
|
||||
PositiveWindowLengthMixin,
|
||||
RestrictedDTypeMixin,
|
||||
SingleInputMixin,
|
||||
)
|
||||
from zipline.pipeline.term import (
|
||||
@@ -396,10 +395,12 @@ float64_only = restrict_to_dtype(
|
||||
FACTOR_DTYPES = frozenset([datetime64ns_dtype, float64_dtype, int64_dtype])
|
||||
|
||||
|
||||
class Factor(ComputableTerm):
|
||||
class Factor(RestrictedDTypeMixin, ComputableTerm):
|
||||
"""
|
||||
Pipeline API expression producing numerically-valued outputs.
|
||||
"""
|
||||
ALLOWED_DTYPES = FACTOR_DTYPES # Used by RestrictedDTypeMixin
|
||||
|
||||
# Dynamically add functions for creating NumExprFactor/NumExprFilter
|
||||
# instances.
|
||||
clsdict = locals()
|
||||
@@ -436,17 +437,6 @@ class Factor(ComputableTerm):
|
||||
|
||||
eq = binary_operator('==')
|
||||
|
||||
def _validate(self):
|
||||
# Do superclass validation first so that `NotSpecified` dtypes get
|
||||
# handled.
|
||||
retval = super(Factor, self)._validate()
|
||||
if self.dtype not in FACTOR_DTYPES:
|
||||
raise UnsupportedDataType(
|
||||
typename=type(self).__name__,
|
||||
dtype=self.dtype
|
||||
)
|
||||
return retval
|
||||
|
||||
@expect_types(
|
||||
mask=(Filter, NotSpecifiedType),
|
||||
groupby=(Classifier, NotSpecifiedType),
|
||||
@@ -1097,3 +1087,16 @@ class CustomFactor(PositiveWindowLengthMixin, CustomTermMixin, Factor):
|
||||
median_low15 = MedianValue([USEquityPricing.low], window_length=15)
|
||||
'''
|
||||
dtype = float64_dtype
|
||||
|
||||
|
||||
class Latest(LatestMixin, CustomFactor):
|
||||
"""
|
||||
Factor producing the most recently-known value of `inputs[0]` on each day.
|
||||
|
||||
The `.latest` attribute of DataSet columns returns an instance of this
|
||||
Factor.
|
||||
"""
|
||||
window_length = 1
|
||||
|
||||
def compute(self, today, assets, out, data):
|
||||
out[:] = data[-1]
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
"""
|
||||
Factor that produces the most most recently-known value of Column.
|
||||
"""
|
||||
from .factor import CustomFactor
|
||||
from ..mixins import SingleInputMixin
|
||||
|
||||
|
||||
class Latest(SingleInputMixin, CustomFactor):
|
||||
"""
|
||||
Factor producing the most recently-known value of `inputs[0]` on each day.
|
||||
|
||||
The `.latest` attribute of DataSet columns returns an instance of this
|
||||
Factor.
|
||||
"""
|
||||
window_length = 1
|
||||
|
||||
def compute(self, today, assets, out, data):
|
||||
out[:] = data[-1]
|
||||
@@ -1,17 +1,17 @@
|
||||
from .filter import (
|
||||
CustomFilter,
|
||||
Filter,
|
||||
NumExprFilter,
|
||||
Latest,
|
||||
NullFilter,
|
||||
NumExprFilter,
|
||||
PercentileFilter,
|
||||
)
|
||||
from .latest import Latest
|
||||
|
||||
__all__ = [
|
||||
'CustomFilter',
|
||||
'Filter',
|
||||
'Latest',
|
||||
'NumExprFilter',
|
||||
'NullFilter',
|
||||
'NumExprFilter',
|
||||
'PercentileFilter',
|
||||
]
|
||||
|
||||
@@ -16,7 +16,9 @@ from zipline.errors import (
|
||||
from zipline.lib.rank import ismissing
|
||||
from zipline.pipeline.mixins import (
|
||||
CustomTermMixin,
|
||||
LatestMixin,
|
||||
PositiveWindowLengthMixin,
|
||||
RestrictedDTypeMixin,
|
||||
SingleInputMixin,
|
||||
)
|
||||
from zipline.pipeline.term import ComputableTerm, Term
|
||||
@@ -26,7 +28,6 @@ from zipline.pipeline.expression import (
|
||||
method_name_for_op,
|
||||
NumericalExpression,
|
||||
)
|
||||
from zipline.utils.control_flow import nullctx
|
||||
from zipline.utils.numpy_utils import bool_dtype
|
||||
|
||||
|
||||
@@ -114,10 +115,11 @@ def unary_operator(op):
|
||||
return unary_operator
|
||||
|
||||
|
||||
class Filter(ComputableTerm):
|
||||
class Filter(RestrictedDTypeMixin, ComputableTerm):
|
||||
"""
|
||||
Pipeline API expression producing boolean-valued outputs.
|
||||
"""
|
||||
ALLOWED_DTYPES = (bool_dtype,) # Used by RestrictedDTypeMixin
|
||||
dtype = bool_dtype
|
||||
|
||||
clsdict = locals()
|
||||
@@ -324,4 +326,10 @@ class CustomFilter(PositiveWindowLengthMixin, CustomTermMixin, Filter):
|
||||
--------
|
||||
zipline.pipeline.factors.factor.CustomFactor
|
||||
"""
|
||||
ctx = nullctx()
|
||||
|
||||
|
||||
class Latest(LatestMixin, CustomFilter):
|
||||
"""
|
||||
Filter producing the most recently-known value of `inputs[0]` on each day.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""
|
||||
Filter that produces the most most recently-known value of a boolean-valued
|
||||
Column.
|
||||
"""
|
||||
from zipline.utils.numpy_utils import bool_dtype
|
||||
|
||||
from .filter import CustomFilter
|
||||
from ..mixins import SingleInputMixin
|
||||
|
||||
|
||||
class Latest(SingleInputMixin, CustomFilter):
|
||||
"""
|
||||
Filter producing the most recently-known value of `inputs[0]` on each day.
|
||||
"""
|
||||
window_length = 1
|
||||
|
||||
def compute(self, today, assets, out, data):
|
||||
out[:] = data[-1]
|
||||
|
||||
def _validate(self):
|
||||
if self.inputs[0].dtype != bool_dtype:
|
||||
raise TypeError(
|
||||
"{name} expected an input of dtype bool, "
|
||||
"but got {not_bool} instead.".format(
|
||||
name=type(self).__name__,
|
||||
not_bool=self.inputs[0].dtype,
|
||||
)
|
||||
)
|
||||
super(Latest, self)._validate()
|
||||
@@ -4,7 +4,7 @@ Mixins classes for use with Filters and Factors.
|
||||
from numpy import full_like
|
||||
|
||||
from zipline.utils.control_flow import nullctx
|
||||
from zipline.errors import WindowLengthNotPositive
|
||||
from zipline.errors import WindowLengthNotPositive, UnsupportedDataType
|
||||
|
||||
from .term import NotSpecified
|
||||
|
||||
@@ -14,9 +14,9 @@ class PositiveWindowLengthMixin(object):
|
||||
Validation mixin enforcing that a Term gets a positive WindowLength
|
||||
"""
|
||||
def _validate(self):
|
||||
super(PositiveWindowLengthMixin, self)._validate()
|
||||
if not self.windowed:
|
||||
raise WindowLengthNotPositive(window_length=self.window_length)
|
||||
return super(PositiveWindowLengthMixin, self)._validate()
|
||||
|
||||
|
||||
class SingleInputMixin(object):
|
||||
@@ -24,6 +24,7 @@ class SingleInputMixin(object):
|
||||
Validation mixin enforcing that a Term gets a length-1 inputs list.
|
||||
"""
|
||||
def _validate(self):
|
||||
super(SingleInputMixin, self)._validate()
|
||||
num_inputs = len(self.inputs)
|
||||
if num_inputs != 1:
|
||||
raise ValueError(
|
||||
@@ -33,7 +34,26 @@ class SingleInputMixin(object):
|
||||
num_inputs=num_inputs
|
||||
)
|
||||
)
|
||||
return super(SingleInputMixin, self)._validate()
|
||||
|
||||
|
||||
class RestrictedDTypeMixin(object):
|
||||
"""
|
||||
Validation mixin enforcing that a term has a specific dtype.
|
||||
"""
|
||||
ALLOWED_DTYPES = NotSpecified
|
||||
|
||||
def _validate(self):
|
||||
super(RestrictedDTypeMixin, self)._validate()
|
||||
assert self.ALLOWED_DTYPES is not NotSpecified, (
|
||||
"ALLOWED_DTYPES not supplied on subclass "
|
||||
"of RestrictedDTypeMixin: %s." % type(self).__name__
|
||||
)
|
||||
|
||||
if self.dtype not in self.ALLOWED_DTYPES:
|
||||
raise UnsupportedDataType(
|
||||
typename=type(self.__name__),
|
||||
dtype=self.dtype,
|
||||
)
|
||||
|
||||
|
||||
class CustomTermMixin(object):
|
||||
@@ -105,3 +125,25 @@ class CustomTermMixin(object):
|
||||
|
||||
def short_repr(self):
|
||||
return type(self).__name__ + '(%d)' % self.window_length
|
||||
|
||||
|
||||
class LatestMixin(SingleInputMixin):
|
||||
"""
|
||||
Mixin for behavior shared by Custom{Factor,Filter,Classifier}.
|
||||
"""
|
||||
window_length = 1
|
||||
|
||||
def compute(self, today, assets, out, data):
|
||||
out[:] = data[-1]
|
||||
|
||||
def _validate(self):
|
||||
super(LatestMixin, self)._validate()
|
||||
if self.inputs[0].dtype != self.dtype:
|
||||
raise TypeError(
|
||||
"{name} expected an input of dtype {expected}, "
|
||||
"but got {actual} instead.".format(
|
||||
name=type(self).__name__,
|
||||
expected=self.dtype,
|
||||
actual=self.inputs[0].dtype,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -398,14 +398,14 @@ class ComputableTerm(Term):
|
||||
)
|
||||
|
||||
def _validate(self):
|
||||
"""
|
||||
Assert that this term is well-formed. This should be called exactly
|
||||
once, at the end of Term._init().
|
||||
"""
|
||||
super(ComputableTerm, self)._validate()
|
||||
|
||||
if self.inputs is NotSpecified:
|
||||
raise TermInputsNotSpecified(termname=type(self).__name__)
|
||||
|
||||
if self.window_length is NotSpecified:
|
||||
raise WindowLengthNotSpecified(termname=type(self).__name__)
|
||||
|
||||
if self.mask is NotSpecified:
|
||||
# This isn't user error, this is a bug in our code.
|
||||
raise AssertionError("{term} has no mask".format(term=self))
|
||||
@@ -415,8 +415,6 @@ class ComputableTerm(Term):
|
||||
if child.windowed:
|
||||
raise WindowedInputToWindowedTerm(parent=self, child=child)
|
||||
|
||||
return super(ComputableTerm, self)._validate()
|
||||
|
||||
def _compute(self, inputs, dates, assets, mask):
|
||||
"""
|
||||
Subclasses should implement this to perform actual computation.
|
||||
|
||||
Reference in New Issue
Block a user