diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 58f948f..bfcff3e 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1583,6 +1583,13 @@ class AnalysisIndicators(object): result = atr(high=high, low=low, close=close, length=length, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def atrts(self, length=None, factor=None, mamode=None, offset: Int = None, **kwargs: DictLike): + 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 = atrts(high=high, low=low, close=close, length=length, factor=factor, mamode=mamode, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def bbands(self, length=None, std=None, mamode=None, offset: Int = None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) result = bbands(close=close, length=length, std=std, mamode=mamode, offset=offset, **kwargs) diff --git a/pandas_ta/volatility/__init__.py b/pandas_ta/volatility/__init__.py index a326697..1617352 100644 --- a/pandas_ta/volatility/__init__.py +++ b/pandas_ta/volatility/__init__.py @@ -13,3 +13,4 @@ from .rvi import rvi from .thermo import thermo from .true_range import true_range from .ui import ui +from .atrts import atrts diff --git a/pandas_ta/volatility/atrts.py b/pandas_ta/volatility/atrts.py new file mode 100644 index 0000000..e21f6a5 --- /dev/null +++ b/pandas_ta/volatility/atrts.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +from .true_range import true_range +from pandas_ta import Imports +from pandas_ta.overlap import ma +from pandas_ta.volatility import atr +from pandas_ta.utils import get_drift, get_offset, verify_series +from pandas import DataFrame, Series +from functools import partial + +try: + from numba import njit +except ImportError: + def njit(_): return _ + +@njit +def calculateFunc(upTrend, dnTrend, prevP, atr, factor): + if upTrend: + return prevP - atr * factor + elif dnTrend: + return prevP + atr * factor + +@njit +def tailingStopFunc(upTrend, dnTrend, prevA, atrts): + if upTrend: + if atrts < prevA: return prevA + elif dnTrend: + if atrts > prevA: return prevA + +def atrts(high, low, close, length=None, factor=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): + """ATR Trailing Stops (ATRTS) + identifies exit points for long and short positions. + First, an exponential moving average (EMA) of the input is taken to determine the current trend. + Then, the Average True Range (ATR) is calculated and multiplied by a user defined factor. + If the EMA is increasing (uptrend), the ATR product is subtracted from the price or, + if the EMA is decreasing (down trend), it is added to the price, and along with a few details the ATRTS is formed. + The user may change the position (long), input (close), method (EMA), period lengths, + percent factor and show entry option(see trading signals below). + This indicator’s definition is further expressed in the condensed code given in the calculation below. + + Sources: + https://www.motivewave.com/studies/atr_trailing_stops.htm + + Calculation: + //position = pos, user defined, default is long + //input = price, user defined, default is close + //method = moving average (ma), user defined, default is EMA + //period1 = maP, user defined, default is 63 + //period2 = artP, user defined, default is 21 + //factor = fac, user defined, default is 3 + //show entrys = showE, user defined, default is false + //index = current bar number, prev = previous + //LOE = less or equal, MOE = more or equal + //shortP = short position, longP = long position + //index = current bar number + + longP = pos == "Long"; + shortP = pos == "Short"; + atrts = 0, atr = 0; + ma = ma(method, maP, input); + prevP = price[index-1]; + prevA = ifNull(price, atrts[index]); //current atrts is plotted at index+1 + upTrend = price moreThan ma; + dnTrend = price LOE ma; + atr = atr(index, atrP); + if (upTrend) + atrts = price - fac * atr; + if (atrts lessThan prevA) atrts = prevA; + endIf + if (dnTrend) + atrts = price + fac * atr; + if (atrts moreThan prevA) atrts = prevA; + endIf + Plot: atrts[index+1]; + //Signals + sell = false, buy = false; + if (atrts != 0) + if (longP AND upTrend) + sell = price lessThan atrts; //sell to exit + buy = prevP lessThan atrts AND price moreThan atrts AND showE; //buy (enter) + endIf + if (shortP AND dnTrend) + sell = prevP moreThan atrts AND price lessThan atrts AND showE; //sell short (enter) + buy = price moreThan atrts; //buy to cover + endIf + endIf + + Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + length (int): It's period. Default: 14 + factor (int): the multiplyer. Default: 3 + mamode (str): See ```help(ta.ma)```. Default: 'rma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + drift (int): The difference period. Default: 1 + offset (int): How many periods to offset the result. Default: 0 + + Kwargs: + percent (bool, optional): Return as percentage. Default: False + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + + Returns: + pd.Series: New feature generated. + """ + # Validate + length = int(length) if length and length > 0 else 21 + factor = int(factor) if factor and factor > 0 else 3 + mamode = mamode.lower() if mamode and isinstance(mamode, str) else "rma" + high = verify_series(high, length) + low = verify_series(low, length) + close = verify_series(close, length) + drift = get_drift(drift) + offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True + + if high is None or low is None or close is None: return + + # Calculate - start + atr_ = atr(high=high, low=low, close=close, length=length) + ma_ = ma(mamode, close, length=length*3) + upTrend = close > ma_ + dnTrend = close <= ma_ + prevP = close.shift(1) + + func_p = partial(calculateFunc, factor=factor) + atrts_ = [func_p(a,b,c,d) for a,b,c,d in zip(upTrend, dnTrend, prevP, atr_)] + atrts_ = Series(atrts_, index=close.index) + + #prevA = atrts_.shift(1) + #atrts = [tailingStopFunc(a,b,c,d) for a,b,c,d in zip(upTrend, dnTrend, prevA, atrts_)] + + #atrts = Series(atrts, index=close.index) + atrts = atrts_.shift(-1) + # Calculate - end + + percentage = kwargs.pop("percent", False) + if percentage: + atrts *= 100 / close + + # Offset + if offset != 0: + atrts = atrts.shift(offset) + + # Handle fills + if "fillna" in kwargs: + atrts.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + atrts.fillna(method=kwargs["fill_method"], inplace=True) + + # Name and Categorize it + atrts.name = f"ATRTS{mamode[0]}_{length}{'p' if percentage else ''}" + atrts.category = "volatility" + + return atrts \ No newline at end of file