diff --git a/tests/pipeline/test_technical.py b/tests/pipeline/test_technical.py index f63d9b8c..4b910a81 100644 --- a/tests/pipeline/test_technical.py +++ b/tests/pipeline/test_technical.py @@ -18,6 +18,7 @@ from zipline.pipeline.factors import ( IchimokuKinkoHyo, LinearWeightedMovingAverage, RateOfChangePercentage, + TrueRange, ) from zipline.testing import ExplodingObject, parameter_space from zipline.testing.fixtures import WithAssetFinder, ZiplineTestCase @@ -413,3 +414,20 @@ class TestLinearWeightedMovingAverage(ZiplineTestCase): wma2.compute(today, assets, out, data) assert_equal(out, np.array([30., 31., 32., 33., 34.])) + + +class TestTrueRange(WithTechnicalFactor, ZiplineTestCase): + + def test_tr_basic(self): + tr = TrueRange() + + today = pd.Timestamp('2014') + assets = np.arange(3, dtype=np.int64) + out = np.empty(3, dtype=np.float64) + + highs = np.full((2, 3), 3) + lows = np.full((2, 3), 2) + closes = np.full((2, 3), 1) + + tr.compute(today, assets, out, highs, lows, closes) + assert_equal(out, np.full((3,), 2)) diff --git a/zipline/pipeline/factors/__init__.py b/zipline/pipeline/factors/__init__.py index 0b505813..0155cab6 100644 --- a/zipline/pipeline/factors/__init__.py +++ b/zipline/pipeline/factors/__init__.py @@ -29,6 +29,7 @@ from .technical import ( Returns, RSI, SimpleMovingAverage, + TrueRange, VWAP, WeightedAverageValue, ) @@ -58,6 +59,7 @@ __all__ = [ 'RollingSpearmanOfReturns', 'RSI', 'SimpleMovingAverage', + 'TrueRange', 'VWAP', 'WeightedAverageValue', ] diff --git a/zipline/pipeline/factors/technical.py b/zipline/pipeline/factors/technical.py index 42e1736d..23a2a7d8 100644 --- a/zipline/pipeline/factors/technical.py +++ b/zipline/pipeline/factors/technical.py @@ -11,6 +11,7 @@ from numpy import ( average, clip, diff, + dstack, exp, fmax, full, @@ -642,3 +643,36 @@ class RateOfChangePercentage(CustomFactor): global_dict={}, out=out, ) + + +class TrueRange(CustomFactor): + """ + True Range + + A technical indicator originally developed by J. Welles Wilder, Jr. + Indicates the true degree of daily price change in an underlying. + + **Default Inputs:** :data:`zipline.pipeline.data.USEquityPricing.high` + :data:`zipline.pipeline.data.USEquityPricing.low` + :data:`zipline.pipeline.data.USEquityPricing.close` + **Default Window Length:** 2 + """ + inputs = ( + USEquityPricing.high, + USEquityPricing.low, + USEquityPricing.close, + ) + window_length = 2 + + def compute(self, today, assets, out, highs, lows, closes): + high_to_low = highs[1:] - lows[1:] + high_to_prev_close = abs(highs[1:] - closes[:-1]) + low_to_prev_close = abs(lows[1:] - closes[:-1]) + out[:] = nanmax( + dstack(( + high_to_low, + high_to_prev_close, + low_to_prev_close, + )), + 2 + )