diff --git a/tests/pipeline/test_technical.py b/tests/pipeline/test_technical.py index 313337e4..110d01ee 100644 --- a/tests/pipeline/test_technical.py +++ b/tests/pipeline/test_technical.py @@ -1,3 +1,6 @@ +from __future__ import division + +from nose_parameterized import parameterized import numpy as np import pandas as pd import talib @@ -7,7 +10,7 @@ from zipline.pipeline import TermGraph from zipline.pipeline.data import USEquityPricing from zipline.pipeline.engine import SimplePipelineEngine from zipline.pipeline.term import AssetExists -from zipline.pipeline.factors import BollingerBands +from zipline.pipeline.factors import BollingerBands, Aroon from zipline.testing import ExplodingObject, parameter_space from zipline.testing.fixtures import WithAssetFinder, ZiplineTestCase from zipline.testing.predicates import assert_equal @@ -138,3 +141,36 @@ class BollingerBandsTestCase(WithTechnicalFactor, ZiplineTestCase): self.assertIs(lower, bbands.lower) self.assertIs(middle, bbands.middle) self.assertIs(upper, bbands.upper) + + +class AroonTestCase(ZiplineTestCase): + window_length = 10 + nassets = 5 + dtype = [('down', 'f8'), ('up', 'f8')] + + @parameterized.expand([ + (np.arange(window_length), + np.arange(window_length) + 1, + np.recarray(shape=(nassets,), dtype=dtype, + buf=np.array([0, 100] * nassets, dtype='f8'))), + (np.arange(window_length, 0, -1), + np.arange(window_length, 0, -1) - 1, + np.recarray(shape=(nassets,), dtype=dtype, + buf=np.array([100, 0] * nassets, dtype='f8'))), + (np.array([10, 10, 10, 1, 10, 10, 10, 10, 10, 10]), + np.array([1, 1, 1, 1, 1, 10, 1, 1, 1, 1]), + np.recarray(shape=(nassets,), dtype=dtype, + buf=np.array([100 * 3 / 9, 100 * 5 / 9] * nassets, + dtype='f8'))), + ]) + def test_aroon_basic(self, lows, highs, expected_out): + aroon = Aroon(window_length=self.window_length) + today = pd.Timestamp('2014', tz='utc') + assets = pd.Index(np.arange(self.nassets, dtype=np.int64)) + shape = (self.nassets,) + out = np.recarray(shape=shape, dtype=self.dtype, + buf=np.empty(shape=shape, dtype=self.dtype)) + + aroon.compute(today, assets, out, lows, highs) + + assert_equal(out, expected_out) diff --git a/zipline/pipeline/factors/__init__.py b/zipline/pipeline/factors/__init__.py index 2e6dbdd3..53eecf03 100644 --- a/zipline/pipeline/factors/__init__.py +++ b/zipline/pipeline/factors/__init__.py @@ -14,6 +14,7 @@ from .events import ( BusinessDaysUntilNextExDate, ) from .technical import ( + Aroon, AverageDollarVolume, BollingerBands, EWMA, @@ -32,6 +33,7 @@ from .technical import ( ) __all__ = [ + 'Aroon', 'AverageDollarVolume', 'BollingerBands', 'BusinessDaysSince13DFilingsDate', diff --git a/zipline/pipeline/factors/technical.py b/zipline/pipeline/factors/technical.py index 057e6471..8eb9b85b 100644 --- a/zipline/pipeline/factors/technical.py +++ b/zipline/pipeline/factors/technical.py @@ -2,6 +2,8 @@ Technical Analysis Factors -------------------------- """ +from __future__ import division + from numbers import Number from numpy import ( abs, @@ -31,6 +33,7 @@ from zipline.utils.numpy_utils import ignore_nanwarnings from zipline.utils.input_validation import expect_types from zipline.utils.math_utils import ( nanargmax, + nanargmin, nanmax, nanmean, nanstd, @@ -739,3 +742,43 @@ class BollingerBands(CustomFactor): out.middle = middle = nanmean(close, axis=0) out.upper = middle + difference out.lower = middle - difference + + +class Aroon(CustomFactor): + """ + Aroon technical indicator. + https://www.fidelity.com/learning-center/trading-investing/technical + -analysis/technical-indicator-guide/aroon-indicator + + **Defaults Inputs:** USEquityPricing.low, USEquityPricing.high + + Parameters + ---------- + window_length : int > 0 + Length of the lookback window over which to compute the Aroon + indicator. + """ + + inputs = (USEquityPricing.low, USEquityPricing.high) + outputs = ('down', 'up') + + def compute(self, today, assets, out, lows, highs): + wl = self.window_length + high_date_index = nanargmax(highs, axis=0) + low_date_index = nanargmin(lows, axis=0) + evaluate( + '(100 * high_date_index) / (wl - 1)', + local_dict={ + 'high_date_index': high_date_index, + 'wl': wl, + }, + out=out.up, + ) + evaluate( + '(100 * low_date_index) / (wl - 1)', + local_dict={ + 'low_date_index': low_date_index, + 'wl': wl, + }, + out=out.down, + )