Completed
Push — master ( ebb4fb...323695 )
by
unknown
01:25
created

from_span()   A

Complexity

Conditions 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 3
dl 0
loc 21
rs 9.3143
1
"""
2
Technical Analysis Factors
3
--------------------------
4
"""
5
from bottleneck import (
6
    nanargmax,
7
    nanmax,
8
    nanmean,
9
    nansum,
10
)
11
from numbers import Number
12
from numpy import (
13
    abs,
14
    arange,
15
    average,
16
    clip,
17
    diff,
18
    exp,
19
    fmax,
20
    full,
21
    inf,
22
    isnan,
23
    log,
24
    NINF,
25
    sqrt,
26
    sum as np_sum,
27
)
28
from numexpr import evaluate
29
30
from zipline.pipeline.data import USEquityPricing
31
from zipline.pipeline.mixins import SingleInputMixin
32
from zipline.utils.control_flow import ignore_nanwarnings
33
from zipline.utils.input_validation import expect_types
34
from .factor import CustomFactor
35
36
37
class Returns(CustomFactor):
38
    """
39
    Calculates the percent change in close price over the given window_length.
40
41
    **Default Inputs**: [USEquityPricing.close]
42
    """
43
    inputs = [USEquityPricing.close]
44
45
    def compute(self, today, assets, out, close):
46
        out[:] = (close[-1] - close[0]) / close[0]
47
48
49
class RSI(CustomFactor, SingleInputMixin):
50
    """
51
    Relative Strength Index
52
53
    **Default Inputs**: [USEquityPricing.close]
54
55
    **Default Window Length**: 15
56
    """
57
    window_length = 15
58
    inputs = (USEquityPricing.close,)
59
60
    def compute(self, today, assets, out, closes):
61
        diffs = diff(closes, axis=0)
62
        ups = nanmean(clip(diffs, 0, inf), axis=0)
63
        downs = abs(nanmean(clip(diffs, -inf, 0), axis=0))
64
        return evaluate(
65
            "100 - (100 / (1 + (ups / downs)))",
66
            local_dict={'ups': ups, 'downs': downs},
67
            global_dict={},
68
            out=out,
69
        )
70
71
72
class SimpleMovingAverage(CustomFactor, SingleInputMixin):
73
    """
74
    Average Value of an arbitrary column
75
76
    **Default Inputs**: None
77
78
    **Default Window Length**: None
79
    """
80
    # numpy's nan functions throw warnings when passed an array containing only
81
    # nans, but they still returns the desired value (nan), so we ignore the
82
    # warning.
83
    ctx = ignore_nanwarnings()
84
85
    def compute(self, today, assets, out, data):
86
        out[:] = nanmean(data, axis=0)
87
88
89
class WeightedAverageValue(CustomFactor):
90
    """
91
    Helper for VWAP-like computations.
92
93
    **Default Inputs:** None
94
95
    **Default Window Length:** None
96
    """
97
    def compute(self, today, assets, out, base, weight):
98
        out[:] = nansum(base * weight, axis=0) / nansum(weight, axis=0)
99
100
101
class VWAP(WeightedAverageValue):
102
    """
103
    Volume Weighted Average Price
104
105
    **Default Inputs:** [USEquityPricing.close, USEquityPricing.volume]
106
107
    **Default Window Length:** None
108
    """
109
    inputs = (USEquityPricing.close, USEquityPricing.volume)
110
111
112
class MaxDrawdown(CustomFactor, SingleInputMixin):
113
    """
114
    Max Drawdown
115
116
    **Default Inputs:** None
117
118
    **Default Window Length:** None
119
    """
120
    ctx = ignore_nanwarnings()
121
122
    def compute(self, today, assets, out, data):
123
        drawdowns = fmax.accumulate(data, axis=0) - data
124
        drawdowns[isnan(drawdowns)] = NINF
125
        drawdown_ends = nanargmax(drawdowns, axis=0)
126
127
        # TODO: Accelerate this loop in Cython or Numba.
128
        for i, end in enumerate(drawdown_ends):
129
            peak = nanmax(data[:end + 1, i])
130
            out[i] = (peak - data[end, i]) / data[end, i]
131
132
133
def DollarVolume():
134
    """
135
    Returns a Factor computing the product of most recent close price and
136
    volume.
137
    """
138
    return USEquityPricing.close.latest * USEquityPricing.volume.latest
139
140
141
class _ExponentialWeightedFactor(SingleInputMixin, CustomFactor):
142
    """
143
    Base class for factors implementing exponential-weighted operations.
144
145
    **Default Inputs:** None
146
147
    **Default Window Length:** None
148
149
    Parameters
150
    ----------
151
    inputs : length-1 list or tuple of BoundColumn
152
        The expression over which to compute the average.
153
    window_length : int > 0
154
        Length of the lookback window over which to compute the average.
155
    decay_rate : float, 0 < decay_rate <= 1
156
        Weighting factor by which to discount past observations.
157
158
        When calculating historical averages, rows are multiplied by the
159
        sequence::
160
161
            decay_rate, decay_rate ** 2, decay_rate ** 3, ...
162
163
    Methods
164
    -------
165
    weights
166
    from_span
167
    from_halflife
168
    from_center_of_mass
169
    """
