From 0f1c08024ab0b6b1519f71df55e86a056270df0c Mon Sep 17 00:00:00 2001 From: Joe Jevnik Date: Sat, 4 Jun 2016 17:57:44 -0400 Subject: [PATCH] ENH: Adds the ichimoku cloud factor --- tests/pipeline/test_technical.py | 119 +++++++++++++++++++++++++- zipline/pipeline/factors/__init__.py | 2 + zipline/pipeline/factors/technical.py | 73 +++++++++++++++- 3 files changed, 191 insertions(+), 3 deletions(-) diff --git a/tests/pipeline/test_technical.py b/tests/pipeline/test_technical.py index 97734477..bcf233fc 100644 --- a/tests/pipeline/test_technical.py +++ b/tests/pipeline/test_technical.py @@ -13,7 +13,8 @@ from zipline.pipeline.term import AssetExists from zipline.pipeline.factors import ( BollingerBands, Aroon, - FastStochasticOscillator + FastStochasticOscillator, + IchimokuKinkoHyo, ) from zipline.testing import ExplodingObject, parameter_space from zipline.testing.fixtures import WithAssetFinder, ZiplineTestCase @@ -237,3 +238,119 @@ class TestFastStochasticOscillator(WithTechnicalFactor, ZiplineTestCase): ) assert_equal(out, expected_out_k) + + +class IchimokuKinkoHyoTestCase(ZiplineTestCase): + def test_ichimoku_kinko_hyo(self): + window_length = 52 + today = pd.Timestamp('2014', tz='utc') + nassets = 5 + assets = pd.Index(np.arange(nassets)) + days_col = np.arange(window_length)[:, np.newaxis] + highs = np.arange(nassets) + 2 + days_col + closes = np.arange(nassets) + 1 + days_col + lows = np.arange(nassets) + days_col + + tenkan_sen_length = 9 + kijun_sen_length = 26 + chikou_span_length = 26 + ichimoku_kinko_hyo = IchimokuKinkoHyo( + window_length=window_length, + tenkan_sen_length=tenkan_sen_length, + kijun_sen_length=kijun_sen_length, + chikou_span_length=chikou_span_length, + ) + + dtype = [ + ('tenkan_sen', 'f8'), + ('kijun_sen', 'f8'), + ('senkou_span_a', 'f8'), + ('senkou_span_b', 'f8'), + ('chikou_span', 'f8'), + ] + out = np.recarray( + shape=(nassets,), + dtype=dtype, + buf=np.empty(shape=(nassets,), dtype=dtype), + ) + ichimoku_kinko_hyo.compute( + today, + assets, + out, + highs, + lows, + closes, + tenkan_sen_length, + kijun_sen_length, + chikou_span_length, + ) + + expected_tenkan_sen = np.array([ + (53 + 43) / 2, + (54 + 44) / 2, + (55 + 45) / 2, + (56 + 46) / 2, + (57 + 47) / 2, + ]) + expected_kijun_sen = np.array([ + (53 + 26) / 2, + (54 + 27) / 2, + (55 + 28) / 2, + (56 + 29) / 2, + (57 + 30) / 2, + ]) + expected_senkou_span_a = (expected_tenkan_sen + expected_kijun_sen) / 2 + expected_senkou_span_b = np.array([ + (53 + 0) / 2, + (54 + 1) / 2, + (55 + 2) / 2, + (56 + 3) / 2, + (57 + 4) / 2, + ]) + expected_chikou_span = np.array([ + 27.0, + 28.0, + 29.0, + 30.0, + 31.0, + ]) + + assert_equal( + out.tenkan_sen, + expected_tenkan_sen, + msg='tenkan_sen', + ) + assert_equal( + out.kijun_sen, + expected_kijun_sen, + msg='kijun_sen', + ) + assert_equal( + out.senkou_span_a, + expected_senkou_span_a, + msg='senkou_span_a', + ) + assert_equal( + out.senkou_span_b, + expected_senkou_span_b, + msg='senkou_span_b', + ) + assert_equal( + out.chikou_span, + expected_chikou_span, + msg='chikou_span', + ) + + @parameter_space( + arg={'tenkan_sen_length', 'kijun_sen_length', 'chikou_span_length'}, + ) + def test_input_validation(self, arg): + window_length = 52 + + with self.assertRaises(ValueError) as e: + IchimokuKinkoHyo(**{arg: window_length + 1}) + + assert_equal( + str(e.exception), + '%s must be <= the window_length: 53 > 52' % arg, + ) diff --git a/zipline/pipeline/factors/__init__.py b/zipline/pipeline/factors/__init__.py index 481219b3..e32dac95 100644 --- a/zipline/pipeline/factors/__init__.py +++ b/zipline/pipeline/factors/__init__.py @@ -22,6 +22,7 @@ from .technical import ( ExponentialWeightedMovingAverage, ExponentialWeightedMovingStdDev, FastStochasticOscillator, + IchimokuKinkoHyo, MaxDrawdown, Returns, RSI, @@ -43,6 +44,7 @@ __all__ = [ 'ExponentialWeightedMovingStdDev', 'Factor', 'FastStochasticOscillator', + 'IchimokuKinkoHyo', 'Latest', 'MaxDrawdown', 'RecarrayField', diff --git a/zipline/pipeline/factors/technical.py b/zipline/pipeline/factors/technical.py index c7e26efe..8344130a 100644 --- a/zipline/pipeline/factors/technical.py +++ b/zipline/pipeline/factors/technical.py @@ -445,8 +445,7 @@ class BollingerBands(CustomFactor): class Aroon(CustomFactor): """ Aroon technical indicator. - https://www.fidelity.com/learning-center/trading-investing/technical - -analysis/technical-indicator-guide/aroon-indicator + https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/aroon-indicator # noqa **Defaults Inputs:** USEquityPricing.low, USEquityPricing.high @@ -521,3 +520,73 @@ class FastStochasticOscillator(CustomFactor): global_dict={}, out=out, ) + + +class IchimokuKinkoHyo(CustomFactor): + """Compute the various metrics for the Ichimoku Kinko Hyo (Ichimoku Cloud). + http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud # noqa + + **Default Inputs:** :data:`zipline.pipeline.data.USEquityPricing.high` + :data:`zipline.pipeline.data.USEquityPricing.low` + :data:`zipline.pipeline.data.USEquityPricing.close` + **Default Window Length:** 52 + + Parameters + ---------- + window_length : int > 0 + The length the the window for the senkou span b. + tenkan_sen_length : int >= 0, <= window_length + The length of the window for the tenkan-sen. + kijun_sen_length : int >= 0, <= window_length + The length of the window for the kijou-sen. + chikou_span_length : int >= 0, <= window_length + The lag for the chikou span. + """ + + params = { + 'tenkan_sen_length': 9, + 'kijun_sen_length': 26, + 'chikou_span_length': 26, + } + inputs = USEquityPricing.high, USEquityPricing.close + outputs = ( + 'tenkan_sen', + 'kijun_sen', + 'senkou_span_a', + 'senkou_span_b', + 'chikou_span', + ) + window_length = 52 + + def _validate(self): + super(IchimokuKinkoHyo, self)._validate() + for k, v in self.params.items(): + if v > self.window_length: + raise ValueError( + '%s must be <= the window_length: %s > %s' % ( + k, v, self.window_length, + ), + ) + + def compute(self, + today, + assets, + out, + high, + low, + close, + tenkan_sen_length, + kijun_sen_length, + chikou_span_length): + + out.tenkan_sen = tenkan_sen = ( + high[-tenkan_sen_length:].max(axis=0) + + low[-tenkan_sen_length:].min(axis=0) + ) / 2 + out.kijun_sen = kijun_sen = ( + high[-kijun_sen_length:].max(axis=0) + + low[-kijun_sen_length:].min(axis=0) + ) / 2 + out.senkou_span_a = (tenkan_sen + kijun_sen) / 2 + out.senkou_span_b = (high.max(axis=0) + low.min(axis=0)) / 2 + out.chikou_span = close[chikou_span_length]