-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1199 from quantopian/boybands-factor
BollingerBands factor
- Loading branch information
Showing
11 changed files
with
378 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import numpy as np | ||
import pandas as pd | ||
import talib | ||
|
||
from zipline.lib.adjusted_array import AdjustedArray | ||
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.testing import ExplodingObject, parameter_space | ||
from zipline.testing.fixtures import WithAssetFinder, ZiplineTestCase | ||
from zipline.testing.predicates import assert_equal | ||
|
||
|
||
class WithTechnicalFactor(WithAssetFinder): | ||
"""ZiplineTestCase fixture for testing technical factors. | ||
""" | ||
ASSET_FINDER_EQUITY_SIDS = tuple(range(5)) | ||
START_DATE = pd.Timestamp('2014-01-01', tz='utc') | ||
|
||
@classmethod | ||
def init_class_fixtures(cls): | ||
super(WithTechnicalFactor, cls).init_class_fixtures() | ||
cls.ndays = ndays = 24 | ||
cls.nassets = nassets = len(cls.ASSET_FINDER_EQUITY_SIDS) | ||
cls.dates = dates = pd.date_range(cls.START_DATE, periods=ndays) | ||
cls.assets = pd.Index(cls.asset_finder.sids) | ||
cls.engine = SimplePipelineEngine( | ||
lambda column: ExplodingObject(), | ||
dates, | ||
cls.asset_finder, | ||
) | ||
cls.asset_exists = exists = np.full((ndays, nassets), True, dtype=bool) | ||
cls.asset_exists_masked = masked = exists.copy() | ||
masked[:, -1] = False | ||
|
||
def run_graph(self, graph, initial_workspace, mask_sid): | ||
initial_workspace.setdefault( | ||
AssetExists(), | ||
self.asset_exists_masked if mask_sid else self.asset_exists, | ||
) | ||
return self.engine.compute_chunk( | ||
graph, | ||
self.dates, | ||
self.assets, | ||
initial_workspace, | ||
) | ||
|
||
|
||
class BollingerBandsTestCase(WithTechnicalFactor, ZiplineTestCase): | ||
@classmethod | ||
def init_class_fixtures(cls): | ||
super(BollingerBandsTestCase, cls).init_class_fixtures() | ||
cls._closes = closes = ( | ||
np.arange(cls.ndays, dtype=float)[:, np.newaxis] + | ||
np.arange(cls.nassets, dtype=float) * 100 | ||
) | ||
cls._closes_masked = masked = closes.copy() | ||
masked[:, -1] = np.nan | ||
|
||
def closes(self, masked): | ||
return self._closes_masked if masked else self._closes | ||
|
||
def expected(self, window_length, k, closes): | ||
"""Compute the expected data (without adjustments) for the given | ||
window, k, and closes array. | ||
This uses talib.BBANDS to generate the expected data. | ||
""" | ||
lower_cols = [] | ||
middle_cols = [] | ||
upper_cols = [] | ||
for n in range(self.nassets): | ||
close_col = closes[:, n] | ||
if np.isnan(close_col).all(): | ||
# ta-lib doesn't deal well with all nans. | ||
upper, middle, lower = [np.full(self.ndays, np.nan)] * 3 | ||
else: | ||
upper, middle, lower = talib.BBANDS( | ||
close_col, | ||
window_length, | ||
k, | ||
k, | ||
) | ||
|
||
upper_cols.append(upper) | ||
middle_cols.append(middle) | ||
lower_cols.append(lower) | ||
|
||
# Stack all of our uppers, middles, lowers into three 2d arrays | ||
# whose columns are the sids. After that, slice off only the | ||
# rows we care about. | ||
where = np.s_[window_length - 1:] | ||
uppers = np.column_stack(upper_cols)[where] | ||
middles = np.column_stack(middle_cols)[where] | ||
lowers = np.column_stack(lower_cols)[where] | ||
return uppers, middles, lowers | ||
|
||
@parameter_space( | ||
window_length={5, 10, 20}, | ||
k={1.5, 2, 2.5}, | ||
mask_sid={True, False}, | ||
) | ||
def test_bollinger_bands(self, window_length, k, mask_sid): | ||
closes = self.closes(mask_sid) | ||
result = self.run_graph( | ||
TermGraph({ | ||
'f': BollingerBands( | ||
window_length=window_length, | ||
k=k, | ||
), | ||
}), | ||
initial_workspace={ | ||
USEquityPricing.close: AdjustedArray( | ||
closes, | ||
np.full_like(closes, True, dtype=bool), | ||
{}, | ||
np.nan, | ||
), | ||
}, | ||
mask_sid=mask_sid, | ||
)['f'] | ||
|
||
expected_upper, expected_middle, expected_lower = self.expected( | ||
window_length, | ||
k, | ||
closes, | ||
) | ||
|
||
assert_equal(result.upper, expected_upper) | ||
assert_equal(result.middle, expected_middle) | ||
assert_equal(result.lower, expected_lower) | ||
|
||
def test_bollinger_bands_output_ordering(self): | ||
bbands = BollingerBands(window_length=5, k=2) | ||
lower, middle, upper = bbands | ||
self.assertIs(lower, bbands.lower) | ||
self.assertIs(middle, bbands.middle) | ||
self.assertIs(upper, bbands.upper) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from zipline.testing.fixtures import ZiplineTestCase | ||
from zipline.testing.predicates import ( | ||
assert_equal, | ||
assert_is, | ||
assert_is_instance, | ||
assert_is_subclass, | ||
assert_true, | ||
) | ||
from zipline.utils.metautils import compose_types, with_metaclasses | ||
|
||
|
||
class C(object): | ||
@staticmethod | ||
def f(): | ||
return 'C.f' | ||
|
||
def delegate(self): | ||
return 'C.delegate', super(C, self).delegate() | ||
|
||
|
||
class D(object): | ||
@staticmethod | ||
def f(): | ||
return 'D.f' | ||
|
||
@staticmethod | ||
def g(): | ||
return 'D.g' | ||
|
||
def delegate(self): | ||
return 'D.delegate' | ||
|
||
|
||
class ComposeTypesTestCase(ZiplineTestCase): | ||
|
||
def test_identity(self): | ||
assert_is( | ||
compose_types(C), | ||
C, | ||
msg='compose_types of a single class should be identity', | ||
) | ||
|
||
def test_compose(self): | ||
composed = compose_types(C, D) | ||
|
||
assert_is_subclass(composed, C) | ||
assert_is_subclass(composed, D) | ||
|
||
def test_compose_mro(self): | ||
composed = compose_types(C, D) | ||
|
||
assert_equal(composed.f(), C.f()) | ||
assert_equal(composed.g(), D.g()) | ||
|
||
assert_equal(composed().delegate(), ('C.delegate', 'D.delegate')) | ||
|
||
|
||
class M(type): | ||
def __new__(mcls, name, bases, dict_): | ||
dict_['M'] = True | ||
return super(M, mcls).__new__(mcls, name, bases, dict_) | ||
|
||
|
||
class N(type): | ||
def __new__(mcls, name, bases, dict_): | ||
dict_['N'] = True | ||
return super(N, mcls).__new__(mcls, name, bases, dict_) | ||
|
||
|
||
class WithMetaclassesTestCase(ZiplineTestCase): | ||
def test_with_metaclasses_no_subclasses(self): | ||
class E(with_metaclasses((M, N))): | ||
pass | ||
|
||
assert_true(E.M) | ||
assert_true(E.N) | ||
|
||
assert_is_instance(E, M) | ||
assert_is_instance(E, N) | ||
|
||
def test_with_metaclasses_with_subclasses(self): | ||
class E(with_metaclasses((M, N), C, D)): | ||
pass | ||
|
||
assert_true(E.M) | ||
assert_true(E.N) | ||
|
||
assert_is_instance(E, M) | ||
assert_is_instance(E, N) | ||
assert_is_subclass(E, C) | ||
assert_is_subclass(E, D) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.