Completed
Push — master ( c4f3f5...2b4ffe )
by
unknown
01:25
created

ation.compute()   A

Complexity

Conditions 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 11
rs 9.4286
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
        Example
190
        -------
191
        .. code-block:: python
192
193
            # Equivalent to:
194
            # my_ewma = EWMA(
195
            #    inputs=[USEquityPricing.close],
196
            #    window_length=30,
197
            #    decay_rate=(1 - (2.0 / (1 + 15.0))),
198
            # )
199
            my_ewma = EWMA.from_span(
200
                inputs=[USEquityPricing.close],
201
                window_length=30,
202
                span=15,
203
            )
204
205
        Note
206
        ----
207
        This classmethod is provided by both
208
        :class:`ExponentialWeightedMovingAverage` and
209
        :class:`ExponentialWeightedMovingStdDev`.
210
        """
211
        if span <= 1:
212
            raise ValueError(
213
                "`span` must be a positive number. %s was passed." % span
214
            )
215
216
        decay_rate = (1.0 - (2.0 / (1.0 + span)))
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
    @expect_types(halflife=Number)
227
    def from_halflife(cls, inputs, window_length, halflife):
228
        """
229
        Convenience constructor for passing ``decay_rate`` in terms of half
230
        life.
231
232
        Forwards ``decay_rate`` as ``exp(log(.5) / halflife)``.  This provides
233
        the behavior equivalent to passing `halflife` to pandas.ewma.
234
235
        Example
236
        -------
237
        .. code-block:: python
238
239
            # Equivalent to:
240
            # my_ewma = EWMA(
241
            #    inputs=[USEquityPricing.close],
242
            #    window_length=30,
243
            #    decay_rate=np.exp(np.log(0.5) / 15),
244
            # )
245
            my_ewma = EWMA.from_halflife(
246
                inputs=[USEquityPricing.close],
247
                window_length=30,
248
                halflife=15,
249
            )
250
251
        Note
252
        ----
253
        This classmethod is provided by both
254
        :class:`ExponentialWeightedMovingAverage` and
255
        :class:`ExponentialWeightedMovingStdDev`.
256
        """
257
        if halflife <= 0:
258
            raise ValueError(
259
                "`span` must be a positive number. %s was passed." % halflife
260
            )
261
        decay_rate = exp(log(.5) / halflife)
262
        assert 0.0 < decay_rate <= 1.0
263
264
        return cls(
265
            inputs=inputs,
266
            window_length=window_length,
267
            decay_rate=decay_rate,
268
        )
269
270
    @classmethod
271
    def from_center_of_mass(cls, inputs, window_length, center_of_mass):
272
        """
273
        Convenience constructor for passing `decay_rate` in terms of center of
274
        mass.
275
276
        Forwards `decay_rate` as `1 - (1 / 1 + center_of_mass)`.  This provides
277
        behavior equivalent to passing `center_of_mass` to pandas.ewma.
278
279
        Example
280
        -------
281
        .. code-block:: python
282
283
            # Equivalent to:
284
            # my_ewma = EWMA(
285
            #    inputs=[USEquityPricing.close],
286
            #    window_length=30,
287
            #    decay_rate=(1 - (1 / 15.0)),
288
            # )
289
            my_ewma = EWMA.from_center_of_mass(
290
                inputs=[USEquityPricing.close],
291
                window_length=30,
292
                center_of_mass=15,
293
            )
294
295
        Note
296
        ----
297
        This classmethod is provided by both
298
        :class:`ExponentialWeightedMovingAverage` and
299
        :class:`ExponentialWeightedMovingStdDev`.
300
        """
301
        return cls(
302
            inputs=inputs,
303
            window_length=window_length,
304
            decay_rate=(1.0 - (1.0 / (1.0 + center_of_mass))),
305
        )
306
307
308
class ExponentialWeightedMovingAverage(_ExponentialWeightedFactor):
309
    """
310
    Exponentially Weighted Moving Average
311
312
    **Default Inputs:** None
313
314
    **Default Window Length:** None
315
316
    Parameters
317
    ----------
318
    inputs : length-1 list/tuple of BoundColumn
319
        The expression over which to compute the average.
320
    window_length : int > 0
321
        Length of the lookback window over which to compute the average.
322
    decay_rate : float, 0 < decay_rate <= 1
323
        Weighting factor by which to discount past observations.
324
325
        When calculating historical averages, rows are multiplied by the
326
        sequence::
327
328
            decay_rate, decay_rate ** 2, decay_rate ** 3, ...
329
330
    Notes
331
    -----
332
    - This class can also be imported under the name ``EWMA``.
333
334
    See Also
335
    --------
336
    :func:`pandas.ewma`
337
    """
338
    def compute(self, today, assets, out, data, decay_rate):
339
        out[:] = average(
340
            data,
341
            axis=0,
342
            weights=self.weights(len(data), decay_rate),
343
        )
344
345
346
class ExponentialWeightedMovingStdDev(_ExponentialWeightedFactor):
347
    """
348
    Exponentially Weighted Moving Standard Deviation
349
350
    **Default Inputs:** None
351
352
    **Default Window Length:** None
353
354
    Parameters
355
    ----------
356
    inputs : length-1 list/tuple of BoundColumn
357
        The expression over which to compute the average.
358
    window_length : int > 0
359
        Length of the lookback window over which to compute the average.
360
    decay_rate : float, 0 < decay_rate <= 1
361
        Weighting factor by which to discount past observations.
362
363
        When calculating historical averages, rows are multiplied by the
364
        sequence::
365
366
            decay_rate, decay_rate ** 2, decay_rate ** 3, ...
367
368
    Notes
369
    -----
370
    - This class can also be imported under the name ``EWMSTD``.
371
372
    See Also
373
    --------
374
    :func:`pandas.ewmstd`
375
    """
376
377
    def compute(self, today, assets, out, data, decay_rate):
378
        weights = self.weights(len(data), decay_rate)
379
380
        mean = average(data, axis=0, weights=weights)
381
        variance = average((data - mean) ** 2, axis=0, weights=weights)
382
383
        squared_weight_sum = (np_sum(weights) ** 2)
384
        bias_correction = (
385
            squared_weight_sum / (squared_weight_sum - np_sum(weights ** 2))
386
        )
387
        out[:] = sqrt(variance * bias_correction)
388
389
390
# Convenience aliases.
391
EWMA = ExponentialWeightedMovingAverage
392
EWMSTD = ExponentialWeightedMovingStdDev
393