170
    params = ('decay_rate',)
171
172
    @staticmethod
173
    def weights(length, decay_rate):
174
        """
175
        Return weighting vector for an exponential moving statistic on `length`
176
        rows with a decay rate of `decay_rate`.
177
        """
178
        return full(length, decay_rate) ** arange(length + 1, 1, -1)
179
180
    @classmethod
181
    @expect_types(span=Number)
182
    def from_span(cls, inputs, window_length, span):
183
        """
184
        Convenience constructor for passing `decay_rate` in terms of `span`.
185
186
        Forwards `decay_rate` as `1 - (2.0 / (1 + span))`.  This provides the
187
        behavior equivalent to passing `span` to pandas.ewma.
188
        """
189
        if span <= 1:
190
            raise ValueError(
191
                "`span` must be a positive number. %s was passed." % span
192
            )
193
194
        decay_rate = (1.0 - (2.0 / (1.0 + span)))
195
        assert 0.0 < decay_rate <= 1.0
196
197
        return cls(
198
            inputs=inputs,
199
            window_length=window_length,
200
            decay_rate=decay_rate,
201
        )
202
203
    @classmethod
204
    @expect_types(halflife=Number)
205
    def from_halflife(cls, inputs, window_length, halflife):
206
        """
207
        Convenience constructor for passing `decay_rate` in terms of half life.
208
209
        Forwards `decay_rate` as `exp(log(.5) / halflife)`.  This provides
210
        the behavior equivalent to passing `halflife` to pandas.ewma.
211
        """
212
        if halflife <= 0:
213
            raise ValueError(
214
                "`span` must be a positive number. %s was passed." % halflife
215
            )
216
        decay_rate = exp(log(.5) / halflife)
217
        assert 0.0 < decay_rate <= 1.0
218
219
        return cls(
220
            inputs=inputs,
221
            window_length=window_length,
222
            decay_rate=decay_rate,
223
        )
224
225
    @classmethod
226
    def from_center_of_mass(cls, inputs, window_length, center_of_mass):
227
        """
228
        Convenience constructor for passing `decay_rate` in terms of center of
229
        mass.
230
231
        Forwards `decay_rate` as `1 - (1 / center_of_mass)`.  This provides
232
        behavior equivalent to passing `center_of_mass` to pandas.ewma.
233
        """
234
        return cls(
235
            inputs=inputs,
236
            window_length=window_length,
237
            decay_rate=(1.0 - (1.0 / (1.0 + center_of_mass))),
238
        )
239
240
241
class ExponentialWeightedMovingAverage(_ExponentialWeightedFactor):
242
    """
243
    Exponentially Weighted Moving Average
244
245
    **Default Inputs:** None
246
247
    **Default Window Length:** None
248
249
    Parameters
250
    ----------
251
    inputs : length-1 list/tuple of BoundColumn
252
        The expression over which to compute the average.
253
    window_length : int > 0
254
        Length of the lookback window over which to compute the average.
255
    decay_rate : float, 0 < decay_rate <= 1
256
        Weighting factor by which to discount past observations.
257
258
        When calculating historical averages, rows are multiplied by the
259
        sequence::
260
261
            decay_rate, decay_rate ** 2, decay_rate ** 3, ...
262
263
    See Also
264
    --------
265
    pandas.ewma
266
    """
267
    def compute(self, today, assets, out, data, decay_rate):
268
        out[:] = average(
269
            data,
270
            axis=0,
271
            weights=self.weights(len(data), decay_rate),
272
        )
273
274
275
class ExponentialWeightedStandardDeviation(_ExponentialWeightedFactor):
276
    """
277
    Exponentially Weighted Moving Standard Deviation
278
279
    **Default Inputs:** None
280
281
    **Default Window Length:** None
282
283
    Parameters
284
    ----------
285
    inputs : length-1 list/tuple of BoundColumn
286
        The expression over which to compute the average.
287
    window_length : int > 0
288
        Length of the lookback window over which to compute the average.
289
    decay_rate : float, 0 < decay_rate <= 1
290
        Weighting factor by which to discount past observations.
291
292
        When calculating historical averages, rows are multiplied by the
293
        sequence::
294
295
            decay_rate, decay_rate ** 2, decay_rate ** 3, ...
296
297
    See Also
298
    --------
299
    pandas.ewmstd
300
    """
301
302
    def compute(self, today, assets, out, data, decay_rate):
303
        weights = self.weights(len(data), decay_rate)
304
305
        mean = average(data, axis=0, weights=weights)
306
        variance = average((data - mean) ** 2, axis=0, weights=weights)
307
308
        squared_weight_sum = (np_sum(weights) ** 2)
309
        bias_correction = (
310
            squared_weight_sum / (squared_weight_sum - np_sum(weights ** 2))
311
        )
312
        out[:] = sqrt(variance * bias_correction)
313
314
315
# Convenience aliases.
316
EWMA = ExponentialWeightedMovingAverage
317
EWMSTD = ExponentialWeightedStandardDeviation
318