Merge branch 'pr/370' into development

This commit is contained in:
Kevin Johnson
2021-08-16 08:43:27 -07:00
9 changed files with 303 additions and 42 deletions
+2
View File
@@ -667,6 +667,7 @@ df = df.ta.cdl_pattern(name=["doji", "inside"])
### **Cycles** (1)
* _Even Better Sinewave_: **ebsw**
* _Reflex_ (companion of trendflex): **reflex**
<br/>
@@ -820,6 +821,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**
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -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",
@@ -70,7 +70,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
+11 -1
View File
@@ -900,6 +900,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):
@@ -1499,7 +1504,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
+1
View File
@@ -1,2 +1,3 @@
# -*- coding: utf-8 -*-
from .ebsw import ebsw
from .reflex import reflex
+83 -39
View File
@@ -5,58 +5,94 @@ from numpy import nan as npNaN
from numpy import pi as npPi
from numpy import sin as npSin
from numpy import sqrt as npSqrt
from numpy import zeros as npZeros
from numpy import roll as npRoll
from numpy import mean as npMean
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def ebsw(close, length=None, bars=None, offset=None, **kwargs):
def ebsw(close, length=None, bars=None, offset=None, initial_version=False, **kwargs):
"""Indicator: Even Better SineWave (EBSW)"""
# Validate arguments
length = int(length) if length and length > 38 else 40
length = int(length) if length and length > 10 else 40
bars = int(bars) if bars and bars > 0 else 10
close = verify_series(close, length)
initial_version = bool(initial_version) # allow initial version to be used (more responsive/caution!)
offset = get_offset(offset)
if close is None: return
# variables
alpha1 = HP = 0 # alpha and HighPass
a1 = b1 = c1 = c2 = c3 = 0
Filt = Pwr = Wave = 0
if initial_version: # not the default version that is active
# variables
alpha1 = HP = 0 # alpha and HighPass
a1 = b1 = c1 = c2 = c3 = 0
Filt = Pwr = Wave = 0
lastClose = lastHP = 0
FilterHist = [0, 0] # Filter history
lastClose = lastHP = 0
FilterHist = [0, 0] # Filter history
# Calculate Result
m = close.size
result = [npNaN for _ in range(0, length - 1)] + [0]
for i in range(length, m):
# HighPass filter cyclic components whose periods are shorter than Duration input
alpha1 = (1 - npSin(360 / length)) / npCos(360 / length)
HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP
# Calculate Result
m = close.size
result = [npNaN for _ in range(0, length - 1)] + [0]
for i in range(length, m):
# HighPass filter cyclic components whose periods are shorter than Duration input
alpha1 = (1 - npSin(360 / length)) / npCos(360 / length)
HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP
# Smooth with a Super Smoother Filter from equation 3-3
a1 = npExp(-npSqrt(2) * npPi / bars)
b1 = 2 * a1 * npCos(npSqrt(2) * 180 / bars)
c2 = b1
c3 = -1 * a1 * a1
# Smooth with a Super Smoother Filter from equation 3-3
a1 = npExp(-npSqrt(2) * npPi / bars)
b1 = 2 * a1 * npCos(npSqrt(2) * 180 / bars)
c2 = b1
c3 = -1 * a1 * a1
c1 = 1 - c2 - c3
Filt = c1 * (HP + lastHP) / 2 + c2 * FilterHist[1] + c3 * FilterHist[0]
# Filt = float("{:.8f}".format(float(Filt))) # to fix for small scientific notations, the big ones fail
# 3 Bar average of Wave amplitude and power
Wave = (Filt + FilterHist[1] + FilterHist[0]) / 3
Pwr = (Filt * Filt + FilterHist[1] * FilterHist[1] + FilterHist[0] * FilterHist[0]) / 3
# Normalize the Average Wave to Square Root of the Average Power
Wave = Wave / npSqrt(Pwr)
# update storage, result
FilterHist.append(Filt) # append new Filt value
FilterHist.pop(0) # remove first element of list (left) -> updating/trim
lastHP = HP
lastClose = close[i]
result.append(Wave)
else: # this version is the default version
# Instance Variables
lastHP = lastClose = 0
filtHist = npZeros(3)
result = [npNaN] * (length - 1) + [0]
# Calculate constants
angle = 2 * npPi / length
alpha1 = (1 - npSin(angle)) / npCos(angle)
ang = 2 ** .5 * npPi / bars
a1 = npExp(-ang)
c2 = 2 * a1 * npCos(ang)
c3 = -a1 ** 2
c1 = 1 - c2 - c3
Filt = c1 * (HP + lastHP) / 2 + c2 * FilterHist[1] + c3 * FilterHist[0]
# Filt = float("{:.8f}".format(float(Filt))) # to fix for small scientific notations, the big ones fail
# 3 Bar average of Wave amplitude and power
Wave = (Filt + FilterHist[1] + FilterHist[0]) / 3
Pwr = (Filt * Filt + FilterHist[1] * FilterHist[1] + FilterHist[0] * FilterHist[0]) / 3
for i in range(length, close.size):
HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP
# Normalize the Average Wave to Square Root of the Average Power
Wave = Wave / npSqrt(Pwr)
# Rotate filters to overwrite oldest value
filtHist = npRoll(filtHist, -1)
filtHist[-1] = c1 * (HP + lastHP) / 2 + c2 * filtHist[1] + c3 * filtHist[0]
# update storage, result
FilterHist.append(Filt) # append new Filt value
FilterHist.pop(0) # remove first element of list (left) -> updating/trim
lastHP = HP
lastClose = close[i]
result.append(Wave)
# Wave calculation
wave = npMean(filtHist)
rms = npSqrt(npMean(filtHist ** 2))
wave = wave / rms
# Update past values
lastHP = HP
lastClose = close[i]
result.append(wave)
ebsw = Series(result, index=close.index)
@@ -78,22 +114,30 @@ def ebsw(close, length=None, bars=None, offset=None, **kwargs):
ebsw.__doc__ = \
"""Even Better SineWave (EBSW) *beta*
"""Even Better SineWave (EBSW)
This indicator measures market cycles and uses a low pass filter to remove noise.
Its output is bound signal between -1 and 1 and the maximum length of a detected
trend is limited by its length input.
Written by rengel8 for Pandas TA based on a publication at 'prorealcode.com' and
a book by J.F.Ehlers.
a book by J.F.Ehlers. According to the suggestion by Squigglez2* and major differences between
the initial version's output close to the implementation from Ehler's, the default version is now
more closely related to the code from pro-realcode.
* This implementation seems to be logically limited. It would make sense to
implement exactly the version from prorealcode and compare the behaviour.
Remark:
The default version is now more cycle oriented and tends to be less whipsaw-prune. Thus the older version
might offer earlier signals at medium and stronger reversals.
A test against the version at TradingView showed very close results with the advantage to be one bar/candle
faster, than the corresponding reference value. This might be pre-roll related and was not further investigated.
* https://github.com/twopirllc/pandas-ta/issues/350
Sources:
https://www.prorealcode.com/prorealtime-indicators/even-better-sinewave/
J.F.Ehlers 'Cycle Analytics for Traders', 2014
- https://www.prorealcode.com/prorealtime-indicators/even-better-sinewave/
- J.F.Ehlers 'Cycle Analytics for Traders', 2014
Calculation:
refer to 'sources' or implementation
+104
View File
@@ -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.
"""
+1
View File
@@ -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
+99
View File
@@ -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.
"""