From 57b6e7d7f2cf09756bb231c35a6f47661633c702 Mon Sep 17 00:00:00 2001 From: ali gheshlaghi Date: Wed, 31 May 2023 01:10:47 +0330 Subject: [PATCH 1/7] Zigzag indicator implemented and added to pandas-ta library! --- pandas_ta/__init__.py | 2 +- pandas_ta/core.py | 6 ++ pandas_ta/trend/__init__.py | 1 + pandas_ta/trend/zigzag.py | 130 ++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 pandas_ta/trend/zigzag.py diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 0e740b84..06254b66 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -70,7 +70,7 @@ "trend": [ "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", "increasing", "long_run", "psar", "qstick", "short_run", "tsignals", - "ttm_trend", "vhf", "vortex", "xsignals" + "ttm_trend", "vhf", "vortex", "xsignals", "zigzag" ], # Volatility "volatility": [ diff --git a/pandas_ta/core.py b/pandas_ta/core.py index fdee64ab..aa870197 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1517,6 +1517,12 @@ def xsignals(self, signal=None, xa=None, xb=None, above=None, long=None, asbool= else: result = xsignals(signal=signal, xa=xa, xb=xb, above=above, long=long, asbool=asbool, trend_offset=trend_offset, trend_reset=trend_reset, offset=offset, **kwargs) return self._post_process(result, **kwargs) + + def zigzag(self, pivot_leg=None, price_deviation=None, retrace=None, last_extreme=None, **kwargs): + high = self._get_column(kwargs.pop("high", "high")) + low = self._get_column(kwargs.pop("low", "low")) + result = zigzag(high, low, pivot_leg, price_deviation, retrace=False, last_extreme=False, **kwargs) + return self._post_process(result, **kwargs) # Utility def above(self, asint=True, offset=None, **kwargs): diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py index 4544a019..0d957d10 100644 --- a/pandas_ta/trend/__init__.py +++ b/pandas_ta/trend/__init__.py @@ -17,3 +17,4 @@ from .vhf import vhf from .vortex import vortex from .xsignals import xsignals +from .zigzag import zigzag diff --git a/pandas_ta/trend/zigzag.py b/pandas_ta/trend/zigzag.py new file mode 100644 index 00000000..1ba9ee44 --- /dev/null +++ b/pandas_ta/trend/zigzag.py @@ -0,0 +1,130 @@ +from pandas import DataFrame, concat, merge + + +def zigzag(high, low, pivot_leg=5, price_deviation=10, retrace=False, last_extreme=False, **kwargs): + def find_extremum(rolling, pivot_leg): + extremum = rolling[( + (rolling.diff() == 0).cumsum() - (rolling.diff() == 0).cumsum().shift( + pivot_leg - 1) == (pivot_leg - 1)).shift(-int(pivot_leg / 2)).fillna(False)] + return extremum + + def calculate_swings(high, low, pivot_leg): + rolling_high = high.rolling(window=pivot_leg, center=True, min_periods=0).max() + rolling_low = low.rolling(window=pivot_leg, center=True, min_periods=0).min() + + high_swings = find_extremum(rolling_high, pivot_leg) + low_swings = find_extremum(rolling_low, pivot_leg) + + highs = DataFrame({'swing_value': high_swings, 'swing_type': 'high'}) + lows = DataFrame({'swing_value': low_swings, 'swing_type': 'low'}) + swings_high_low = concat([highs, lows]).sort_index() + return swings_high_low + + def price_dev(swings, price_deviation): + """ + This function should get swings data and will return the swings, + which are below the price deviation threshold. + This data may have some high/low swings together and sequential which is not desired. + These bad data will be handled in other functions. + """ + swings = swings[((swings['swing_value'].pct_change()).abs()) > price_deviation / 100] + swings = swings.reset_index(drop=True) + + return swings + + def swing_seq_catch(swing_data): + """ + This function should get swings data and will return the swings, + with one column added named new_swing_type which will determine if there is a change in swing type, + e.g. from high to low or vice versa. + """ + new_swings = swing_data[(swing_data.swing_type.shift(1) == swing_data.swing_type) | + (swing_data.swing_type.shift(-1) == swing_data.swing_type)] + if len(new_swings) > 0: + swing_change_idx = (swing_data.swing_type.shift(1) != swing_data.swing_type).cumsum() + new_s = new_swings['swing_type'].astype(str) + swing_change_idx.astype('str') + new_swings.insert(2, "new_swing_type", new_s, True) + return new_swings + + def swing_seq_picker(swings_data, swing_type): + """ + This function manages to remove useless sequential swings in a pandas way + """ + if swing_type == 'high': + agg_func = 'max' + elif swing_type == 'low': + agg_func = 'min' + else: + raise ValueError("Input data must be eigher high or low!") + + duplicate_swings = swings_data.groupby(['new_swing_type']).agg({'swing_value': agg_func}) + return merge(duplicate_swings, swings_data, how="inner", on=["swing_value", 'new_swing_type']) + + def swing_hl_merge(swings_sequential, swings_all): + """ + This function is responsible for handle high/low swing types. + It will merge all the high/low swings and will garantee that high/low are sequential. + """ + if len(swings_sequential) > 0: + high_swings = swing_seq_picker(swings_sequential[swings_sequential['swing_type'] == 'high'], 'high') + low_swings = swing_seq_picker(swings_sequential[swings_sequential['swing_type'] == 'low'], 'low') + swings_high_low = concat([swings_sequential, high_swings, low_swings]).drop_duplicates(keep=False) + swings_high_low = swings_high_low.reset_index(drop=True) + del swings_high_low['new_swing_type'] + else: + swings_high_low = swings_sequential + swings_high_low = concat([swings_all, swings_high_low]).drop_duplicates(keep=False) + return swings_high_low + + all_swings = calculate_swings(high, low, pivot_leg) + all_swings['idx'] = all_swings.index + swings_seq = swing_seq_catch(all_swings) + swings_merged = swing_hl_merge(swings_seq, all_swings).reset_index(drop=True) + swings = swings_merged.reset_index(drop=True) + + while (swings['swing_value'].pct_change().abs() <= price_deviation / 100).any(): + swings_price_dev = price_dev(swings, price_deviation) + swings_seq_price_dev = swing_seq_catch(swings_price_dev) + swings_seq_filtered = swing_hl_merge(swings_seq_price_dev, swings_price_dev) + swings = swings_seq_filtered + + swings.index = swings['idx'] + del swings['idx'] + + return all_swings, swings + + +zigzag.__doc__ = \ +""" Zigzag Inddicator + +The ZigZag feature on SharpCharts is not an indicator per se, but rather a means to filter out smaller price movements. +A ZigZag set at 10 would ignore all price movements less than 10%; only price movements greater than 10% would be shown. +Filtering out smaller movements gives chartists the ability to see the forest instead of just trees. + +Sources: + https://www.tradingview.com/support/solutions/43000591664-zig-zag/#:~:text=Definition,trader%20visual%20the%20price%20action. + https://school.stockcharts.com/doku.php?id=technical_indicators:zigzag + +Calculation: + ZigZag (high, low, pivot_leg, price_deviation, retrace=False, LastExtreme=False) + If % change > = X, plot ZigZag + + Default Inputs: + pivot_leg = 5, price_deviation = 10 + retrace = FALSE, LastExtreme = TRUE + + See Source links + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + pivot_leg (int): Initial Acceleration Factor. Default: 5 + price_deviation (float): % change of price. Default: 10 + retrace (Boolean): This can be the change in the retracement of a previous move. Default: False + Last_extreme (Boolean): This references extreme price, if it is the same over multiple periods. Default: False + +Kwargs: + +Returns: + pd.DataFrame: index of swings, swing_value, and swing_type (high or low). +""" From 8320fa7e11eaa9151a2cc0c822f02fdee4f09a8f Mon Sep 17 00:00:00 2001 From: ali gheshlaghi Date: Sun, 4 Jun 2023 22:07:17 +0330 Subject: [PATCH 2/7] Alpha Trend indicator added to pandas-ta --- pandas_ta/__init__.py | 2 +- pandas_ta/core.py | 12 +++- pandas_ta/trend/__init__.py | 1 + pandas_ta/trend/alphatrend.py | 116 ++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 pandas_ta/trend/alphatrend.py diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 06254b66..182bfe66 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -68,7 +68,7 @@ ], # Trend "trend": [ - "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", + "adx", "amat", "alphatrend", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", "increasing", "long_run", "psar", "qstick", "short_run", "tsignals", "ttm_trend", "vhf", "vortex", "xsignals", "zigzag" ], diff --git a/pandas_ta/core.py b/pandas_ta/core.py index aa870197..1696c9e1 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1405,7 +1405,15 @@ def adx(self, length=None, lensig=None, mamode=None, scalar=None, drift=None, of close = self._get_column(kwargs.pop("close", "close")) result = adx(high=high, low=low, close=close, length=length, lensig=lensig, mamode=mamode, scalar=scalar, drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) - + + def alphatrend(self, volume=None, src=None, common_period=None, multiplier=None, **kwargs): + open = self._get_column(kwargs.pop("open", "open")) + high = self._get_column(kwargs.pop("high", "high")) + low = self._get_column(kwargs.pop("low", "low")) + close = self._get_column(kwargs.pop("close", "close")) + result = alphatrend(open, high, low, close, volume=None, src=None, common_period=None, multiplier=None, **kwargs) + return self._post_process(result, **kwargs) + def amat(self, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = amat(close=close, fast=fast, slow=slow, mamode=mamode, lookback=lookback, offset=offset, **kwargs) @@ -1523,7 +1531,7 @@ def zigzag(self, pivot_leg=None, price_deviation=None, retrace=None, last_extrem low = self._get_column(kwargs.pop("low", "low")) result = zigzag(high, low, pivot_leg, price_deviation, retrace=False, last_extreme=False, **kwargs) return self._post_process(result, **kwargs) - + # Utility def above(self, asint=True, offset=None, **kwargs): a = self._get_column(kwargs.pop("close", "a")) diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py index 0d957d10..3c84fa5b 100644 --- a/pandas_ta/trend/__init__.py +++ b/pandas_ta/trend/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from .adx import adx +from .alphatrend import alphatrend from .amat import amat from .aroon import aroon from .chop import chop diff --git a/pandas_ta/trend/alphatrend.py b/pandas_ta/trend/alphatrend.py new file mode 100644 index 00000000..9faab685 --- /dev/null +++ b/pandas_ta/trend/alphatrend.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame, concat +from pandas_ta import true_range, sma, rsi, mfi +from pandas_ta.utils import get_offset, verify_series + + +def alphatrend(open, high, low, close, volume=None, src=None, common_period=None, multiplier=None, **kwargs): + """Indicator: Alpha Trend""" + open = verify_series(open) + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + + src_mapping = { + 'open': open, + 'high': high, + 'low': low, + 'close': close + } + src = src if src in src_mapping.keys() else 'close' + + common_period = int(common_period) if common_period is not None else 14 + multiplier = multiplier if (multiplier is not None and multiplier > 0) else 1 + + if volume is not None: + volume = verify_series(volume) + + if high is None or low is None or close is None: return + + def alpha_trend_search(series): + alpha_trend_ = [0] + for idx, val in enumerate(series): + if up50.iloc[idx]: + if alpha_trend_[idx] < val: + alpha_trend_.append(val) + else: + alpha_trend_.append(alpha_trend_[idx]) + else: + if alpha_trend_[idx] > val: + alpha_trend_.append(val) + else: + alpha_trend_.append(alpha_trend_[idx]) + + return alpha_trend_[1:] + + def momentum_filter(momentum_series): + df = DataFrame({'upt': upt, + 'downt': downt}) + df['up50'] = momentum_series >= 50 + df['upt-downt'] = concat([upt[momentum_series >= 50], downt[momentum_series < 50]]) + df[['alpha-trend-val']] = df[['upt-downt']].apply(alpha_trend_search) + + return df['alpha-trend-val'] + + tr = true_range(high, low, close) + atr = sma(tr, common_period) + + src = src_mapping.get(src, src) + + upt = (low - atr * multiplier).fillna(0) + + downt = (high + atr * multiplier).fillna(0) + + if volume is None: + rsi_ = rsi(src, common_period) + up50 = rsi_ >= 50 + alpha_trend_ = momentum_filter(rsi_) + + else: + mfi_ = mfi(high, low, close, volume, common_period) + up50 = mfi_ >= 50 + alpha_trend_ = momentum_filter(mfi_) + + alpha_ind = DataFrame({'k1': alpha_trend_, + 'k2': alpha_trend_.shift(2).fillna(0)}) + alpha_ind.index = src.index + + return alpha_ind + + +alphatrend.__doc__ = \ +""" Alpha Trend Inddicator + +In Magic Trend we had some problems, Alpha Trend tries to solve those problems such as: + +1-To minimize stop losses and overcome sideways market conditions. +2-To have more accurate BUY/SELL signals during trending market conditions. +3- To have significant support and resistance levels. +4- To bring together indicators from different categories that are compatible with each other and make a meaningful combination regarding momentum, trend, volatility, volume and trailing stop loss. + +Sources: + https://www.tradingview.com/script/o50NYLAZ-AlphaTrend/ + https://github.com/OnlyFibonacci/AlgoSeyri/blob/main/alphaTrendIndicator.py + + Default Inputs: + volume: None, if left blank, it will be None, otherwise it's a series. + multiplier: 1 which is the factor of trailing ATR value + common_period: 14 which is the length of ATR MFI and RSI + src: 'close' is default for source of ATRm MFI or RSI. However, `src` variable can also be to 'open', 'high' or 'low' + See Source links + +Args: + open (pd.series): series of 'open's + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + volume (pd.Series): Series of 'volume's. Default: None + src (str): can be 'open', 'high', 'low' or 'close'. Default: 'close' + multiplier (float): lfactor of trailing ATR value. Default: 1 + common_period (int): length of ATR MFI and RSI. Default: 14 + +Kwargs: + +Returns: + pd.DataFrame: index, k1, and k2 of all the input. +""" From 93ca029e66eeeaaecc88f6ef68a32e03e2696648 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Tue, 12 Sep 2023 11:38:32 -0700 Subject: [PATCH 3/7] MAINT bbands rename var --- pandas_ta/volatility/bbands.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas_ta/volatility/bbands.py b/pandas_ta/volatility/bbands.py index ff6338e5..d226dee7 100644 --- a/pandas_ta/volatility/bbands.py +++ b/pandas_ta/volatility/bbands.py @@ -66,10 +66,8 @@ def bbands( from talib import BBANDS upper, mid, lower = BBANDS(close, length, std, std, tal_ma(mamode)) else: - standard_deviation = stdev( - close=close, length=length, ddof=ddof, talib=mode_tal - ) - deviations = std * standard_deviation + std_dev = stdev(close=close, length=length, ddof=ddof, talib=mode_tal) + deviations = std * std_dev # deviations = std * standard_deviation.loc[standard_deviation.first_valid_index():,] mid = ma(mamode, close, length=length, talib=mode_tal, **kwargs) From 241430858ff57c25099d59f11c8a116b0d9848dc Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Tue, 12 Sep 2023 11:47:48 -0700 Subject: [PATCH 4/7] MAINT remove whitespace --- pandas_ta/trend/alphatrend.py | 29 +++++++++++++++-------------- pandas_ta/trend/zigzag.py | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pandas_ta/trend/alphatrend.py b/pandas_ta/trend/alphatrend.py index 9faab685..e96da22f 100644 --- a/pandas_ta/trend/alphatrend.py +++ b/pandas_ta/trend/alphatrend.py @@ -21,12 +21,12 @@ def alphatrend(open, high, low, close, volume=None, src=None, common_period=None common_period = int(common_period) if common_period is not None else 14 multiplier = multiplier if (multiplier is not None and multiplier > 0) else 1 - + if volume is not None: volume = verify_series(volume) if high is None or low is None or close is None: return - + def alpha_trend_search(series): alpha_trend_ = [0] for idx, val in enumerate(series): @@ -42,39 +42,40 @@ def alpha_trend_search(series): alpha_trend_.append(alpha_trend_[idx]) return alpha_trend_[1:] - + def momentum_filter(momentum_series): df = DataFrame({'upt': upt, 'downt': downt}) df['up50'] = momentum_series >= 50 df['upt-downt'] = concat([upt[momentum_series >= 50], downt[momentum_series < 50]]) df[['alpha-trend-val']] = df[['upt-downt']].apply(alpha_trend_search) - + return df['alpha-trend-val'] tr = true_range(high, low, close) atr = sma(tr, common_period) - + src = src_mapping.get(src, src) - + upt = (low - atr * multiplier).fillna(0) - downt = (high + atr * multiplier).fillna(0) - + if volume is None: rsi_ = rsi(src, common_period) up50 = rsi_ >= 50 alpha_trend_ = momentum_filter(rsi_) - + else: mfi_ = mfi(high, low, close, volume, common_period) up50 = mfi_ >= 50 alpha_trend_ = momentum_filter(mfi_) - - alpha_ind = DataFrame({'k1': alpha_trend_, - 'k2': alpha_trend_.shift(2).fillna(0)}) - alpha_ind.index = src.index - + + alpha_ind = DataFrame( + {"k1": alpha_trend_, + "k2": alpha_trend_.shift(2).fillna(0)}, + index = src.index + ) + return alpha_ind diff --git a/pandas_ta/trend/zigzag.py b/pandas_ta/trend/zigzag.py index 1ba9ee44..cde9e0b7 100644 --- a/pandas_ta/trend/zigzag.py +++ b/pandas_ta/trend/zigzag.py @@ -107,8 +107,8 @@ def swing_hl_merge(swings_sequential, swings_all): Calculation: ZigZag (high, low, pivot_leg, price_deviation, retrace=False, LastExtreme=False) - If % change > = X, plot ZigZag - + If % change > = X, plot ZigZag + Default Inputs: pivot_leg = 5, price_deviation = 10 retrace = FALSE, LastExtreme = TRUE From db75b7769dd1321704630b778b20a3f778d1b981 Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 12 Oct 2023 23:59:04 +0530 Subject: [PATCH 5/7] Added atr_mamode argument to supertrend atr_mamode will be passed on to ATR as mamode. Useful because many libraries/platforms use SMA for calculating ATR leading to different values for supertrend. --- pandas_ta/overlap/supertrend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas_ta/overlap/supertrend.py b/pandas_ta/overlap/supertrend.py index a99829ee..f868d19d 100644 --- a/pandas_ta/overlap/supertrend.py +++ b/pandas_ta/overlap/supertrend.py @@ -11,6 +11,7 @@ def supertrend( high: Series, low: Series, close: Series, length: Int = None, atr_length: Int = None, multiplier: IntFloat = None, + atr_mamode : str = None, offset: Int = None, **kwargs: DictLike ) -> DataFrame: """Supertrend (supertrend) @@ -31,6 +32,7 @@ def supertrend( variable of control. Default: length multiplier (float): Coefficient for upper and lower band distance to midrange. Default: 3.0 + atr_mamode (str) : Can be used to specify the type of MA to be used for ATR calculation. offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -60,7 +62,7 @@ def supertrend( long, short = [nan] * m, [nan] * m hl2_ = hl2(high, low) - matr = multiplier * atr(high, low, close, atr_length) + matr = multiplier * atr(high, low, close, atr_length, mamode=atr_mamode) lb = hl2_ - matr # Lowerband ub = hl2_ + matr # Upperband for i in range(1, m): From 2a173b46bc85aa7ea9010fdaa16aedcaf3c2fd53 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Thu, 12 Oct 2023 13:01:33 -0700 Subject: [PATCH 6/7] ENH #658 #694 alphatrend and zigzag stub MAINT refactoring --- README.md | 9 +- pandas_ta/candles/cdl_z.py | 5 +- pandas_ta/core.py | 27 +++ pandas_ta/maps.py | 7 +- pandas_ta/momentum/brar.py | 9 +- pandas_ta/momentum/dm.py | 13 +- pandas_ta/momentum/eri.py | 2 +- pandas_ta/momentum/fisher.py | 2 +- pandas_ta/momentum/kdj.py | 9 +- pandas_ta/momentum/kst.py | 8 +- pandas_ta/momentum/macd.py | 5 +- pandas_ta/momentum/ppo.py | 8 +- pandas_ta/momentum/qqe.py | 11 +- pandas_ta/momentum/rvgi.py | 3 +- pandas_ta/momentum/smi.py | 2 +- pandas_ta/momentum/squeeze.py | 4 +- pandas_ta/momentum/squeeze_pro.py | 4 +- pandas_ta/momentum/stc.py | 2 +- pandas_ta/momentum/stochf.py | 5 +- pandas_ta/momentum/stochrsi.py | 7 +- pandas_ta/momentum/td_seq.py | 2 +- pandas_ta/momentum/tsi.py | 3 +- pandas_ta/overlap/hilo.py | 3 +- pandas_ta/overlap/mama.py | 6 +- pandas_ta/overlap/supertrend.py | 7 +- pandas_ta/performance/drawdown.py | 2 +- pandas_ta/transform/cube.py | 3 +- pandas_ta/transform/ifisher.py | 2 +- pandas_ta/trend/__init__.py | 5 +- pandas_ta/trend/adx.py | 8 +- pandas_ta/trend/alphatrend.py | 251 ++++++++++++++---------- pandas_ta/trend/amat.py | 16 +- pandas_ta/trend/aroon.py | 10 +- pandas_ta/trend/chop.py | 2 +- pandas_ta/trend/cksp.py | 11 +- pandas_ta/trend/psar.py | 11 +- pandas_ta/trend/rwi.py | 2 +- pandas_ta/trend/ttm_trend.py | 3 +- pandas_ta/trend/vortex.py | 8 +- pandas_ta/trend/zigzag.py | 216 ++++++++------------ pandas_ta/volatility/aberration.py | 8 +- pandas_ta/volatility/accbands.py | 8 +- pandas_ta/volatility/bbands.py | 26 +-- pandas_ta/volatility/chandelier_exit.py | 5 +- pandas_ta/volatility/donchian.py | 8 +- pandas_ta/volatility/hwc.py | 2 +- pandas_ta/volatility/kc.py | 8 +- pandas_ta/volatility/thermo.py | 2 +- pandas_ta/volume/aobv.py | 13 +- pandas_ta/volume/pvo.py | 2 +- pandas_ta/volume/wb_tsv.py | 2 +- setup.py | 2 +- tests/test_indicator_trend.py | 12 +- tests/test_studies.py | 6 +- 54 files changed, 436 insertions(+), 381 deletions(-) diff --git a/README.md b/README.md index 0855d5e5..cc261fdd 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is a free, Open Source, and easy to * [Performance](#performance-3) * [Statistics](#statistics-11) * [Transform](#transform-3) - * [Trend](#trend-20) + * [Trend](#trend-21) * [Utility](#utility-5) * [Volatility](#volatility-16) * [Volume](#volume-20) @@ -197,7 +197,7 @@ $ pip install pandas_ta[full] Development Version ------------------- -The _development_ version, _0.4.2b_, includes _numerous_ bug fixes, speed improvements and better documentation since release, _0.3.14b_. +The _development_ version, _0.4.3b_, includes _numerous_ bug fixes, speed improvements and better documentation since release, _0.3.14b_. ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta.git@development ``` @@ -292,7 +292,7 @@ Contributions, feedback, and bug squashing are integral to the success of this l _Thank you for your contributions!_ - +
@@ -1007,10 +1007,11 @@ Back to [Contents](#contents)
-### **Trend** (20) +### **Trend** (21) * _Average Directional Movement Index_: **adx** * Also includes **dmp** and **dmn** in the resultant DataFrame. +* _Alpha Trend_: **alphatrend** * _Archer Moving Averages Trends_: **amat** * _Aroon & Aroon Oscillator_: **aroon** * _Choppiness Index_: **chop** diff --git a/pandas_ta/candles/cdl_z.py b/pandas_ta/candles/cdl_z.py index b9e79e90..96dc10ca 100644 --- a/pandas_ta/candles/cdl_z.py +++ b/pandas_ta/candles/cdl_z.py @@ -60,12 +60,13 @@ def cdl_z( _full = "a" if full else "" _props = _full if full else f"_{length}_{ddof}" - df = DataFrame({ + data = { f"open_Z{_props}": z_open, f"high_Z{_props}": z_high, f"low_Z{_props}": z_low, f"close_Z{_props}": z_close, - }) + } + df = DataFrame(data, index=close.index) if full: df.fillna(method="backfill", axis=0, inplace=True) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 21ffda3a..7eea4057 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1441,6 +1441,21 @@ def adx(self, length=None, lensig=None, mamode=None, scalar=None, drift=None, of drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def alphatrend(self, volume=None, src=None, length=None, multiplier=None, threshold=None, lag=None, mamode=None, talib=None, offset=None, **kwargs: DictLike): + open_ = self._get_column(kwargs.pop("open", "open")) + high = self._get_column(kwargs.pop("high", "high")) + low = self._get_column(kwargs.pop("low", "low")) + close = self._get_column(kwargs.pop("close", "close")) + if volume is not None: + volume = self._get_column(kwargs.pop("volume", "volume")) + result = alphatrend( + open_=open_, high=high, low=low, close=close, volume=volume, + src=src, length=length, multiplier=multiplier, + threshold=threshold, lag=lag, mamode=mamode, + talib=talib, offset=offset, **kwargs + ) + return self._post_process(result, **kwargs) + def amat(self, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) result = amat(close=close, fast=fast, slow=slow, mamode=mamode, lookback=lookback, offset=offset, **kwargs) @@ -1569,6 +1584,18 @@ def xsignals(self, signal=None, xa=None, xb=None, above=None, long=None, asbool= trend_offset=trend_offset, trend_reset=trend_reset, offset=offset, **kwargs) return self._post_process(result, **kwargs) + # def zigzag(self, close=None, pivot_leg=None, price_deviation=None, retrace=None, last_extreme=None, offset=None, **kwargs: DictLike): + # high = self._get_column(kwargs.pop("high", "high")) + # low = self._get_column(kwargs.pop("low", "low")) + # if close is not None: + # close = self._get_column(kwargs.pop("close", "close")) + # result = zigzag( + # high=high, low=low, close=close, + # pivot_leg=pivot_leg, price_deviation=price_deviation, + # retrace=retrace, last_extreme=last_extreme, + # offset=offset, **kwargs) + # return self._post_process(result, **kwargs) + # Volatility def aberration(self, length=None, atr_length=None, offset=None, **kwargs: DictLike): high = self._get_column(kwargs.pop("high", "high")) diff --git a/pandas_ta/maps.py b/pandas_ta/maps.py index 406fa0b0..9c94cc0e 100644 --- a/pandas_ta/maps.py +++ b/pandas_ta/maps.py @@ -73,9 +73,10 @@ "transform": ["cube", "ifisher", "remap"], # Trend "trend": [ - "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", - "increasing", "long_run", "psar", "qstick", "rwi", "short_run", - "trendflex", "tsignals", "ttm_trend", "vhf", "vortex", "xsignals" + "adx", "alphatrend", "amat", "aroon", "chop", "cksp", "decay", + "decreasing", "dpo", "increasing", "long_run", "psar", "qstick", + "rwi", "short_run", "trendflex", "tsignals", "ttm_trend", "vhf", + "vortex", "xsignals" ], # Volatility "volatility": [ diff --git a/pandas_ta/momentum/brar.py b/pandas_ta/momentum/brar.py index bc1afa87..72b7c35a 100644 --- a/pandas_ta/momentum/brar.py +++ b/pandas_ta/momentum/brar.py @@ -89,8 +89,9 @@ def brar( br.name = f"BR{_props}" ar.category = br.category = "momentum" - brardf = DataFrame({ar.name: ar, br.name: br}) - brardf.name = f"BRAR{_props}" - brardf.category = "momentum" + data = {ar.name: ar, br.name: br} + df = DataFrame(data, index=close.index) + df.name = f"BRAR{_props}" + df.category = "momentum" - return brardf + return df diff --git a/pandas_ta/momentum/dm.py b/pandas_ta/momentum/dm.py index dcbefa8f..3acad40a 100644 --- a/pandas_ta/momentum/dm.py +++ b/pandas_ta/momentum/dm.py @@ -90,11 +90,10 @@ def dm( neg.fillna(method=kwargs["fill_method"], inplace=True) # Name and Category - _params = f"_{length}" - data = {f"DMP{_params}": pos, f"DMN{_params}": neg, } + _props = f"_{length}" + data = {f"DMP{_props}": pos, f"DMN{_props}": neg} + df = DataFrame(data, index=high.index) + df.name = f"DM{_props}" + df.category = "momentum" - dmdf = DataFrame(data) - dmdf.name = f"DM{_params}" - dmdf.category = "momentum" - - return dmdf + return df diff --git a/pandas_ta/momentum/eri.py b/pandas_ta/momentum/eri.py index 3c98783f..6ad413c3 100644 --- a/pandas_ta/momentum/eri.py +++ b/pandas_ta/momentum/eri.py @@ -72,7 +72,7 @@ def eri( bull.category = bear.category = "momentum" data = {bull.name: bull, bear.name: bear} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"ERI_{length}" df.category = bull.category diff --git a/pandas_ta/momentum/fisher.py b/pandas_ta/momentum/fisher.py index 7b5a37f5..ae961067 100644 --- a/pandas_ta/momentum/fisher.py +++ b/pandas_ta/momentum/fisher.py @@ -92,7 +92,7 @@ def fisher( fisher.category = signalma.category = "momentum" data = {fisher.name: fisher, signalma.name: signalma} - df = DataFrame(data) + df = DataFrame(data, index=high.index) df.name = f"FISHERT{_props}" df.category = fisher.category diff --git a/pandas_ta/momentum/kdj.py b/pandas_ta/momentum/kdj.py index 1fc4412f..0fbf6883 100644 --- a/pandas_ta/momentum/kdj.py +++ b/pandas_ta/momentum/kdj.py @@ -89,8 +89,9 @@ def kdj( j.name = f"J{_params}" k.category = d.category = j.category = "momentum" - kdjdf = DataFrame({k.name: k, d.name: d, j.name: j}) - kdjdf.name = f"KDJ{_params}" - kdjdf.category = "momentum" + data = {k.name: k, d.name: d, j.name: j} + df = DataFrame(data, index=close.index) + df.name = f"KDJ{_params}" + df.category = "momentum" - return kdjdf + return df diff --git a/pandas_ta/momentum/kst.py b/pandas_ta/momentum/kst.py index 4fb1f854..ef2e7927 100644 --- a/pandas_ta/momentum/kst.py +++ b/pandas_ta/momentum/kst.py @@ -92,8 +92,8 @@ def kst( kst.category = kst_signal.category = "momentum" data = {kst.name: kst, kst_signal.name: kst_signal} - kstdf = DataFrame(data) - kstdf.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}_{signal}" - kstdf.category = "momentum" + df = DataFrame(data, index=close.index) + df.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}_{signal}" + df.category = "momentum" - return kstdf + return df diff --git a/pandas_ta/momentum/macd.py b/pandas_ta/momentum/macd.py index 2474447e..d04ddb49 100644 --- a/pandas_ta/momentum/macd.py +++ b/pandas_ta/momentum/macd.py @@ -108,8 +108,9 @@ def macd( data = { macd.name: macd, histogram.name: histogram, - signalma.name: signalma} - df = DataFrame(data) + signalma.name: signalma + } + df = DataFrame(data, index=close.index) df.name = f"MACD{_asmode}{_props}" df.category = macd.category diff --git a/pandas_ta/momentum/ppo.py b/pandas_ta/momentum/ppo.py index 482e2df2..160eef75 100644 --- a/pandas_ta/momentum/ppo.py +++ b/pandas_ta/momentum/ppo.py @@ -100,8 +100,12 @@ def ppo( signalma.name = f"PPOs{_props}" ppo.category = histogram.category = signalma.category = "momentum" - data = {ppo.name: ppo, histogram.name: histogram, signalma.name: signalma} - df = DataFrame(data) + data = { + ppo.name: ppo, + histogram.name: histogram, + signalma.name: signalma + } + df = DataFrame(data, index=close.index) df.name = f"PPO{_props}" df.category = ppo.category diff --git a/pandas_ta/momentum/qqe.py b/pandas_ta/momentum/qqe.py index 3cf5a435..bd1413f5 100644 --- a/pandas_ta/momentum/qqe.py +++ b/pandas_ta/momentum/qqe.py @@ -165,11 +165,14 @@ def qqe( qqe_long.category = qqe_short.category = qqe.category data = { - qqe.name: qqe, rsi_ma.name: rsi_ma, - # long.name: long, short.name: short - qqe_long.name: qqe_long, qqe_short.name: qqe_short + qqe.name: qqe, + rsi_ma.name: rsi_ma, + # long.name: long, + # short.name: short + qqe_long.name: qqe_long, + qqe_short.name: qqe_short } - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"QQE{_props}" df.category = qqe.category diff --git a/pandas_ta/momentum/rvgi.py b/pandas_ta/momentum/rvgi.py index 7dc3cb3a..ded6ad52 100644 --- a/pandas_ta/momentum/rvgi.py +++ b/pandas_ta/momentum/rvgi.py @@ -84,7 +84,8 @@ def rvgi( signal.name = f"RVGIs_{length}_{swma_length}" rvgi.category = signal.category = "momentum" - df = DataFrame({rvgi.name: rvgi, signal.name: signal}) + data = {rvgi.name: rvgi, signal.name: signal} + df = DataFrame(data, index=close.index) df.name = f"RVGI_{length}_{swma_length}" df.category = rvgi.category diff --git a/pandas_ta/momentum/smi.py b/pandas_ta/momentum/smi.py index fd354a71..04d4f454 100644 --- a/pandas_ta/momentum/smi.py +++ b/pandas_ta/momentum/smi.py @@ -93,7 +93,7 @@ def smi( smi.category = signalma.category = osc.category = "momentum" data = {smi.name: smi, signalma.name: signalma, osc.name: osc} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"SMI{_props}" df.category = smi.category diff --git a/pandas_ta/momentum/squeeze.py b/pandas_ta/momentum/squeeze.py index fdd54832..6e7e0e9d 100644 --- a/pandas_ta/momentum/squeeze.py +++ b/pandas_ta/momentum/squeeze.py @@ -166,9 +166,9 @@ def squeeze( squeeze.name: squeeze, f"SQZ_ON": squeeze_on, f"SQZ_OFF": squeeze_off, - f"SQZ_NO": no_squeeze, + f"SQZ_NO": no_squeeze } - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = squeeze.name df.category = squeeze.category = "momentum" diff --git a/pandas_ta/momentum/squeeze_pro.py b/pandas_ta/momentum/squeeze_pro.py index 5abd34f2..157f5494 100644 --- a/pandas_ta/momentum/squeeze_pro.py +++ b/pandas_ta/momentum/squeeze_pro.py @@ -190,9 +190,9 @@ def squeeze_pro( f"SQZPRO_ON_NORMAL": squeeze_on_normal, f"SQZPRO_ON_NARROW": squeeze_on_narrow, f"SQZPRO_OFF": squeeze_off_wide, - f"SQZPRO_NO": no_squeeze, + f"SQZPRO_NO": no_squeeze } - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = squeeze.name df.category = squeeze.category = "momentum" diff --git a/pandas_ta/momentum/stc.py b/pandas_ta/momentum/stc.py index e95d3755..77579382 100644 --- a/pandas_ta/momentum/stc.py +++ b/pandas_ta/momentum/stc.py @@ -147,7 +147,7 @@ def stc( macd.name: macd, stoch.name: stoch } - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"STC{_props}" df.category = stc.category diff --git a/pandas_ta/momentum/stochf.py b/pandas_ta/momentum/stochf.py index b9aa12b9..0ee9e705 100644 --- a/pandas_ta/momentum/stochf.py +++ b/pandas_ta/momentum/stochf.py @@ -97,10 +97,7 @@ def stochf( stochf_d.name = f"{_name}d{_props}" stochf_k.category = stochf_d.category = "momentum" - data = { - stochf_k.name: stochf_k, - stochf_d.name: stochf_d - } + data = {stochf_k.name: stochf_k, stochf_d.name: stochf_d} df = DataFrame(data, index=close.index) df.name = f"{_name}{_props}" df.category = stochf_k.category diff --git a/pandas_ta/momentum/stochrsi.py b/pandas_ta/momentum/stochrsi.py index 16f7e667..0cd0ae15 100644 --- a/pandas_ta/momentum/stochrsi.py +++ b/pandas_ta/momentum/stochrsi.py @@ -93,11 +93,8 @@ def stochrsi( stochrsi_d.name = f"{_name}d{_props}" stochrsi_k.category = stochrsi_d.category = "momentum" - data = { - stochrsi_k.name: stochrsi_k, - stochrsi_d.name: stochrsi_d - } - df = DataFrame(data) + data = {stochrsi_k.name: stochrsi_k, stochrsi_d.name: stochrsi_d} + df = DataFrame(data, index=close.index) df.name = f"{_name}{_props}" df.category = stochrsi_k.category diff --git a/pandas_ta/momentum/td_seq.py b/pandas_ta/momentum/td_seq.py index 5b77bfd0..444e5149 100644 --- a/pandas_ta/momentum/td_seq.py +++ b/pandas_ta/momentum/td_seq.py @@ -72,7 +72,7 @@ def td_seq( up_seq.category = down_seq.category = "momentum" data = {up_seq.name: up_seq, down_seq.name: down_seq} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.index = close.index # Only works here for some reason? df.name = "TD_SEQ" df.category = up_seq.category diff --git a/pandas_ta/momentum/tsi.py b/pandas_ta/momentum/tsi.py index 9ace9389..cc44a3e7 100644 --- a/pandas_ta/momentum/tsi.py +++ b/pandas_ta/momentum/tsi.py @@ -103,7 +103,8 @@ def tsi( tsi_signal.name = f"TSIs_{fast}_{slow}_{signal}" tsi.category = tsi_signal.category = "momentum" - df = DataFrame({tsi.name: tsi, tsi_signal.name: tsi_signal}) + data = {tsi.name: tsi, tsi_signal.name: tsi_signal} + df = DataFrame(data, index=close.index) df.name = f"TSI_{fast}_{slow}_{signal}" df.category = "momentum" diff --git a/pandas_ta/overlap/hilo.py b/pandas_ta/overlap/hilo.py index a10dc267..b4374ad8 100644 --- a/pandas_ta/overlap/hilo.py +++ b/pandas_ta/overlap/hilo.py @@ -99,7 +99,8 @@ def hilo( data = { f"HILO{_props}": hilo, f"HILOl{_props}": long, - f"HILOs{_props}": short} + f"HILOs{_props}": short + } df = DataFrame(data, index=close.index) df.name = f"HILO{_props}" diff --git a/pandas_ta/overlap/mama.py b/pandas_ta/overlap/mama.py index b2aea7d4..f7fbb154 100644 --- a/pandas_ta/overlap/mama.py +++ b/pandas_ta/overlap/mama.py @@ -156,10 +156,8 @@ def mama( # Name and Category _props = f"_{fastlimit}_{slowlimit}" - df = DataFrame({ - f"MAMA{_props}": mama, - f"FAMA{_props}": fama, - }, index=close.index) + data = {f"MAMA{_props}": mama, f"FAMA{_props}": fama} + df = DataFrame(data, index=close.index) df.name = f"MAMA{_props}" df.category = "overlap" diff --git a/pandas_ta/overlap/supertrend.py b/pandas_ta/overlap/supertrend.py index a99829ee..a860cfdb 100644 --- a/pandas_ta/overlap/supertrend.py +++ b/pandas_ta/overlap/supertrend.py @@ -84,12 +84,13 @@ def supertrend( dir_[:length] = [nan] * length _props = f"_{length}_{multiplier}" - df = DataFrame({ + data = { f"SUPERT{_props}": trend, f"SUPERTd{_props}": dir_, f"SUPERTl{_props}": long, - f"SUPERTs{_props}": short, - }, index=close.index) + f"SUPERTs{_props}": short + } + df = DataFrame(data, index=close.index) df.name = f"SUPERT{_props}" df.category = "overlap" diff --git a/pandas_ta/performance/drawdown.py b/pandas_ta/performance/drawdown.py index a23174f8..e33a80e9 100644 --- a/pandas_ta/performance/drawdown.py +++ b/pandas_ta/performance/drawdown.py @@ -65,7 +65,7 @@ def drawdown( dd.category = dd_pct.category = dd_log.category = "performance" data = {dd.name: dd, dd_pct.name: dd_pct, dd_log.name: dd_log} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = dd.name df.category = dd.category diff --git a/pandas_ta/transform/cube.py b/pandas_ta/transform/cube.py index 406178d8..b7a7c7e7 100644 --- a/pandas_ta/transform/cube.py +++ b/pandas_ta/transform/cube.py @@ -75,7 +75,8 @@ def cube( ct_signal.name = f"CUBEs{_props}" ct.category = ct_signal.category = "transform" - df = DataFrame({ct.name: ct, ct_signal.name: ct_signal}) + data = {ct.name: ct, ct_signal.name: ct_signal} + df = DataFrame(data, index=close.index) df.name = f"CUBE{_props}" df.category = ct.category diff --git a/pandas_ta/transform/ifisher.py b/pandas_ta/transform/ifisher.py index 30a51610..3e752ff4 100644 --- a/pandas_ta/transform/ifisher.py +++ b/pandas_ta/transform/ifisher.py @@ -92,7 +92,7 @@ def ifisher( inv_fisher.category = signal.category = "transform" data = {inv_fisher.name: inv_fisher, signal.name: signal} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"INVFISHER{_props}" df.category = inv_fisher.category diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py index 91c392c4..7c53fbe1 100644 --- a/pandas_ta/trend/__init__.py +++ b/pandas_ta/trend/__init__.py @@ -20,10 +20,10 @@ from .vhf import vhf from .vortex import vortex from .xsignals import xsignals -from .zigzag import zigzag __all__ = [ "adx", + "alphatrend", "amat", "aroon", "chop", @@ -42,6 +42,5 @@ "ttm_trend", "vhf", "vortex", - "xsignals", - "zigzag" + "xsignals" ] diff --git a/pandas_ta/trend/adx.py b/pandas_ta/trend/adx.py index e0b86ca0..9ed832a6 100644 --- a/pandas_ta/trend/adx.py +++ b/pandas_ta/trend/adx.py @@ -157,8 +157,8 @@ def adx( adx.category = dmp.category = dmn.category = "trend" data = {adx.name: adx, dmp.name: dmp, dmn.name: dmn} - adxdf = DataFrame(data) - adxdf.name = f"ADX_{lensig}" - adxdf.category = "trend" + df = DataFrame(data, index=close.index) + df.name = f"ADX_{lensig}" + df.category = "trend" - return adxdf + return df diff --git a/pandas_ta/trend/alphatrend.py b/pandas_ta/trend/alphatrend.py index e96da22f..9eac8f4d 100644 --- a/pandas_ta/trend/alphatrend.py +++ b/pandas_ta/trend/alphatrend.py @@ -1,117 +1,164 @@ # -*- coding: utf-8 -*- -from pandas import DataFrame, concat -from pandas_ta import true_range, sma, rsi, mfi -from pandas_ta.utils import get_offset, verify_series +from numpy import isnan, nan, zeros +from numba import njit +from pandas import DataFrame, Series +from pandas_ta._typing import Array, DictLike, Int, IntFloat +from pandas_ta.momentum import rsi +from pandas_ta.volatility import atr +from pandas_ta.volume.mfi import mfi +from pandas_ta.utils import ( + v_mamode, + v_offset, + v_pos_default, + v_series, + v_str, + v_talib +) + + +@njit(cache=True) +def np_alpha(low_atr: Array, high_atr: Array, momo_threshold: Array): + """Alphatrend alpha threshold calculation""" + m = momo_threshold.size + alpha = zeros(m) + + for i in range(1, m): + if momo_threshold[i]: + if low_atr[i] < alpha[i - 1]: + alpha[i] = alpha[i - 1] + else: + alpha[i] = low_atr[i] + else: + if high_atr[i] > alpha[i - 1]: + alpha[i] = alpha[i - 1] + else: + alpha[i] = high_atr[i] + alpha[0] = nan + + return alpha + + +def alphatrend( + open_: Series, high: Series, low: Series, close: Series, + volume: Series = None, src: str = None, + length: int = None, multiplier: IntFloat = None, + threshold: IntFloat = None, lag: Int = None, + mamode: str = None, talib: bool = None, + offset: Int = None, **kwargs: DictLike +): + """ Alpha Trend (alphatrend) + + Alpha Trend attemps to solve the problems of Magic Trend. For instance, it + tries to ilter out sideways market conditions and yield more accurate + BUY/SELL signals + + Sources: + https://www.tradingview.com/script/o50NYLAZ-AlphaTrend/ + https://github.com/OnlyFibonacci/AlgoSeyri/blob/main/alphaTrendIndicator.py + + Args: + open (pd.series): series of 'open's + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + volume (pd.Series): Series of 'volume's. Default: None + src (str): One of 'open', 'high', 'low' or 'close'. Default: 'close' + length (int): Length for ATR, MFI, or RSI. Default: 14 + multiplier (float): Trailing ATR value. Default: 1 + threshold (float): Momentum threshold. Default: 50 + lag (int): Lag period of main trend. Default: 2 + offset (int): How many periods to offset the result. Default: 0 + + Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + + Returns: + pd.DataFrame: trend, trendlag of all the input. + """ + # Validate + length = v_pos_default(length, 14) + open_ = v_series(open_, length) + high = v_series(high, length) + low = v_series(low, length) + close = v_series(close, length) + + if open_ is None or high is None or low is None or close is None: + return + + _src = {"open": open_, "high": high, "low": low, "close": close} + src = v_str(src, "close") + src = src if src in _src.keys() else "close" + + multiplier = v_pos_default(multiplier, 1) + threshold = v_pos_default(threshold, 50) + lag = v_pos_default(lag, 2) + + mamode = v_mamode(mamode, "sma") + mode_tal = v_talib(talib) + offset = v_offset(offset) + if volume is not None: + volume = v_series(volume) + if volume is None: + return + + # Calculate + atr_ = atr( + high=high, low=low, close=close, length=length, + mamode=mamode, talib=mode_tal + ) -def alphatrend(open, high, low, close, volume=None, src=None, common_period=None, multiplier=None, **kwargs): - """Indicator: Alpha Trend""" - open = verify_series(open) - high = verify_series(high) - low = verify_series(low) - close = verify_series(close) + if atr_ is None or all(isnan(atr_)): + return - src_mapping = { - 'open': open, - 'high': high, - 'low': low, - 'close': close - } - src = src if src in src_mapping.keys() else 'close' + lower_atr = low - atr_ * multiplier + upper_atr = high + atr_ * multiplier - common_period = int(common_period) if common_period is not None else 14 - multiplier = multiplier if (multiplier is not None and multiplier > 0) else 1 + momo = None + if volume is None: + momo = rsi(close=_src[src], length=length, mamode=mamode, talib=mode_tal) + else: + momo = mfi( + high=high, low=low, close=close, volume=volume, + length=length, talib=mode_tal + ) - if volume is not None: - volume = verify_series(volume) - - if high is None or low is None or close is None: return - - def alpha_trend_search(series): - alpha_trend_ = [0] - for idx, val in enumerate(series): - if up50.iloc[idx]: - if alpha_trend_[idx] < val: - alpha_trend_.append(val) - else: - alpha_trend_.append(alpha_trend_[idx]) - else: - if alpha_trend_[idx] > val: - alpha_trend_.append(val) - else: - alpha_trend_.append(alpha_trend_[idx]) + if momo is None: + return - return alpha_trend_[1:] + np_upper_atr, np_lower_atr = upper_atr.values, lower_atr.values - def momentum_filter(momentum_series): - df = DataFrame({'upt': upt, - 'downt': downt}) - df['up50'] = momentum_series >= 50 - df['upt-downt'] = concat([upt[momentum_series >= 50], downt[momentum_series < 50]]) - df[['alpha-trend-val']] = df[['upt-downt']].apply(alpha_trend_search) + at = np_alpha(np_lower_atr, np_upper_atr, momo.values >= threshold) + at = Series(at, index=close.index) - return df['alpha-trend-val'] + atl = at.shift(lag) - tr = true_range(high, low, close) - atr = sma(tr, common_period) + if all(isnan(at)) or all(isnan(atl)): + return # Emergency Break - src = src_mapping.get(src, src) + # Offset + if offset != 0: + at = at.shift(offset) + atl = atl.shift(offset) - upt = (low - atr * multiplier).fillna(0) - downt = (high + atr * multiplier).fillna(0) + # Fill + if "fillna" in kwargs: + at.fillna(kwargs["fillna"], inplace=True) + atl.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + at.fillna(method=kwargs["fill_method"], inplace=True) + atl.fillna(method=kwargs["fill_method"], inplace=True) - if volume is None: - rsi_ = rsi(src, common_period) - up50 = rsi_ >= 50 - alpha_trend_ = momentum_filter(rsi_) + # Name and Category + _props = f"_{length}_{multiplier}_{threshold}" + at.name = f"ALPHAT{_props}" + atl.name = f"ALPHATl{_props}_{lag}" + at.category = atl.category = "trend" - else: - mfi_ = mfi(high, low, close, volume, common_period) - up50 = mfi_ >= 50 - alpha_trend_ = momentum_filter(mfi_) - - alpha_ind = DataFrame( - {"k1": alpha_trend_, - "k2": alpha_trend_.shift(2).fillna(0)}, - index = src.index - ) + data = {at.name: at, atl.name: atl} + df = DataFrame(data, index=close.index) + df.name = at.name + df.category = at.category - return alpha_ind - - -alphatrend.__doc__ = \ -""" Alpha Trend Inddicator - -In Magic Trend we had some problems, Alpha Trend tries to solve those problems such as: - -1-To minimize stop losses and overcome sideways market conditions. -2-To have more accurate BUY/SELL signals during trending market conditions. -3- To have significant support and resistance levels. -4- To bring together indicators from different categories that are compatible with each other and make a meaningful combination regarding momentum, trend, volatility, volume and trailing stop loss. - -Sources: - https://www.tradingview.com/script/o50NYLAZ-AlphaTrend/ - https://github.com/OnlyFibonacci/AlgoSeyri/blob/main/alphaTrendIndicator.py - - Default Inputs: - volume: None, if left blank, it will be None, otherwise it's a series. - multiplier: 1 which is the factor of trailing ATR value - common_period: 14 which is the length of ATR MFI and RSI - src: 'close' is default for source of ATRm MFI or RSI. However, `src` variable can also be to 'open', 'high' or 'low' - See Source links - -Args: - open (pd.series): series of 'open's - high (pd.Series): Series of 'high's - low (pd.Series): Series of 'low's - close (pd.Series): Series of 'close's - volume (pd.Series): Series of 'volume's. Default: None - src (str): can be 'open', 'high', 'low' or 'close'. Default: 'close' - multiplier (float): lfactor of trailing ATR value. Default: 1 - common_period (int): length of ATR MFI and RSI. Default: 14 - -Kwargs: - -Returns: - pd.DataFrame: index, k1, and k2 of all the input. -""" + return df diff --git a/pandas_ta/trend/amat.py b/pandas_ta/trend/amat.py index 92a27593..6d1cd833 100644 --- a/pandas_ta/trend/amat.py +++ b/pandas_ta/trend/amat.py @@ -74,13 +74,15 @@ def amat( mas_long.fillna(method=kwargs["fill_method"], inplace=True) mas_short.fillna(method=kwargs["fill_method"], inplace=True) - amatdf = DataFrame({ - f"AMAT{mamode[0]}_LR_{fast}_{slow}_{lookback}": mas_long, - f"AMAT{mamode[0]}_SR_{fast}_{slow}_{lookback}": mas_short - }) + _props = f"_{fast}_{slow}_{lookback}" + data = { + f"AMAT{mamode[0]}_LR{_props}": mas_long, + f"AMAT{mamode[0]}_SR{_props}": mas_short + } + df = DataFrame(data, index=close.index) # Name and Category - amatdf.name = f"AMAT{mamode[0]}_{fast}_{slow}_{lookback}" - amatdf.category = "trend" + df.name = f"AMAT{mamode[0]}{_props}" + df.category = "trend" - return amatdf + return df diff --git a/pandas_ta/trend/aroon.py b/pandas_ta/trend/aroon.py index ffcb0287..21888857 100644 --- a/pandas_ta/trend/aroon.py +++ b/pandas_ta/trend/aroon.py @@ -96,10 +96,10 @@ def aroon( data = { aroon_down.name: aroon_down, aroon_up.name: aroon_up, - aroon_osc.name: aroon_osc, + aroon_osc.name: aroon_osc } - aroondf = DataFrame(data) - aroondf.name = f"AROON_{length}" - aroondf.category = aroon_down.category + df = DataFrame(data, index=high.index) + df.name = f"AROON_{length}" + df.category = aroon_down.category - return aroondf + return df diff --git a/pandas_ta/trend/chop.py b/pandas_ta/trend/chop.py index 07c04d85..d7e17f66 100644 --- a/pandas_ta/trend/chop.py +++ b/pandas_ta/trend/chop.py @@ -37,7 +37,7 @@ def chop( close (pd.Series): Series of 'close's length (int): It's period. Default: 14 atr_length (int): Length for ATR. Default: 1 - ln (bool): If True, uses ln otherwise log10. Default: False + ln (bool): When True, it uses 'ln' instead of 'log10'. Default: False scalar (float): How much to magnify. Default: 100 drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/trend/cksp.py b/pandas_ta/trend/cksp.py index a5b3ee51..f6bf191c 100644 --- a/pandas_ta/trend/cksp.py +++ b/pandas_ta/trend/cksp.py @@ -101,10 +101,9 @@ def cksp( short_stop.name = f"CKSPs{_props}" long_stop.category = short_stop.category = "trend" - ckspdf = DataFrame({ - long_stop.name: long_stop, short_stop.name: short_stop - }) - ckspdf.name = f"CKSP{_props}" - ckspdf.category = long_stop.category + data = {long_stop.name: long_stop, short_stop.name: short_stop} + df = DataFrame(data, index=close.index) + df.name = f"CKSP{_props}" + df.category = long_stop.category - return ckspdf + return df diff --git a/pandas_ta/trend/psar.py b/pandas_ta/trend/psar.py index afd5c31c..ddde31dd 100644 --- a/pandas_ta/trend/psar.py +++ b/pandas_ta/trend/psar.py @@ -150,13 +150,14 @@ def psar( f"PSARl{_params}": long, f"PSARs{_params}": short, f"PSARaf{_params}": _af, - f"PSARr{_params}": reversal, + f"PSARr{_params}": reversal } - psardf = DataFrame(data) - psardf.name = f"PSAR{_params}" - psardf.category = long.category = short.category = "trend" + df = DataFrame(data, index=high.index) + df.name = f"PSAR{_params}" + df.category = long.category = short.category = "trend" + + return df - return psardf def _falling(high, low, drift: int = 1): """Returns the last -DM value""" diff --git a/pandas_ta/trend/rwi.py b/pandas_ta/trend/rwi.py index ac4650ee..a686328e 100644 --- a/pandas_ta/trend/rwi.py +++ b/pandas_ta/trend/rwi.py @@ -89,7 +89,7 @@ def rwi( # Prepare DataFrame to return data = {rwi_high.name: rwi_high, rwi_low.name: rwi_low} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"RWI_{length}" df.category = "trend" diff --git a/pandas_ta/trend/ttm_trend.py b/pandas_ta/trend/ttm_trend.py index bf7f5099..7e4f0468 100644 --- a/pandas_ta/trend/ttm_trend.py +++ b/pandas_ta/trend/ttm_trend.py @@ -69,8 +69,7 @@ def ttm_trend( tm_trend.name = f"TTM_TRND_{length}" tm_trend.category = "momentum" - data = {tm_trend.name: tm_trend} - df = DataFrame(data) + df = DataFrame({tm_trend.name: tm_trend}, index=close.index) df.name = f"TTMTREND_{length}" df.category = tm_trend.category diff --git a/pandas_ta/trend/vortex.py b/pandas_ta/trend/vortex.py index 1c74c3e7..f63ccb80 100644 --- a/pandas_ta/trend/vortex.py +++ b/pandas_ta/trend/vortex.py @@ -78,8 +78,8 @@ def vortex( vip.category = vim.category = "trend" data = {vip.name: vip, vim.name: vim} - vtxdf = DataFrame(data) - vtxdf.name = f"VTX_{length}" - vtxdf.category = "trend" + df = DataFrame(data, index=close.index) + df.name = f"VTX_{length}" + df.category = "trend" - return vtxdf + return df diff --git a/pandas_ta/trend/zigzag.py b/pandas_ta/trend/zigzag.py index cde9e0b7..eb6a4176 100644 --- a/pandas_ta/trend/zigzag.py +++ b/pandas_ta/trend/zigzag.py @@ -1,130 +1,86 @@ -from pandas import DataFrame, concat, merge - - -def zigzag(high, low, pivot_leg=5, price_deviation=10, retrace=False, last_extreme=False, **kwargs): - def find_extremum(rolling, pivot_leg): - extremum = rolling[( - (rolling.diff() == 0).cumsum() - (rolling.diff() == 0).cumsum().shift( - pivot_leg - 1) == (pivot_leg - 1)).shift(-int(pivot_leg / 2)).fillna(False)] - return extremum - - def calculate_swings(high, low, pivot_leg): - rolling_high = high.rolling(window=pivot_leg, center=True, min_periods=0).max() - rolling_low = low.rolling(window=pivot_leg, center=True, min_periods=0).min() - - high_swings = find_extremum(rolling_high, pivot_leg) - low_swings = find_extremum(rolling_low, pivot_leg) - - highs = DataFrame({'swing_value': high_swings, 'swing_type': 'high'}) - lows = DataFrame({'swing_value': low_swings, 'swing_type': 'low'}) - swings_high_low = concat([highs, lows]).sort_index() - return swings_high_low - - def price_dev(swings, price_deviation): - """ - This function should get swings data and will return the swings, - which are below the price deviation threshold. - This data may have some high/low swings together and sequential which is not desired. - These bad data will be handled in other functions. - """ - swings = swings[((swings['swing_value'].pct_change()).abs()) > price_deviation / 100] - swings = swings.reset_index(drop=True) - - return swings - - def swing_seq_catch(swing_data): - """ - This function should get swings data and will return the swings, - with one column added named new_swing_type which will determine if there is a change in swing type, - e.g. from high to low or vice versa. - """ - new_swings = swing_data[(swing_data.swing_type.shift(1) == swing_data.swing_type) | - (swing_data.swing_type.shift(-1) == swing_data.swing_type)] - if len(new_swings) > 0: - swing_change_idx = (swing_data.swing_type.shift(1) != swing_data.swing_type).cumsum() - new_s = new_swings['swing_type'].astype(str) + swing_change_idx.astype('str') - new_swings.insert(2, "new_swing_type", new_s, True) - return new_swings - - def swing_seq_picker(swings_data, swing_type): - """ - This function manages to remove useless sequential swings in a pandas way - """ - if swing_type == 'high': - agg_func = 'max' - elif swing_type == 'low': - agg_func = 'min' - else: - raise ValueError("Input data must be eigher high or low!") - - duplicate_swings = swings_data.groupby(['new_swing_type']).agg({'swing_value': agg_func}) - return merge(duplicate_swings, swings_data, how="inner", on=["swing_value", 'new_swing_type']) - - def swing_hl_merge(swings_sequential, swings_all): - """ - This function is responsible for handle high/low swing types. - It will merge all the high/low swings and will garantee that high/low are sequential. - """ - if len(swings_sequential) > 0: - high_swings = swing_seq_picker(swings_sequential[swings_sequential['swing_type'] == 'high'], 'high') - low_swings = swing_seq_picker(swings_sequential[swings_sequential['swing_type'] == 'low'], 'low') - swings_high_low = concat([swings_sequential, high_swings, low_swings]).drop_duplicates(keep=False) - swings_high_low = swings_high_low.reset_index(drop=True) - del swings_high_low['new_swing_type'] - else: - swings_high_low = swings_sequential - swings_high_low = concat([swings_all, swings_high_low]).drop_duplicates(keep=False) - return swings_high_low - - all_swings = calculate_swings(high, low, pivot_leg) - all_swings['idx'] = all_swings.index - swings_seq = swing_seq_catch(all_swings) - swings_merged = swing_hl_merge(swings_seq, all_swings).reset_index(drop=True) - swings = swings_merged.reset_index(drop=True) - - while (swings['swing_value'].pct_change().abs() <= price_deviation / 100).any(): - swings_price_dev = price_dev(swings, price_deviation) - swings_seq_price_dev = swing_seq_catch(swings_price_dev) - swings_seq_filtered = swing_hl_merge(swings_seq_price_dev, swings_price_dev) - swings = swings_seq_filtered - - swings.index = swings['idx'] - del swings['idx'] - - return all_swings, swings - - -zigzag.__doc__ = \ -""" Zigzag Inddicator - -The ZigZag feature on SharpCharts is not an indicator per se, but rather a means to filter out smaller price movements. -A ZigZag set at 10 would ignore all price movements less than 10%; only price movements greater than 10% would be shown. -Filtering out smaller movements gives chartists the ability to see the forest instead of just trees. - -Sources: - https://www.tradingview.com/support/solutions/43000591664-zig-zag/#:~:text=Definition,trader%20visual%20the%20price%20action. - https://school.stockcharts.com/doku.php?id=technical_indicators:zigzag - -Calculation: - ZigZag (high, low, pivot_leg, price_deviation, retrace=False, LastExtreme=False) - If % change > = X, plot ZigZag - - Default Inputs: - pivot_leg = 5, price_deviation = 10 - retrace = FALSE, LastExtreme = TRUE - - See Source links - -Args: - high (pd.Series): Series of 'high's - low (pd.Series): Series of 'low's - pivot_leg (int): Initial Acceleration Factor. Default: 5 - price_deviation (float): % change of price. Default: 10 - retrace (Boolean): This can be the change in the retracement of a previous move. Default: False - Last_extreme (Boolean): This references extreme price, if it is the same over multiple periods. Default: False - -Kwargs: - -Returns: - pd.DataFrame: index of swings, swing_value, and swing_type (high or low). -""" +# -*- coding: utf-8 -*- +# from numpy import isnan, nan, zeros +from numba import njit +from pandas import Series +from pandas_ta._typing import DictLike, Int, IntFloat +from pandas_ta.utils import ( + v_bool, + v_offset, + v_pos_default, + v_series, +) + + +def zigzag( + high: Series, low: Series, close: Series = None, + pivot_leg: int = None, price_deviation: IntFloat = None, + retrace: bool = None, last_extreme: bool = None, + offset: Int = None, **kwargs: DictLike +): + """ Zigzag (ZIGZAG) + + Zigzag attempts to filter out smaller price movments while highlighting + trend direction. It does not predict future trends, but it does identify + swing highs and lows. When 'price_deviation' is set to 10, it will ignore + all price movements less than 10%; only price movements greater than 10% + would be shown. + + Note: Zigzag lines are not permanent and a price reversal will create a + new line. + + Sources: + https://www.tradingview.com/support/solutions/43000591664-zig-zag/#:~:text=Definition,trader%20visual%20the%20price%20action. + https://school.stockcharts.com/doku.php?id=technical_indicators:zigzag + + Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's. Default: None + pivot_leg (int): Number of legs > 2. Default: 10 + price_deviation (float): Price Deviation Percentage for a reversal. + Default: 5 + retrace (bool): Default: False + last_extreme (bool): Default: True + offset (int): How many periods to offset the result. Default: 0 + + Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + + Returns: + pd.DataFrame: swing, and swing_type (high or low). + """ + # Validate + pivot_leg = _length = v_pos_default(pivot_leg, 10) + high = v_series(high, _length + 1) + low = v_series(low, _length + 1) + + if high is None or low is None: + return + + if close is not None: + close = v_series(close, _length + 1) + np_close = close.values + if close is None: + return + + price_deviation = v_pos_default(price_deviation, 5.0) + retrace = v_bool(retrace, False) + last_extreme = v_bool(last_extreme, True) + offset = v_offset(offset) + + # Calculation + np_high, np_low = high.values, low.values + highest_high = high.rolling(window=pivot_leg, center=True, min_periods=0).max() + lowest_low = low.rolling(window=pivot_leg, center=True, min_periods=0).min() + + # Fix and fill working code + + # Offset + # if offset != 0: + + # Fill + # if "fillna" in kwargs: + # if "fill_method" in kwargs: + + # Name and Category diff --git a/pandas_ta/volatility/aberration.py b/pandas_ta/volatility/aberration.py index 477a5d24..21082580 100644 --- a/pandas_ta/volatility/aberration.py +++ b/pandas_ta/volatility/aberration.py @@ -96,8 +96,8 @@ def aberration( xg.category = atr_.category = zg.category data = {zg.name: zg, sg.name: sg, xg.name: xg, atr_.name: atr_} - aberdf = DataFrame(data) - aberdf.name = f"ABER{_props}" - aberdf.category = zg.category + df = DataFrame(data, index=close.index) + df.name = f"ABER{_props}" + df.category = zg.category - return aberdf + return df diff --git a/pandas_ta/volatility/accbands.py b/pandas_ta/volatility/accbands.py index 78b8ea01..b0e325cb 100644 --- a/pandas_ta/volatility/accbands.py +++ b/pandas_ta/volatility/accbands.py @@ -90,8 +90,8 @@ def accbands( mid.category = upper.category = lower.category = "volatility" data = {lower.name: lower, mid.name: mid, upper.name: upper} - accbandsdf = DataFrame(data) - accbandsdf.name = f"ACCBANDS_{length}" - accbandsdf.category = mid.category + df = DataFrame(data, index=close.index) + df.name = f"ACCBANDS_{length}" + df.category = mid.category - return accbandsdf + return df diff --git a/pandas_ta/volatility/bbands.py b/pandas_ta/volatility/bbands.py index d226dee7..2892f3cf 100644 --- a/pandas_ta/volatility/bbands.py +++ b/pandas_ta/volatility/bbands.py @@ -101,20 +101,24 @@ def bbands( percent.fillna(method=kwargs["fill_method"], inplace=True) # Name and Category - lower.name = f"BBL_{length}_{std}" - mid.name = f"BBM_{length}_{std}" - upper.name = f"BBU_{length}_{std}" - bandwidth.name = f"BBB_{length}_{std}" - percent.name = f"BBP_{length}_{std}" + _props = f"_{length}_{std}" + lower.name = f"BBL{_props}" + mid.name = f"BBM{_props}" + upper.name = f"BBU{_props}" + bandwidth.name = f"BBB{_props}" + percent.name = f"BBP{_props}" upper.category = lower.category = "volatility" mid.category = bandwidth.category = upper.category data = { - lower.name: lower, mid.name: mid, upper.name: upper, - bandwidth.name: bandwidth, percent.name: percent + lower.name: lower, + mid.name: mid, + upper.name: upper, + bandwidth.name: bandwidth, + percent.name: percent } - bbandsdf = DataFrame(data) - bbandsdf.name = f"BBANDS_{length}_{std}" - bbandsdf.category = mid.category + df = DataFrame(data, index=close.index) + df.name = f"BBANDS{_props}" + df.category = mid.category - return bbandsdf + return df diff --git a/pandas_ta/volatility/chandelier_exit.py b/pandas_ta/volatility/chandelier_exit.py index ec219feb..40368389 100644 --- a/pandas_ta/volatility/chandelier_exit.py +++ b/pandas_ta/volatility/chandelier_exit.py @@ -124,11 +124,12 @@ def chandelier_exit( if use_close: _props = f"_CLOSE_{_props}" - df = DataFrame({ + data = { f"{_name}l{_props}": long, f"{_name}s{_props}": short, f"{_name}d{_props}": direction - }, index=close.index) + } + df = DataFrame(data, index=close.index) df.name = f"{_name}{_props}" df.category = "volatility" diff --git a/pandas_ta/volatility/donchian.py b/pandas_ta/volatility/donchian.py index cde35507..55b05015 100644 --- a/pandas_ta/volatility/donchian.py +++ b/pandas_ta/volatility/donchian.py @@ -74,8 +74,8 @@ def donchian( mid.category = upper.category = lower.category = "volatility" data = {lower.name: lower, mid.name: mid, upper.name: upper} - dcdf = DataFrame(data) - dcdf.name = f"DC_{lower_length}_{upper_length}" - dcdf.category = mid.category + df = DataFrame(data, index=high.index) + df.name = f"DC_{lower_length}_{upper_length}" + df.category = mid.category - return dcdf + return df diff --git a/pandas_ta/volatility/hwc.py b/pandas_ta/volatility/hwc.py index 4a58fa7b..1c3ff2ff 100644 --- a/pandas_ta/volatility/hwc.py +++ b/pandas_ta/volatility/hwc.py @@ -144,7 +144,7 @@ def hwc( hwc_upper.name: hwc_upper, hwc_lower.name: hwc_lower } - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"HWC_{scalar}" df.category = hwc.category diff --git a/pandas_ta/volatility/kc.py b/pandas_ta/volatility/kc.py index abfa8e27..cb7256f4 100644 --- a/pandas_ta/volatility/kc.py +++ b/pandas_ta/volatility/kc.py @@ -93,8 +93,8 @@ def kc( basis.category = upper.category = lower.category = "volatility" data = {lower.name: lower, basis.name: basis, upper.name: upper} - kcdf = DataFrame(data) - kcdf.name = f"KC{_props}" - kcdf.category = basis.category + df = DataFrame(data, index=close.index) + df.name = f"KC{_props}" + df.category = basis.category - return kcdf + return df diff --git a/pandas_ta/volatility/thermo.py b/pandas_ta/volatility/thermo.py index 6ec193e5..e89c443f 100644 --- a/pandas_ta/volatility/thermo.py +++ b/pandas_ta/volatility/thermo.py @@ -110,7 +110,7 @@ def thermo( thermo_long.name: thermo_long, thermo_short.name: thermo_short } - df = DataFrame(data) + df = DataFrame(data, index=high.index) df.name = f"THERMO{_props}" df.category = thermo.category diff --git a/pandas_ta/volume/aobv.py b/pandas_ta/volume/aobv.py index f927822b..4bfc4f16 100644 --- a/pandas_ta/volume/aobv.py +++ b/pandas_ta/volume/aobv.py @@ -2,7 +2,8 @@ from pandas import DataFrame, Series from pandas_ta._typing import DictLike, Int from pandas_ta.ma import ma -from pandas_ta.trend import long_run, short_run +from pandas_ta.trend.long_run import long_run +from pandas_ta.trend.short_run import short_run from pandas_ta.utils import v_mamode, v_offset, v_pos_default, v_series from .obv import obv @@ -103,12 +104,12 @@ def aobv( f"OBV{_mode}_{fast}": maf, f"OBV{_mode}_{slow}": mas, f"AOBV_LR_{run_length}": obv_long, - f"AOBV_SR_{run_length}": obv_short, + f"AOBV_SR_{run_length}": obv_short } - aobvdf = DataFrame(data) + df = DataFrame(data, index=close.index) # Name and Category - aobvdf.name = f"AOBV{_mode}_{fast}_{slow}_{min_lookback}_{max_lookback}_{run_length}" - aobvdf.category = "volume" + df.name = f"AOBV{_mode}_{fast}_{slow}_{min_lookback}_{max_lookback}_{run_length}" + df.category = "volume" - return aobvdf + return df diff --git a/pandas_ta/volume/pvo.py b/pandas_ta/volume/pvo.py index 21e0c5f6..f9390f52 100644 --- a/pandas_ta/volume/pvo.py +++ b/pandas_ta/volume/pvo.py @@ -78,7 +78,7 @@ def pvo( pvo.category = histogram.category = signalma.category = "momentum" data = {pvo.name: pvo, histogram.name: histogram, signalma.name: signalma} - df = DataFrame(data) + df = DataFrame(data, index=volume.index) df.name = pvo.name df.category = pvo.category diff --git a/pandas_ta/volume/wb_tsv.py b/pandas_ta/volume/wb_tsv.py index 6a77b6b9..25044535 100644 --- a/pandas_ta/volume/wb_tsv.py +++ b/pandas_ta/volume/wb_tsv.py @@ -99,7 +99,7 @@ def wb_tsv( tsv.category = signal_.category = ratio.category = "volume" data = {tsv.name: tsv, signal_.name: signal_, ratio.name: ratio} - df = DataFrame(data) + df = DataFrame(data, index=close.index) df.name = f"TSV{_props}" df.category = tsv.category diff --git a/setup.py b/setup.py index 069abbc7..4b8206f1 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "4", "2b")), + version=".".join(("0", "4", "3b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py index 77f6de78..b1313f93 100644 --- a/tests/test_indicator_trend.py +++ b/tests/test_indicator_trend.py @@ -68,12 +68,17 @@ def test_adx(df): pdt.assert_frame_equal(result, expected_tv_adx) +def test_alphatrend(df): + result = ta.alphatrend(df.open, df.high, df.low, df.close) + assert isinstance(result, DataFrame) + assert result.name == "ALPHAT_14_1_50" + + def test_amat(df): result = ta.amat(df.close) assert isinstance(result, DataFrame) assert result.name == "AMATe_8_21_2" - def test_aroon(df): result = ta.aroon(df.high, df.low, talib=False) assert isinstance(result, DataFrame) @@ -260,6 +265,11 @@ def test_ext_adx(df): assert list(df.columns[-3:]) == ["ADX_14", "DMP_14", "DMN_14"] +def test_ext_alphatrend(df): + df.ta.alphatrend(append=True) + assert list(df.columns[-2:]) == ["ALPHAT_14_1_50", "ALPHATl_14_1_50_2"] + + def test_ext_amat(df): df.ta.amat(append=True) assert list(df.columns[-2:]) == ["AMATe_LR_8_21_2", "AMATe_SR_8_21_2"] diff --git a/tests/test_studies.py b/tests/test_studies.py index 2f3189d6..2dfc1630 100644 --- a/tests/test_studies.py +++ b/tests/test_studies.py @@ -9,7 +9,7 @@ [pytest.param(ta.CommonStudy, id="common"), pytest.param(ta.AllStudy, id="all")] # +/- when adding/removing indicators -ALL_COLUMNS = 315 +ALL_COLUMNS = 317 def test_all_study_props(all_study): @@ -32,7 +32,7 @@ def test_common_study_props(common_study): @pytest.mark.parametrize("category,columns", [ ("candles", 70), ("cycles", 2), ("momentum", 73), ("overlap", 56), - ("performance", 2), ("statistics", 16), ("transform", 5), ("trend", 27), + ("performance", 2), ("statistics", 16), ("transform", 5), ("trend", 29), ("volatility", 36), ("volume", 28), pytest.param(ta.AllStudy, ALL_COLUMNS, id=f"all-{ALL_COLUMNS}"), pytest.param(ta.CommonStudy, 5, id="common-5"), @@ -89,7 +89,7 @@ def test_study_custom_e(df, custom_study_e, talib): @pytest.mark.parametrize("talib", [False, True]) def test_study_all_multirun(df, all_study, talib): - all_columns = 598 # +/- when adding/removing indicators + all_columns = 604 # +/- when adding/removing indicators initial_columns = df.shape[1] df.ta.study(all_study, length=10, cores=0, talib=talib) df.ta.study(all_study, length=50, cores=0, talib=talib) From 934ece70b920190116928cad2efe5153074220d9 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Thu, 12 Oct 2023 13:30:22 -0700 Subject: [PATCH 7/7] ENH #727 DOC --- README.md | 4 ++-- pandas_ta/overlap/supertrend.py | 11 +++++++---- setup.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cc261fdd..525ecad3 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ $ pip install pandas_ta[full] Development Version ------------------- -The _development_ version, _0.4.3b_, includes _numerous_ bug fixes, speed improvements and better documentation since release, _0.3.14b_. +The _development_ version, _0.4.4b_, includes _numerous_ bug fixes, speed improvements and better documentation since release, _0.3.14b_. ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta.git@development ``` @@ -292,7 +292,7 @@ Contributions, feedback, and bug squashing are integral to the success of this l _Thank you for your contributions!_ - +
diff --git a/pandas_ta/overlap/supertrend.py b/pandas_ta/overlap/supertrend.py index fc446deb..f1efdaf3 100644 --- a/pandas_ta/overlap/supertrend.py +++ b/pandas_ta/overlap/supertrend.py @@ -3,7 +3,7 @@ from pandas import DataFrame, Series from pandas_ta._typing import DictLike, Int, IntFloat from pandas_ta.overlap import hl2 -from pandas_ta.utils import v_offset, v_pos_default, v_series +from pandas_ta.utils import v_mamode, v_offset, v_pos_default, v_series from pandas_ta.volatility import atr @@ -32,7 +32,8 @@ def supertrend( variable of control. Default: length multiplier (float): Coefficient for upper and lower band distance to midrange. Default: 3.0 - atr_mamode (str) : Can be used to specify the type of MA to be used for ATR calculation. + atr_mamode (str) : MA type to be used for ATR calculation. + See ``help(ta.ma)``. Default: 'rma' offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -54,6 +55,7 @@ def supertrend( return multiplier = v_pos_default(multiplier, 3.0) + atr_mamode = v_mamode(atr_mamode, "rma") offset = v_offset(offset) # Calculate @@ -63,8 +65,9 @@ def supertrend( hl2_ = hl2(high, low) matr = multiplier * atr(high, low, close, atr_length, mamode=atr_mamode) - lb = hl2_ - matr # Lowerband - ub = hl2_ + matr # Upperband + lb = hl2_ - matr + ub = hl2_ + matr + for i in range(1, m): if close.iat[i] > ub.iat[i - 1]: dir_[i] = 1 diff --git a/setup.py b/setup.py index 4b8206f1..11d37191 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "4", "3b")), + version=".".join(("0", "4", "4b")), description=long_description, long_description=long_description, author="Kevin Johnson",