class Filter(Term): """ A boolean predicate on a universe of Assets. """ domain = None dtype = bool_ clsdict = locals() clsdict.update( { method_name_for_op(op): binary_operator(op) for op in FILTER_BINOPS } ) def then(self, other): """ Create a new filter by computing `self`, then computing `other` on the data that survived the first filter. Parameters ---------- other : zipline.modelling.filter.Filter The Filter to apply next. Returns ------- filter : zipline.modelling.filter.SequencedFilter A filter which will compute `self` and then `other`. See Also -------- zipline.modelling.filter.SequencedFilter """ return SequencedFilter(self, other)
def binary_operator(op): """ Factory function for making binary operator methods on a Filter subclass. Returns a function "binary_operator" suitable for implementing functions like __and__ or __or__. """ # When combining a Filter with a NumericalExpression, we use this # attrgetter instance to defer to the commuted interpretation of the # NumericalExpression operator. commuted_method_getter = attrgetter(method_name_for_op(op, commute=True)) def binary_operator(self, other): if isinstance(self, NumericalExpression): self_expr, other_expr, new_inputs = self.build_binary_op(op, other) return NumExprFilter("({left}) {op} ({right})".format(left=self_expr, op=op, right=other_expr), new_inputs) elif isinstance(other, NumericalExpression): # NumericalExpression overrides numerical ops to correctly handle # merging of inputs. Look up and call the appropriate # right-binding operator with ourself as the input. return commuted_method_getter(other)(self) elif isinstance(other, Filter): if self is other: return NumExprFilter("x_0 {op} x_0".format(op=op), (self,)) return NumExprFilter("x_0 {op} x_1".format(op=op), (self, other)) elif isinstance(other, int): # Note that this is true for bool as well return NumExprFilter("x_0 {op} ({constant})".format(op=op, constant=int(other)), binds=(self,)) raise BadBinaryOperator(op, self, other) return binary_operator
def binary_operator(op): """ Factory function for making binary operator methods on a Factor subclass. Returns a function, "binary_operator" suitable for implementing functions like __add__. """ # When combining a Factor with a NumericalExpression, we use this # attrgetter instance to defer to the commuted implementation of the # NumericalExpression operator. commuted_method_getter = attrgetter(method_name_for_op(op, commute=True)) def binary_operator(self, other): # This can't be hoisted up a scope because the types returned by # binop_return_type aren't defined when the top-level function is # invoked in the class body of Factor. return_type = binop_return_type(op) if isinstance(self, NumExprFactor): self_expr, other_expr, new_inputs = self.build_binary_op( op, other, ) return return_type( "({left}) {op} ({right})".format( left=self_expr, op=op, right=other_expr, ), new_inputs, ) elif isinstance(other, NumExprFactor): # NumericalExpression overrides ops to correctly handle merging of # inputs. Look up and call the appropriate reflected operator with # ourself as the input. return commuted_method_getter(other)(self) elif isinstance(other, Factor): if self is other: return return_type( "x_0 {op} x_0".format(op=op), (self, ), ) return return_type( "x_0 {op} x_1".format(op=op), (self, other), ) elif isinstance(other, NUMERIC_TYPES): return return_type( "x_0 {op} ({constant})".format(op=op, constant=other), binds=(self, ), ) raise BadBinaryOperator(op, self, other) return binary_operator
def binary_operator(op): """ Factory function for making binary operator methods on a Factor subclass. Returns a function, "binary_operator" suitable for implementing functions like __add__. """ # When combining a Factor with a NumericalExpression, we use this # attrgetter instance to defer to the commuted implementation of the # NumericalExpression operator. commuted_method_getter = attrgetter(method_name_for_op(op, commute=True)) def binary_operator(self, other): # This can't be hoisted up a scope because the types returned by # binop_return_type aren't defined when the top-level function is # invoked in the class body of Factor. return_type = binop_return_type(op) if isinstance(self, NumExprFactor): self_expr, other_expr, new_inputs = self.build_binary_op( op, other, ) return return_type( "({left}) {op} ({right})".format( left=self_expr, op=op, right=other_expr, ), new_inputs, ) elif isinstance(other, NumExprFactor): # NumericalExpression overrides ops to correctly handle merging of # inputs. Look up and call the appropriate reflected operator with # ourself as the input. return commuted_method_getter(other)(self) elif isinstance(other, Factor): if self is other: return return_type( "x_0 {op} x_0".format(op=op), (self,), ) return return_type( "x_0 {op} x_1".format(op=op), (self, other), ) elif isinstance(other, NUMERIC_TYPES): return return_type( "x_0 {op} ({constant})".format(op=op, constant=other), binds=(self,), ) raise BadBinaryOperator(op, self, other) return binary_operator
def binary_operator(op): """ Factory function for making binary operator methods on a Filter subclass. Returns a function "binary_operator" suitable for implementing functions like __and__ or __or__. """ # When combining a Filter with a NumericalExpression, we use this # attrgetter instance to defer to the commuted interpretation of the # NumericalExpression operator. commuted_method_getter = attrgetter(method_name_for_op(op, commute=True)) def binary_operator(self, other): if isinstance(self, NumericalExpression): self_expr, other_expr, new_inputs = self.build_binary_op( op, other, ) return NumExprFilter( "({left}) {op} ({right})".format( left=self_expr, op=op, right=other_expr, ), new_inputs, ) elif isinstance(other, NumericalExpression): # NumericalExpression overrides numerical ops to correctly handle # merging of inputs. Look up and call the appropriate # right-binding operator with ourself as the input. return commuted_method_getter(other)(self) elif isinstance(other, Filter): if self is other: return NumExprFilter( "x_0 {op} x_0".format(op=op), (self, ), ) return NumExprFilter( "x_0 {op} x_1".format(op=op), (self, other), ) elif isinstance(other, int): # Note that this is true for bool as well return NumExprFilter( "x_0 {op} ({constant})".format(op=op, constant=int(other)), binds=(self, ), ) raise BadBinaryOperator(op, self, other) return binary_operator
class Factor(Term): """ A transformation yielding a timeseries of scalar values associated with an Asset. """ # Dynamically add functions for creating NumExprFactor/NumExprFilter # instances. clsdict = locals() clsdict.update({ method_name_for_op(op): binary_operator(op) # Don't override __eq__ because it breaks comparisons on tuples of # Factors. for op in MATH_BINOPS.union(COMPARISONS - {'=='}) }) clsdict.update({ method_name_for_op(op, commute=True): reflected_binary_operator(op) for op in MATH_BINOPS }) clsdict.update({'__neg__': unary_operator(op) for op in UNARY_OPS}) clsdict.update({ funcname: function_application(funcname) for funcname in NUMEXPR_MATH_FUNCS }) __truediv__ = clsdict['__div__'] __rtruediv__ = clsdict['__rdiv__'] eq = binary_operator('==') def rank(self, method='ordinal'): """ Construct a new Factor representing the sorted rank of each column within each row. Returns ------- ranks : zipline.modelling.factor.Rank A new factor that will compute the sorted indices of the data produced by `self`. method : str, {'ordinal', 'min', 'max', 'dense', 'average'} The method used to assign ranks to tied elements. Default is 'ordinal'. See `scipy.stats.rankdata` for a full description of the semantics for each ranking method. The default is 'ordinal'. Notes ----- The default value for `method` is different from the default for `scipy.stats.rankdata`. See that function's documentation for a full description of the valid inputs to `method`. Missing or non-existent data on a given day will cause an asset to be given a rank of NaN for that day. See Also -------- scipy.stats.rankdata : Underlying ranking algorithm. zipline.modelling.factor.Rank : Class implementing core functionality. """ return Rank(self, method=method) def percentile_between(self, min_percentile, max_percentile): """ Construct a new Filter representing entries from the output of this Factor that fall within the percentile range defined by min_percentile and max_percentile. Parameters ---------- min_percentile : float [0.0, 100.0] max_percentile : float [0.0, 100.0] Returns ------- out : zipline.modelling.filter.PercentileFilter A new filter that will compute the specified percentile-range mask. See Also -------- zipline.modelling.filter.PercentileFilter """ return PercentileFilter( self, min_percentile=min_percentile, max_percentile=max_percentile, )