From 3ebd362b1e0d2dc7b5079c0038da757c7dcc56c4 Mon Sep 17 00:00:00 2001 From: rengel Date: Wed, 11 Aug 2021 22:42:38 +0200 Subject: [PATCH] ENH added Reflex and Trendflex indicators Reflex and Trendflex indicators by Ehlers. --- README.md | 2 + pandas_ta/__init__.py | 4 +- pandas_ta/core.py | 12 +++- pandas_ta/cycles/__init__.py | 1 + pandas_ta/cycles/reflex.py | 104 +++++++++++++++++++++++++++++++++++ pandas_ta/trend/__init__.py | 1 + pandas_ta/trend/trendflex.py | 99 +++++++++++++++++++++++++++++++++ 7 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 pandas_ta/cycles/reflex.py create mode 100644 pandas_ta/trend/trendflex.py diff --git a/README.md b/README.md index fe3e28b..0013422 100644 --- a/README.md +++ b/README.md @@ -667,6 +667,7 @@ df = df.ta.cdl_pattern(name=["doji", "inside"]) ### **Cycles** (1) * _Even Better Sinewave_: **ebsw** +* _Reflex_ (companion of trendflex): **reflex**
@@ -818,6 +819,7 @@ Use parameter: cumulative=**True** for cumulative results. * _Parabolic Stop and Reverse_: **psar** * _Q Stick_: **qstick** * _Short Run_: **short_run** +* _Trendflex_ (companion of reflex): **trendflex** * _Trend Signals_: **tsignals** * _TTM Trend_: **ttm_trend** * _Vertical Horizontal Filter_: **vhf** diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 0e740b8..91716ce 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -43,7 +43,7 @@ Category = { "cdl_pattern", "cdl_z", "ha" ], # Cycles - "cycles": ["ebsw"], + "cycles": ["ebsw", "reflex"], # Momentum "momentum": [ "ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo", @@ -69,7 +69,7 @@ Category = { # Trend "trend": [ "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", - "increasing", "long_run", "psar", "qstick", "short_run", "tsignals", + "increasing", "long_run", "psar", "qstick", "short_run", "trendflex", "tsignals", "ttm_trend", "vhf", "vortex", "xsignals" ], # Volatility diff --git a/pandas_ta/core.py b/pandas_ta/core.py index fdee64a..33f5039 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -895,6 +895,11 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(kwargs.pop("close", "close")) result = ebsw(close=close, length=length, bars=bars, offset=offset, **kwargs) return self._post_process(result, **kwargs) + + def reflex(self, close=None, length=None, smooth_bars=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = reflex(close=close, length=length, smooth_bars=bars, offset=offset, **kwargs) + return self._post_process(result, **kwargs) # Momentum def ao(self, fast=None, slow=None, offset=None, **kwargs): @@ -1484,7 +1489,12 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(kwargs.pop("close", "close")) result = supertrend(high=high, low=low, close=close, period=period, multiplier=multiplier, mamode=mamode, drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) - + + def trendflex(self, close=None, length=None, smooth_bars=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = trendflex(close=close, length=length, smooth_bars=bars, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def tsignals(self, trend=None, asbool=None, trend_reset=None, trend_offset=None, offset=None, **kwargs): if trend is None: return self._df diff --git a/pandas_ta/cycles/__init__.py b/pandas_ta/cycles/__init__.py index 65bf123..9289e7f 100644 --- a/pandas_ta/cycles/__init__.py +++ b/pandas_ta/cycles/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- from .ebsw import ebsw +from .reflex import reflex diff --git a/pandas_ta/cycles/reflex.py b/pandas_ta/cycles/reflex.py new file mode 100644 index 0000000..8d9b81a --- /dev/null +++ b/pandas_ta/cycles/reflex.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +from numpy import NaN as npNaN +from numpy import cos as npCos +from numpy import exp as npExp +from numpy import full as npFull +from numpy import sqrt as npSqrt +from pandas import DataFrame, Series, concat +from pandas_ta.overlap import rma +from pandas_ta.utils import get_drift, get_offset, verify_series, signals + + +def reflex(close, length=None, smooth_bars=None, offset=None, **kwargs): + """Indicator: Reflex""" + # Validate arguments + close = verify_series(close, length) + length = int(length) if length and length > 0 else 20 + smooth_bars = int(smooth_bars) if smooth_bars and smooth_bars > 0 else 20 + offset = get_offset(offset) + + # Precalculations + a1 = npExp(-1.414 * 3.14159 / smooth_bars) + b1 = 2 * a1 * npCos(1.414 * 180 / smooth_bars) + c2 = b1 + c3 = -a1 * a1 + c1 = 1 - c2 - c3 + Filt = npFull(close.size, 0) + MS = npFull(close.size, 0) + # Reflex = list(Filt) + Reflex = npFull(close.size, npNaN) + + # Calculation + for i in range(1, close.size): + # Gently smooth the data in a SuperSmoother + Filt[i] = c1 * (close[i] + close[i - 1]) / 2 + c2 * Filt[i - 1] + c3 * Filt[i - 2] + + # Length is assumed cycle period + Slope = (Filt[i - length] - Filt[i]) / length + + # Sum the differences + Sum = 0 + for count in range(1, length): + Sum = Sum + (Filt[i] + count * Slope) - Filt[i - count] + Sum = Sum / length + + # Normalize in terms of Standard Deviations + MS[i] = .04 * Sum * Sum + .96 * MS[i - 1] + if MS[i] != 0: + Reflex[i] = Sum / npSqrt(MS[i]) + else: + Reflex[i] = Sum / 0.00001 + + result = Series(Reflex, index=close.index) + + # Neutralize pre-roll phase + result.iloc[0:length] = npNaN + + # Offset + if offset != 0: + result = result.shift(offset) + + # Handle fills + if "fillna" in kwargs: + result.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + result.fillna(method=kwargs["fill_method"], inplace=True) + + # Name and Categorize it + result.name = f"REFLEX_{length}_{smooth_bars}" + result.category = "cycles" + + return result + + +reflex.__doc__ = \ +"""Reflex (reflex) + +John F. Ehlers introduced two indicators within the article "Reflex: A New Zero-Lag Indicator” +in February 2020, TASC magazine. One of which is the Reflex, a lag reduced cycle indicator. +Both indicators (Reflex/Trendflex) are oscillators and complement each other with the focus for +cycle and trend. + +Written for Pandas TA by rengel8 (2021-08-11) based on the implementation on prorealcode (refer to source). +Beyond the mentioned source, this implementation has a separate control parameter for the internal +applied SuperSmoother. + +Sources: + https://www.prorealcode.com/prorealtime-indicators/reflex-and-trendflex-indicators-john-f-ehlers/ + +Calculation: + Refer to provided source or the code above. + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 20 + smooth_bars (int): Period of internal SuperSmoother (default: asmooth_bars = length). Default: 20 + 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.Series: New feature generated. +""" diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py index 4544a01..ce0b86d 100644 --- a/pandas_ta/trend/__init__.py +++ b/pandas_ta/trend/__init__.py @@ -12,6 +12,7 @@ from .long_run import long_run from .psar import psar from .qstick import qstick from .short_run import short_run +from .trendflex import trendflex from .tsignals import tsignals from .ttm_trend import ttm_trend from .vhf import vhf diff --git a/pandas_ta/trend/trendflex.py b/pandas_ta/trend/trendflex.py new file mode 100644 index 0000000..356137d --- /dev/null +++ b/pandas_ta/trend/trendflex.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from numpy import NaN as npNaN +from numpy import cos as npCos +from numpy import exp as npExp +from numpy import full as npFull +from numpy import sqrt as npSqrt +from pandas import DataFrame, Series, concat +from pandas_ta.overlap import rma +from pandas_ta.utils import get_drift, get_offset, verify_series, signals + + +def trendflex(close, length=None, smooth_bars=None, offset=None, **kwargs): + """Indicator: Reflex""" + # Validate arguments + close = verify_series(close, length) + length = int(length) if length and length > 0 else 20 + smooth_bars = int(smooth_bars) if smooth_bars and smooth_bars > 0 else 20 + offset = get_offset(offset) + + # Precalculations + a1 = npExp(-1.414 * 3.14159 / smooth_bars) + b1 = 2 * a1 * npCos(1.414 * 180 / smooth_bars) + c2 = b1 + c3 = -a1 * a1 + c1 = 1 - c2 - c3 + Filt = npFull(close.size, 0) + MS = npFull(close.size, 0) + Trendflex = list(Filt) + + # Calculation + for i in range(1, close.size): + # Gently smooth the data in a SuperSmoother + Filt[i] = c1 * (close[i] + close[i - 1]) / 2 + c2 * Filt[i - 1] + c3 * Filt[i - 2] + + # Sum the differences + Sum = 0 + for count in range(1, length): + Sum = Sum + Filt[i] - Filt[i - count] + Sum = Sum / length + + # Normalize in terms of Standard Deviations + MS[i] = .04 * Sum * Sum + .96 * MS[i - 1] + if MS[i] != 0: + Trendflex[i] = Sum / npSqrt(MS[i]) + else: + Trendflex[i] = Sum / 0.00001 + + result = Series(Trendflex, index=close.index) + + # Neutralize pre-roll phase + result.iloc[0:length] = npNaN + + # Offset + if offset != 0: + result = result.shift(offset) + + # Handle fills + if "fillna" in kwargs: + result.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + result.fillna(method=kwargs["fill_method"], inplace=True) + + # Name and Categorize it + result.name = f"TRENDFLEX_{length}_{smooth_bars}" + result.category = "trend" + + return result + +trendflex.__doc__ = \ +"""Trendflex (trendflex) + +John F. Ehlers introduced two indicators within the article "Reflex: A New Zero-Lag Indicator” +in February 2020, TASC magazine. One of which is the Trendflex, a lag reduced trend indicator. +Both indicators (Reflex/Trendflex) are oscillators and complement each other with the focus for +cycle and trend. + +Written for Pandas TA by rengel8 (2021-08-11) based on the implementation on prorealcode (refer to source). +Beyond the mentioned source, this implementation has a separate control parameter for the internal +applied SuperSmoother. + +Sources: + https://www.prorealcode.com/prorealtime-indicators/reflex-and-trendflex-indicators-john-f-ehlers/ + +Calculation: + Refer to provided source or the code above. + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 20 + smooth_bars (int): Period of internal SuperSmoother (default: asmooth_bars = length). Default: 20 + 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.Series: New feature generated. +"""