mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-09-12 12:40:39 +08:00
Merge branch 'pr/370' into development
This commit is contained in:
@@ -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**
|
||||
|
||||
@@ -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
@@ -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,2 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .ebsw import ebsw
|
||||
from .reflex import reflex
|
||||
|
||||
+83
-39
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
Reference in New Issue
Block a user