From e9b7209c135e5a0c2a3b08141e16f44900a4a533 Mon Sep 17 00:00:00 2001 From: Thomas Wiecki Date: Mon, 4 Aug 2014 13:17:21 +0200 Subject: [PATCH 1/2] MAINT: Factor talib tests out to separate file. --- tests/test_transforms.py | 128 +---------------------------- tests/test_transforms_talib.py | 146 +++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 127 deletions(-) create mode 100644 tests/test_transforms_talib.py diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 29d1942c..215c523b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,10 +15,9 @@ import pytz import numpy as np -import pandas as pd from datetime import timedelta, datetime -from unittest import TestCase, skip +from unittest import TestCase from six.moves import range @@ -33,8 +32,6 @@ from zipline.transforms import MovingStandardDev from zipline.transforms import Returns import zipline.utils.factory as factory -from zipline.test_algorithms import TALIBAlgorithm - def to_dt(msg): return Event({'dt': msg}) @@ -276,126 +273,3 @@ class TestFinanceTransforms(TestCase): self.assertIsNone(v2) continue self.assertEquals(round(v1, 5), round(v2, 5)) - - -############################################################ -# Test TALIB - -import talib -import zipline.transforms.ta as ta - - -class TestTALIB(TestCase): - def setUp(self): - setup_logger(self) - sim_params = factory.create_simulation_parameters( - start=datetime(1990, 1, 1, tzinfo=pytz.utc), - end=datetime(1990, 3, 30, tzinfo=pytz.utc)) - self.source, self.panel = \ - factory.create_test_panel_ohlc_source(sim_params) - - @skip - def test_talib_with_default_params(self): - BLACKLIST = ['make_transform', 'BatchTransform', - # TODO: Figure out why MAVP generates a KeyError - 'MAVP'] - names = [name for name in dir(ta) if name[0].isupper() - and name not in BLACKLIST] - - for name in names: - print(name) - zipline_transform = getattr(ta, name)(sid=0) - talib_fn = getattr(talib.abstract, name) - - start = datetime(1990, 1, 1, tzinfo=pytz.utc) - end = start + timedelta(days=zipline_transform.lookback + 10) - sim_params = factory.create_simulation_parameters( - start=start, end=end) - source, panel = \ - factory.create_test_panel_ohlc_source(sim_params) - - algo = TALIBAlgorithm(talib=zipline_transform) - algo.run(source) - - zipline_result = np.array( - algo.talib_results[zipline_transform][-1]) - - talib_data = dict() - data = zipline_transform.window - # TODO: Figure out if we are clobbering the tests by this - # protection against empty windows - if not data: - continue - for key in ['open', 'high', 'low', 'volume']: - if key in data: - talib_data[key] = data[key][0].values - talib_data['close'] = data['price'][0].values - expected_result = talib_fn(talib_data) - - if isinstance(expected_result, list): - expected_result = np.array([e[-1] for e in expected_result]) - else: - expected_result = np.array(expected_result[-1]) - if not (np.all(np.isnan(zipline_result)) - and np.all(np.isnan(expected_result))): - self.assertTrue(np.allclose(zipline_result, expected_result)) - else: - print('--- NAN') - - # reset generator so next iteration has data - # self.source, self.panel = \ - # factory.create_test_panel_ohlc_source(self.sim_params) - - def test_multiple_talib_with_args(self): - zipline_transforms = [ta.MA(timeperiod=10), - ta.MA(timeperiod=25)] - talib_fn = talib.abstract.MA - algo = TALIBAlgorithm(talib=zipline_transforms) - algo.run(self.source) - # Test if computed values match those computed by pandas rolling mean. - sid = 0 - talib_values = np.array([x[sid] for x in - algo.talib_results[zipline_transforms[0]]]) - np.testing.assert_array_equal(talib_values, - pd.rolling_mean(self.panel[0]['price'], - 10).values) - talib_values = np.array([x[sid] for x in - algo.talib_results[zipline_transforms[1]]]) - np.testing.assert_array_equal(talib_values, - pd.rolling_mean(self.panel[0]['price'], - 25).values) - for t in zipline_transforms: - talib_result = np.array(algo.talib_results[t][-1]) - talib_data = dict() - data = t.window - # TODO: Figure out if we are clobbering the tests by this - # protection against empty windows - if not data: - continue - for key in ['open', 'high', 'low', 'volume']: - if key in data: - talib_data[key] = data[key][0].values - talib_data['close'] = data['price'][0].values - expected_result = talib_fn(talib_data, **t.call_kwargs)[-1] - np.testing.assert_allclose(talib_result, expected_result) - - def test_talib_with_minute_data(self): - - ma_one_day_minutes = ta.MA(timeperiod=10, bars='minute') - - # Assert that the BatchTransform window length is enough to cover - # the amount of minutes in the timeperiod. - - # Here, 10 minutes only needs a window length of 1. - self.assertEquals(1, ma_one_day_minutes.window_length) - - # With minutes greater than the 390, i.e. one trading day, we should - # have a window_length of two days. - ma_two_day_minutes = ta.MA(timeperiod=490, bars='minute') - self.assertEquals(2, ma_two_day_minutes.window_length) - - # TODO: Ensure that the lookback into the datapanel is returning - # expected results. - # Requires supplying minute instead of day data to the unit test. - # When adding test data, should add more minute events than the - # timeperiod to ensure that lookback is behaving properly. diff --git a/tests/test_transforms_talib.py b/tests/test_transforms_talib.py new file mode 100644 index 00000000..8021b2ed --- /dev/null +++ b/tests/test_transforms_talib.py @@ -0,0 +1,146 @@ +# +# Copyright 2013 Quantopian, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytz +import numpy as np +import pandas as pd + +from datetime import timedelta, datetime +from unittest import TestCase, skip + +from zipline.utils.test_utils import setup_logger + +import zipline.utils.factory as factory + +from zipline.test_algorithms import TALIBAlgorithm + +import talib +import zipline.transforms.ta as ta + + +class TestTALIB(TestCase): + def setUp(self): + setup_logger(self) + sim_params = factory.create_simulation_parameters( + start=datetime(1990, 1, 1, tzinfo=pytz.utc), + end=datetime(1990, 3, 30, tzinfo=pytz.utc)) + self.source, self.panel = \ + factory.create_test_panel_ohlc_source(sim_params) + + @skip + def test_talib_with_default_params(self): + BLACKLIST = ['make_transform', 'BatchTransform', + # TODO: Figure out why MAVP generates a KeyError + 'MAVP'] + names = [name for name in dir(ta) if name[0].isupper() + and name not in BLACKLIST] + + for name in names: + print(name) + zipline_transform = getattr(ta, name)(sid=0) + talib_fn = getattr(talib.abstract, name) + + start = datetime(1990, 1, 1, tzinfo=pytz.utc) + end = start + timedelta(days=zipline_transform.lookback + 10) + sim_params = factory.create_simulation_parameters( + start=start, end=end) + source, panel = \ + factory.create_test_panel_ohlc_source(sim_params) + + algo = TALIBAlgorithm(talib=zipline_transform) + algo.run(source) + + zipline_result = np.array( + algo.talib_results[zipline_transform][-1]) + + talib_data = dict() + data = zipline_transform.window + # TODO: Figure out if we are clobbering the tests by this + # protection against empty windows + if not data: + continue + for key in ['open', 'high', 'low', 'volume']: + if key in data: + talib_data[key] = data[key][0].values + talib_data['close'] = data['price'][0].values + expected_result = talib_fn(talib_data) + + if isinstance(expected_result, list): + expected_result = np.array([e[-1] for e in expected_result]) + else: + expected_result = np.array(expected_result[-1]) + if not (np.all(np.isnan(zipline_result)) + and np.all(np.isnan(expected_result))): + self.assertTrue(np.allclose(zipline_result, expected_result)) + else: + print('--- NAN') + + # reset generator so next iteration has data + # self.source, self.panel = \ + # factory.create_test_panel_ohlc_source(self.sim_params) + + def test_multiple_talib_with_args(self): + zipline_transforms = [ta.MA(timeperiod=10), + ta.MA(timeperiod=25)] + talib_fn = talib.abstract.MA + algo = TALIBAlgorithm(talib=zipline_transforms) + algo.run(self.source) + # Test if computed values match those computed by pandas rolling mean. + sid = 0 + talib_values = np.array([x[sid] for x in + algo.talib_results[zipline_transforms[0]]]) + np.testing.assert_array_equal(talib_values, + pd.rolling_mean(self.panel[0]['price'], + 10).values) + talib_values = np.array([x[sid] for x in + algo.talib_results[zipline_transforms[1]]]) + np.testing.assert_array_equal(talib_values, + pd.rolling_mean(self.panel[0]['price'], + 25).values) + for t in zipline_transforms: + talib_result = np.array(algo.talib_results[t][-1]) + talib_data = dict() + data = t.window + # TODO: Figure out if we are clobbering the tests by this + # protection against empty windows + if not data: + continue + for key in ['open', 'high', 'low', 'volume']: + if key in data: + talib_data[key] = data[key][0].values + talib_data['close'] = data['price'][0].values + expected_result = talib_fn(talib_data, **t.call_kwargs)[-1] + np.testing.assert_allclose(talib_result, expected_result) + + def test_talib_with_minute_data(self): + + ma_one_day_minutes = ta.MA(timeperiod=10, bars='minute') + + # Assert that the BatchTransform window length is enough to cover + # the amount of minutes in the timeperiod. + + # Here, 10 minutes only needs a window length of 1. + self.assertEquals(1, ma_one_day_minutes.window_length) + + # With minutes greater than the 390, i.e. one trading day, we should + # have a window_length of two days. + ma_two_day_minutes = ta.MA(timeperiod=490, bars='minute') + self.assertEquals(2, ma_two_day_minutes.window_length) + + # TODO: Ensure that the lookback into the datapanel is returning + # expected results. + # Requires supplying minute instead of day data to the unit test. + # When adding test data, should add more minute events than the + # timeperiod to ensure that lookback is behaving properly. From 3f1c3b39ad7666ec9e312c517a3650c7f94b9500 Mon Sep 17 00:00:00 2001 From: Thomas Wiecki Date: Mon, 4 Aug 2014 13:25:46 +0200 Subject: [PATCH 2/2] MAINT: Make talib an optional dependency. Fixes #362. --- conda/zipline/meta.yaml | 1 - etc/requirements.txt | 7 ------- etc/requirements_dev.txt | 7 +++++++ setup.py | 5 ++++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/conda/zipline/meta.yaml b/conda/zipline/meta.yaml index 77303ce4..4eda4523 100644 --- a/conda/zipline/meta.yaml +++ b/conda/zipline/meta.yaml @@ -19,7 +19,6 @@ requirements: - pandas - scipy - matplotlib - - ta-lib - logbook test: diff --git a/etc/requirements.txt b/etc/requirements.txt index baea4bab..2f7cc685 100644 --- a/etc/requirements.txt +++ b/etc/requirements.txt @@ -17,12 +17,5 @@ statsmodels==0.5.0 python-dateutil==2.2 six==1.6.1 -# Cython is required for TA-Lib -Cython==0.20.1 -# This --allow syntax is for compatibility with pip >= 1.5 -# However, this is backwards incompatible change, since previous -# versions of pip do not support that flag. ---allow-external TA-Lib --allow-unverified TA-Lib TA-Lib==0.4.8 - # For fetching remote data requests==2.3.0 diff --git a/etc/requirements_dev.txt b/etc/requirements_dev.txt index 5f4537ac..4da6201c 100644 --- a/etc/requirements_dev.txt +++ b/etc/requirements_dev.txt @@ -24,3 +24,10 @@ tornado==3.2.1 pyparsing==2.0.2 Markdown==2.4.1 + +# Cython is required for TA-Lib +Cython==0.20.1 +# This --allow syntax is for compatibility with pip >= 1.5 +# However, this is backwards incompatible change, since previous +# versions of pip do not support that flag. +--allow-external TA-Lib --allow-unverified TA-Lib TA-Lib==0.4.8 diff --git a/setup.py b/setup.py index bd6ceb85..1ca18e20 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2013 Quantopian, Inc. +# Copyright 2014 Quantopian, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -71,5 +71,8 @@ setup( 'pandas', 'six' ], + extras_require = { + 'talib': ["talib"], + }, url="https://github.com/quantopian/zipline" )