From 5756f2932da729feb5bed0e70dabba29b57e2a1a Mon Sep 17 00:00:00 2001 From: Samuel Woo Date: Thu, 14 Jul 2016 14:21:53 -0400 Subject: [PATCH] ENH: Adds LinearWeightedMovingAverage factor --- tests/pipeline/test_technical.py | 33 +++++++++++++++++++++++++++ zipline/pipeline/factors/__init__.py | 2 ++ zipline/pipeline/factors/technical.py | 29 +++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/tests/pipeline/test_technical.py b/tests/pipeline/test_technical.py index a34d88e3..f63d9b8c 100644 --- a/tests/pipeline/test_technical.py +++ b/tests/pipeline/test_technical.py @@ -16,6 +16,7 @@ from zipline.pipeline.factors import ( Aroon, FastStochasticOscillator, IchimokuKinkoHyo, + LinearWeightedMovingAverage, RateOfChangePercentage, ) from zipline.testing import ExplodingObject, parameter_space @@ -380,3 +381,35 @@ class TestRateOfChangePercentage(ZiplineTestCase): out = np.zeros(len(assets)) rocp.compute(today, assets, out, data) assert_equal(out, np.full((len(assets),), expected)) + + +class TestLinearWeightedMovingAverage(ZiplineTestCase): + def test_wma1(self): + wma1 = LinearWeightedMovingAverage( + inputs=(USEquityPricing.close,), + window_length=10 + ) + + today = pd.Timestamp('2015') + assets = np.arange(5, dtype=np.int64) + + data = np.ones((10, 5)) + out = np.zeros(data.shape[1]) + + wma1.compute(today, assets, out, data) + assert_equal(out, np.ones(5)) + + def test_wma2(self): + wma2 = LinearWeightedMovingAverage( + inputs=(USEquityPricing.close,), + window_length=10 + ) + + today = pd.Timestamp('2015') + assets = np.arange(5, dtype=np.int64) + + data = np.arange(50, dtype=float).reshape((10, 5)) + out = np.zeros(data.shape[1]) + + wma2.compute(today, assets, out, data) + assert_equal(out, np.array([30., 31., 32., 33., 34.])) diff --git a/zipline/pipeline/factors/__init__.py b/zipline/pipeline/factors/__init__.py index 422cf750..0b505813 100644 --- a/zipline/pipeline/factors/__init__.py +++ b/zipline/pipeline/factors/__init__.py @@ -23,6 +23,7 @@ from .technical import ( ExponentialWeightedMovingStdDev, FastStochasticOscillator, IchimokuKinkoHyo, + LinearWeightedMovingAverage, MaxDrawdown, RateOfChangePercentage, Returns, @@ -47,6 +48,7 @@ __all__ = [ 'FastStochasticOscillator', 'IchimokuKinkoHyo', 'Latest', + 'LinearWeightedMovingAverage', 'MaxDrawdown', 'RateOfChangePercentage', 'RecarrayField', diff --git a/zipline/pipeline/factors/technical.py b/zipline/pipeline/factors/technical.py index 5802fff3..42e1736d 100644 --- a/zipline/pipeline/factors/technical.py +++ b/zipline/pipeline/factors/technical.py @@ -364,6 +364,35 @@ class ExponentialWeightedMovingAverage(_ExponentialWeightedFactor): ) +class LinearWeightedMovingAverage(CustomFactor, SingleInputMixin): + """ + Weighted Average Value of an arbitrary column + + **Default Inputs**: None + + **Default Window Length**: None + """ + # numpy's nan functions throw warnings when passed an array containing only + # nans, but they still returns the desired value (nan), so we ignore the + # warning. + ctx = ignore_nanwarnings() + + def compute(self, today, assets, out, data): + num_days = data.shape[0] + + # Initialize weights array + weights = arange(1, num_days + 1, dtype=float).reshape(num_days, 1) + + # Compute normalizer + normalizer = (num_days * (num_days + 1)) / 2 + + # Weight the data + weighted_data = data * weights + + # Compute weighted averages + out[:] = nansum(weighted_data, axis=0) / normalizer + + class ExponentialWeightedMovingStdDev(_ExponentialWeightedFactor): """ Exponentially Weighted Moving Standard Deviation