diff --git a/README.md b/README.md index 00d5595..707912b 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Pandas TA Strategies](#pandas-ta-strategies) * [Types of Strategies](#types-of-strategies) * [DataFrame Properties](#dataframe-properties) +* [DataFrame Methods](#dataframe-methods) * [Indicators by Category](#indicators-by-category) * [Candles](#candles-3) * [Cycles](#cycles-1) - * [Momentum](#momentum-36) + * [Momentum](#momentum-37) * [Overlap](#overlap-31) * [Performance](#performance-4) * [Statistics](#statistics-9) @@ -84,7 +85,7 @@ $ pip install pandas_ta Latest Version -------------- -Best choice! Version: *0.2.53b* +Best choice! Version: *0.2.62b* ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta ``` @@ -182,7 +183,7 @@ Thanks for using **Pandas TA**! _Thank you for your contributions!_ -[alexonab](https://github.com/alexonab) | [allahyarzadeh](https://github.com/allahyarzadeh) | [codesutras](https://github.com/codesutras) | [daikts](https://github.com/daikts) | [DrPaprikaa](https://github.com/DrPaprikaa) | [edwardwang1](https://github.com/edwardwang1) | [ffhirata](https://github.com/ffhirata) | [FGU1](https://github.com/FGU1) | [lluissalord](https://github.com/lluissalord) | [maxdignan](https://github.com/maxdignan) | [moritzgun](https://github.com/moritzgun) | [M6stafa](https://github.com/M6stafa) | [NkosenhleDuma](https://github.com/NkosenhleDuma) | [pbrumblay](https://github.com/pbrumblay) | [RajeshDhalange](https://github.com/RajeshDhalange) | [rengel8](https://github.com/rengel8) | [rluong003](https://github.com/rluong003) | [SoftDevDanial](https://github.com/SoftDevDanial) | [tg12](https://github.com/tg12) | [twrobel](https://github.com/twrobel) | [whubsch](https://github.com/whubsch) | [witokondoria](https://github.com/witokondoria) | [wouldayajustlookatit](https://github.com/wouldayajustlookatit) | [YuvalWein](https://github.com/YuvalWein) +[alexonab](https://github.com/alexonab) | [allahyarzadeh](https://github.com/allahyarzadeh) | [codesutras](https://github.com/codesutras) | [DrPaprikaa](https://github.com/DrPaprikaa) | [daikts](https://github.com/daikts) | [dorren](https://github.com/dorren) | [edwardwang1](https://github.com/edwardwang1) | [ffhirata](https://github.com/ffhirata) | [FGU1](https://github.com/FGU1) | [lluissalord](https://github.com/lluissalord) | [M6stafa](https://github.com/M6stafa) | [maxdignan](https://github.com/maxdignan) | [moritzgun](https://github.com/moritzgun) | [NkosenhleDuma](https://github.com/NkosenhleDuma) | [pbrumblay](https://github.com/pbrumblay) | [RajeshDhalange](https://github.com/RajeshDhalange) | [rengel8](https://github.com/rengel8) | [rluong003](https://github.com/rluong003) | [SoftDevDanial](https://github.com/SoftDevDanial) | [tg12](https://github.com/tg12) | [twrobel](https://github.com/twrobel) | [whubsch](https://github.com/whubsch) | [witokondoria](https://github.com/witokondoria) | [wouldayajustlookatit](https://github.com/wouldayajustlookatit) | [YuvalWein](https://github.com/YuvalWein)
@@ -566,7 +567,7 @@ help(ta.yf) * _Even Better Sinewave_: **ebsw**
-### **Momentum** (36) +### **Momentum** (37) * _Awesome Oscillator_: **ao** * _Absolute Price Oscillator_: **apo** @@ -601,7 +602,8 @@ help(ta.yf) * Default is John Carter's. Enable Lazybear's with ```lazybear=True``` * _Stochastic Oscillator_: **stoch** * _Stochastic RSI_: **stochrsi** -* _TD Sequential_: **td** +* _TD Sequential_: **td_seq** + * Excluded from ```df.ta.strategy()```. * _Trix_: **trix** * _True strength index_: **tsi** * _Ultimate Oscillator_: **uo** @@ -799,27 +801,12 @@ result = ta.cagr(df.close)
-## **Breaking Indicators** -* _Bollinger Bands_ (**bbands**): New column 'bandwidth' appended to the returning DataFrame. See: ```help(ta.bbands)``` -* _Volume Weighted Average Price_ (**vwap**): **Requires** the DataFrame index to be a DatetimeIndex. - - ## **New Indicators** * _Arnaud Legoux Moving Average_ (**alma**) uses the curve of the Normal (Gauss) distribution to allow regulating the smoothness and high sensitivity of the indicator. See: ```help(ta.alma)``` -* _Drawdown_ (**drawdown**) shows the peak-to-trough decline during a specific period for an investment, trading account, or fund. See: ```help(ta.drawdown)``` * _Even Better Sinewave_ (**ebsw**) measures market cycles and uses a low pass filter to remove noise. See: ```help(ta.ebsw)``` -* _Gann High-Low Activator_ (**hilo**) was created by Robert Krausz in a 1998. See: ```help(ta.hilo)``` -* _Holt-Winter Moving Average_ (**hwma**) is a three-parameter moving average by the Holt-Winter method. -* _McGinley Dynamic_ (**mcgd**) is an overlap indicator developed by John R. McGinley, a Certified Market Technician. See: ```help(ta.mcgd)``` -* _Price Volume Rank_ (**pvr**) was created by Anthony J. Macek. See: ```help(ta.pvr)``` -* _Quantitative Qualitative Estimation_ (**qqe**) is like SuperTrend for a Smoothed RSI. See: ```help(ta.qqe)``` -article in the June, 1994 issue of Technical Analysis of Stocks & Commodities Magazine. See: ```help(ta.pvr)``` -* _Relative Strength Xtra_ (**rsx**) is based on the popular RSI indicator and inspired by the work Jurik Research. See: ```help(ta.rsx)``` -* _Ehler's Super Smoother Filter_ (**ssf**). Ehler's solution to reduce lag and remove aliasing noise compared to other common moving average indicators. See: ```help(ta.ssf)``` -* _Elder's Thermometer_ (**thermo**) measures price volatility. See: ```help(ta.thermo)``` -* _TTM Trend_ (**ttm_trend**) is a trend indicator inspired from John Carter's book "Mastering the Trade" issue of Stocks & Commodities Magazine. It is a moving average based trend indicator consisting of two different simple moving averages. See: ```help(ta.ttm_trend)``` -* _Variable Index Dynamic Average_ (**vidya**) is a popular Dynamic Moving Average created by Tushar Chande. See: ```help(ta.vidya)``` +* _Tom DeMark's Sequential_ (**td_seq**) attempts to identify a price point where an uptrend or a downtrend exhausts itself and reverses. Currently exlcuded from ```df.ta.strategy()``` for performance reasons. See: ```help(ta.td_seq)``` +
## **Updated Indicators** * _ADX_ (**adx**): Added ```mamode``` with default "**RMA**" and with the same ```mamode``` options as TradingView. See ```help(ta.adx)```. diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 6f7e029..7972ef3 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -47,7 +47,7 @@ Category = { "ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo", "coppock", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd", "mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi", - "slope", "smi", "squeeze", "stoch", "stochrsi", "trix", "tsi", "uo", + "slope", "smi", "squeeze", "stoch", "stochrsi", "td_seq", "trix", "tsi", "uo", "willr" ], # Overlap diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 5de6004..2ca01f5 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -607,21 +607,30 @@ class AnalysisIndicators(BasePandasObject): """Strategy Method An experimental method that by default runs all applicable indicators. - Future implementations will allow more specific indicator generation through - a json config file. + Future implementations will allow more specific indicator generation + with possibly as json, yaml config file or an sqlite3 table. - Args: - name (str, optional): Default: "all" - exclude (list, optional): Default: []. List of indicator names to exclude. - kwargs: - (optional) Default: {}. Any indicator argument you want to modify. - For example, length=20 or offset=-1 or high=df["high"] ... + Kwargs: + chunksize (bool): Adjust the chunksize for the Multiprocessing Pool. + Default: Number of cores of the OS + exclude (list): List of indicator names to exclude. Some are + excluded by default for various reasons; they require additional + sources, performance (td_seq), not a ohlcv chart (vp) etc. + name (str): Select all indicators or indicators by + Category such as: "candles", "cycles", "momentum", "overlap", + "performance", "statistics", "trend", "volatility", "volume", or + "all". Default: "all" + ordered (bool): Whether to run "all" in order. Default: True + timed (bool): Show the process time of the strategy(). + Default: False + verbose (bool): Provide some additional insight on the progress of + the strategy() execution. Default: False """ # cpus = cpu_count() # Ensure indicators are appended to the DataFrame kwargs["append"] = True - all_ordered = kwargs.pop("ordered", False) + all_ordered = kwargs.pop("ordered", True) mp_chunksize = kwargs.pop("chunksize", self.cores) # Initialize @@ -637,6 +646,7 @@ class AnalysisIndicators(BasePandasObject): "long_run", "short_run", "trend_return", + "td_seq", # Performance exclusion "vp", ] @@ -702,7 +712,7 @@ class AnalysisIndicators(BasePandasObject): # Some magic to optimize chunksize for speed based on total ta indicators _chunksize = mp_chunksize - 1 if mp_chunksize > _total_ta else int(npLog10(_total_ta)) + 1 if verbose: - print(f"[i] Multiprocessing {_total_ta} indicators with {_chunksize} chunks over {self.cores}/{cpu_count()} cpus.") + print(f"[i] Multiprocessing {_total_ta} indicators with {_chunksize} chunks and {self.cores}/{cpu_count()} cpus.") results = None if mode["custom"]: @@ -810,7 +820,11 @@ class AnalysisIndicators(BasePandasObject): elif df.empty: print(f"[X] DataFrame is empty: {df.shape}") return - else: self._df = df + else: + if kwargs.pop("lc_input", False): + df.index.name = df.index.name.lower() + df.columns = df.columns.str.lower() + self._df = df if strategy is not None: self.strategy(strategy, **kwargs) return df @@ -1043,10 +1057,9 @@ 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, show_all=True, **kwargs): + def td_seq(self, asint=None, offset=None, show_all=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) - - result = td(close=close, offset=offset, show_all=show_all, **kwargs) + result = td_seq(close=close, asint=asint, 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): diff --git a/pandas_ta/momentum/__init__.py b/pandas_ta/momentum/__init__.py index 9eaa0fb..ded8f90 100644 --- a/pandas_ta/momentum/__init__.py +++ b/pandas_ta/momentum/__init__.py @@ -31,7 +31,7 @@ from .smi import smi from .squeeze import squeeze from .stoch import stoch from .stochrsi import stochrsi -from .td import td +from .td_seq import td_seq 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 deleted file mode 100644 index d60a415..0000000 --- a/pandas_ta/momentum/td.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- 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, 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) - - 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, 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 - 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 - show_all (bool): default True, show 1 - 13. If set to false, only show 6 - 9 - -Kwargs: - fillna (value, optional): pd.DataFrame.fillna(value) - -Returns: - pd.DataFrame: New feature generated. -""" \ No newline at end of file diff --git a/pandas_ta/momentum/td_seq.py b/pandas_ta/momentum/td_seq.py new file mode 100644 index 0000000..8bd12fd --- /dev/null +++ b/pandas_ta/momentum/td_seq.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# import numpy as np +from numpy import where as npWhere +from pandas import DataFrame, Series +from pandas_ta.utils import get_offset, verify_series + + +def td_seq(close, asint=None, offset=None, **kwargs): + """Indicator: Tom Demark Sequential (TD_SEQ)""" + # Validate arguments + close = verify_series(close) + offset = get_offset(offset) + asint = asint if isinstance(asint, bool) else False + show_all = kwargs.setdefault("show_all", True) + + def true_sequence_count(series: Series): + index = series.where(series == False).last_valid_index() + + if index is None: + return series.count() + else: + s = series[series.index > index] + return s.count() + + def calc_td(series: Series, direction: str, show_all: bool): + td_bool = series.diff(4) > 0 if direction=="up" else series.diff(4) < 0 + td_num = npWhere( + td_bool, td_bool.rolling(13, min_periods=0).apply(true_sequence_count), 0 + ) + td_num = Series(td_num) + + 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 + + up_seq = calc_td(close, "up", show_all) + down_seq = calc_td(close, "down", show_all) + + if asint: + if up_seq.hasnans and down_seq.hasnans: + up_seq.fillna(0, inplace=True) + down_seq.fillna(0, inplace=True) + up_seq = up_seq.astype(int) + down_seq = down_seq.astype(int) + + # Offset + if offset != 0: + up_seq = up_seq.shift(offset) + down_seq = down_seq.shift(offset) + + # Handle fills + if "fillna" in kwargs: + up_seq.fillna(kwargs["fillna"], inplace=True) + down_seq.fillna(kwargs["fillna"], inplace=True) + + if "fill_method" in kwargs: + up_seq.fillna(method=kwargs["fill_method"], inplace=True) + down_seq.fillna(method=kwargs["fill_method"], inplace=True) + + # Name & Category + up_seq.name = f"TD_SEQ_UPa" if show_all else f"TD_SEQ_UP" + down_seq.name = f"TD_SEQ_DNa" if show_all else f"TD_SEQ_DN" + up_seq.category = down_seq.category = "momentum" + + # Prepare Dataframe to return + data = { + up_seq.name: up_seq, + down_seq.name: down_seq + } + df = DataFrame(data) + df.name = "TD_SEQ" + df.category = up_seq.category + + return df + + +td_seq.__doc__ = \ +"""TD Sequential (TD_SEQ) + +Tom DeMark's Sequential indicator attempts to identify a price point where an +uptrend or a downtrend exhausts itself and reverses. + +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 + asint (bool): If True, fillnas with 0 and change type to int. Default: False + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + show_all (bool): Show 1 - 13. If set to False, show 6 - 9. Default: True + fillna (value, optional): pd.DataFrame.fillna(value) + +Returns: + pd.DataFrame: New feature generated. +""" \ No newline at end of file diff --git a/pandas_ta/trend/cksp.py b/pandas_ta/trend/cksp.py index c9f9a55..88dde58 100644 --- a/pandas_ta/trend/cksp.py +++ b/pandas_ta/trend/cksp.py @@ -57,10 +57,9 @@ def cksp(high, low, close, p=None, x=None, q=None, offset=None, **kwargs): cksp.__doc__ = \ """Chande Kroll Stop (CKSP) -The Tushar Chande and Stanley Kroll in their book -“The New Technical Trader”. It is a trend-following indicator, -identifying your stop by calculating the average true range of -the recent market volatility. +The Tushar Chande and Stanley Kroll in their book “The New Technical Trader”. +It is a trend-following indicator, identifying your stop by calculating the +average true range of the recent market volatility. Sources: https://www.multicharts.com/discussion/viewtopic.php?t=48914 diff --git a/pandas_ta/utils/_math.py b/pandas_ta/utils/_math.py index 3bc8253..e532b37 100644 --- a/pandas_ta/utils/_math.py +++ b/pandas_ta/utils/_math.py @@ -57,7 +57,7 @@ def fibonacci(n: int = 2, **kwargs: dict) -> npNdArray: a, b = 1, 1 result = npArray([a]) - for i in range(0, n): + for _ in range(0, n): a, b = b, a + b result = npAppend(result, a) diff --git a/setup.py b/setup.py index dc59882..946f8c2 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "2", "61b")), + version=".".join(("0", "2", "62b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_momentum.py b/tests/test_ext_indicator_momentum.py index 8beae3f..d5b5c95 100644 --- a/tests/test_ext_indicator_momentum.py +++ b/tests/test_ext_indicator_momentum.py @@ -207,10 +207,14 @@ 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) + def test_td_seq_ext(self): + self.data.ta.td_seq(show_all=False, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ["TD_up", "TD_down"]) + self.assertEqual(list(self.data.columns[-2:]), ["TD_SEQ_UP", "TD_SEQ_DN"]) + + self.data.ta.td_seq(show_all=True, append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-2:]), ["TD_SEQ_UPa", "TD_SEQ_DNa"]) def test_trix_ext(self): self.data.ta.trix(append=True) diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 51ec9ad..7589ac3 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -362,11 +362,10 @@ 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) + def test_td_seq(self): + result = pandas_ta.td_seq(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, "TD") + self.assertEqual(result.name, "TD_SEQ") def test_trix(self): result = pandas_ta.trix(self.close) diff --git a/tests/test_strategy.py b/tests/test_strategy.py index 7ec1939..0efb262 100644 --- a/tests/test_strategy.py +++ b/tests/test_strategy.py @@ -165,6 +165,25 @@ class TestStrategyMethods(TestCase): ) self.data.ta.strategy(custom, verbose=verbose, timed=strategy_timed) + # @skip + def test_custom_a(self): + self.category = "Custom E" + + amat_logret_ta = [ + {"kind": "amat", "fast": 20, "slow": 50 }, # 2 + {"kind": "log_return", "cumulative": True}, # 1 + {"kind": "ema", "close": "CUMLOGRET_1", "length": 5} # 1 + ] + + custom = pandas_ta.Strategy( + "AMAT Log Returns", # name + amat_logret_ta, # ta + "AMAT Log Returns", # description + ) + self.data.ta.strategy(custom, verbose=verbose, timed=strategy_timed, ordered=True) + self.data.ta.trend_return(trend=self.data["AMATe_LR_2"], cumulative=True, append=True) + self.assertEqual(len(self.data.columns), 13) + # @skip def test_momentum_category(self): self.category = "Momentum"