From 9bd878be0f37327760f2d6cf533846b9b32fbe3b Mon Sep 17 00:00:00 2001 From: Dorren Chen Date: Fri, 12 Mar 2021 23:40:19 -0500 Subject: [PATCH 1/3] td sequential --- README.md | 1 + pandas_ta/core.py | 6 +++ pandas_ta/momentum/__init__.py | 1 + pandas_ta/momentum/td.py | 62 ++++++++++++++++++++++++++++ tests/test_ext_indicator_momentum.py | 5 +++ tests/test_indicator_momentum.py | 6 +++ 6 files changed, 81 insertions(+) create mode 100644 pandas_ta/momentum/td.py diff --git a/README.md b/README.md index 9101753..0d72be2 100644 --- a/README.md +++ b/README.md @@ -497,6 +497,7 @@ print(bothhl2.name) # "pre_HL2_post" * Default is John Carter's. Enable Lazybear's with ```lazybear=True``` * _Stochastic Oscillator_: **stoch** * _Stochastic RSI_: **stochrsi** +* _TD Sequential_: **td** * _Trix_: **trix** * _True strength index_: **tsi** * _Ultimate Oscillator_: **uo** diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 6138e18..10b12d6 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -957,6 +957,12 @@ class AnalysisIndicators(BasePandasObject): result = stochrsi(high=high, low=low, close=close, length=length, rsi_length=rsi_length, k=k, d=d, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def td(self, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + + result = td(close=close, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def trix(self, length=None, signal=None, scalar=None, drift=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = trix(close=close, length=length, signal=signal, scalar=scalar, drift=drift, offset=offset, **kwargs) diff --git a/pandas_ta/momentum/__init__.py b/pandas_ta/momentum/__init__.py index 5384434..9eaa0fb 100644 --- a/pandas_ta/momentum/__init__.py +++ b/pandas_ta/momentum/__init__.py @@ -31,6 +31,7 @@ from .smi import smi from .squeeze import squeeze from .stoch import stoch from .stochrsi import stochrsi +from .td import td from .trix import trix from .tsi import tsi from .uo import uo diff --git a/pandas_ta/momentum/td.py b/pandas_ta/momentum/td.py new file mode 100644 index 0000000..b495af2 --- /dev/null +++ b/pandas_ta/momentum/td.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +import numpy as np +from pandas import DataFrame, Series +from pandas_ta.utils import get_offset, verify_series + +def true_sequence_count(s): + index = s.where(s == False).last_valid_index() + + if index is None: + return s.count() + else: + s = s[s.index > index] + return s.count() + +def calc_td(close, direction='up'): + td_bool = close.diff(4) > 0 if direction=='up' else close.diff(4) < 0 + td_num = np.where(td_bool, td_bool.rolling(13, min_periods=0).apply(true_sequence_count), 0) + td_num = Series(td_num) + td_num = td_num.mask(~td_num.between(6,9)) + + return td_num + +def td(close, offset=None, **kwargs): + up = calc_td(close, 'up') + down = calc_td(close, 'down') + df = DataFrame({'TD_up': up, 'TD_down': down}) + + # Offset + if offset and offset != 0: + df = df.shift(offset) + + if "fillna" in kwargs: + df.fillna(kwargs["fillna"], inplace=True) + + # Name & Category + df.name = "TD" + df.category = "momentum" + + return df + +td.__doc__ = \ +"""TD Sequential (TD) + +TD Sequential indicator. + +Sources: + https://tradetrekker.wordpress.com/tdsequential/ + +Calculation: + compare current close price with 4 days ago price, up to 13 days. + for the consecutive ascending or descending price sequence, display 6th to 9th day value. + +Args: + close (pd.Series): Series of 'close's + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + +Returns: + pd.DataFrame: New feature generated. +""" \ No newline at end of file diff --git a/tests/test_ext_indicator_momentum.py b/tests/test_ext_indicator_momentum.py index c4d6900..8beae3f 100644 --- a/tests/test_ext_indicator_momentum.py +++ b/tests/test_ext_indicator_momentum.py @@ -207,6 +207,11 @@ class TestMomentumExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(list(self.data.columns[-2:]), ["STOCHRSIk_14_14_3_3", "STOCHRSId_14_14_3_3"]) + def test_td_ext(self): + self.data.ta.td(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-2:]), ["TD_up", "TD_down"]) + def test_trix_ext(self): self.data.ta.trix(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 5aa9a84..51ec9ad 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -362,6 +362,12 @@ class TestMomentum(TestCase): self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "STOCHRSI_14_14_3_3") + def test_td(self): + # TD Sequential + result = pandas_ta.td(self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "TD") + def test_trix(self): result = pandas_ta.trix(self.close) self.assertIsInstance(result, DataFrame) From d51eeada34e0e0d2dffba119169fd77856470cb3 Mon Sep 17 00:00:00 2001 From: Dorren Chen Date: Sat, 13 Mar 2021 11:07:12 -0500 Subject: [PATCH 2/3] add show_all flag (default True) --- pandas_ta/momentum/td.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pandas_ta/momentum/td.py b/pandas_ta/momentum/td.py index b495af2..d60a415 100644 --- a/pandas_ta/momentum/td.py +++ b/pandas_ta/momentum/td.py @@ -12,17 +12,21 @@ def true_sequence_count(s): s = s[s.index > index] return s.count() -def calc_td(close, direction='up'): +def calc_td(close, direction, show_all): td_bool = close.diff(4) > 0 if direction=='up' else close.diff(4) < 0 td_num = np.where(td_bool, td_bool.rolling(13, min_periods=0).apply(true_sequence_count), 0) td_num = Series(td_num) - td_num = td_num.mask(~td_num.between(6,9)) + + if show_all: + td_num = td_num.mask(td_num == 0) + else: + td_num = td_num.mask(~td_num.between(6,9)) return td_num -def td(close, offset=None, **kwargs): - up = calc_td(close, 'up') - down = calc_td(close, 'down') +def td(close, offset=None, show_all=True, **kwargs): + up = calc_td(close, 'up', show_all) + down = calc_td(close, 'down', show_all) df = DataFrame({'TD_up': up, 'TD_down': down}) # Offset @@ -53,6 +57,7 @@ Calculation: Args: close (pd.Series): Series of 'close's offset (int): How many periods to offset the result. Default: 0 + show_all (bool): default True, show 1 - 13. If set to false, only show 6 - 9 Kwargs: fillna (value, optional): pd.DataFrame.fillna(value) From adb4ae3421d1f64152a107a8797f418011c1bdaa Mon Sep 17 00:00:00 2001 From: Dorren Chen Date: Sat, 13 Mar 2021 17:16:01 -0500 Subject: [PATCH 3/3] add show_all param to wrapper method --- pandas_ta/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 10b12d6..e2f0388 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -957,10 +957,10 @@ class AnalysisIndicators(BasePandasObject): result = stochrsi(high=high, low=low, close=close, length=length, rsi_length=rsi_length, k=k, d=d, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def td(self, offset=None, **kwargs): + def td(self, offset=None, show_all=True, **kwargs): close = self._get_column(kwargs.pop("close", "close")) - result = td(close=close, offset=offset, **kwargs) + result = td(close=close, offset=offset, show_all=show_all, **kwargs) return self._post_process(result, **kwargs) def trix(self, length=None, signal=None, scalar=None, drift=None, offset=None, **kwargs):