diff --git a/README.md b/README.md index 5582ff5..40fced1 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ print(bothhl2.name) # "pre_HL2_post" |:--------:| | ![Example MACD](/images/SPY_MACD.png) | -### **Overlap** (27) +### **Overlap** (28) * _Double Exponential Moving Average_: **dema** * _Exponential Moving Average_: **ema** @@ -482,6 +482,7 @@ print(bothhl2.name) # "pre_HL2_post" * _WildeR's Moving Average_: **rma** * _Sine Weighted Moving Average_: **sinwma** * _Simple Moving Average_: **sma** +* _Ehler's Super Smoother Filter_: **ssf** * _Supertrend_: **supertrend** * _Symmetric Weighted Moving Average_: **swma** * _T3 Moving Average_: **t3** @@ -621,6 +622,7 @@ trading account, or fund.. * _Quantitative Qualitative Estimation_ (**qqe**) The Quantitative Qualitative Estimation (QQE) is like SuperTrend for a Smoothed RSI. See: ```help(ta.qqe)``` * _SMI Ergodic_ (**smi**) Developed by William Blau, the SMI Ergodic Indicator is the same as the True Strength Index (TSI) except the SMI includes a signal line and oscillator. * _Squeeze_ (**squeeze**). A Momentum indicator. Both John Carter's TTM **and** Lazybear's TradingView versions are implemented. The default is John Carter's, or ```lazybear=False```. Set ```lazybear=True``` to enable Lazybear's. +* _Ehler's Super Smoother Filter_ (**ssf**). Ehler's solution to reduce lag and remove aliasing noise compared to other common moving average indicators. See: ```help(ta.ssf)``` * _Stochastic RSI_ (**stochrsi**) "Stochastic RSI and Dynamic Momentum Index" was created by Tushar Chande and Stanley Kroll. In line with Trading View's calculation. See: ```help(ta.stochrsi)``` * _TTM Trend_ (**ttm_trend**). A trend indicator inspired from John Carter's book "Mastering the Trade" issue of Stocks & Commodities Magazine. It is a moving average based trend diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 618cfe2..0891bcf 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -49,21 +49,21 @@ Category = { "overlap": [ "dema", "ema", "fwma", "hilo", "hl2", "hlc3", "hma", "ichimoku", "kama", "linreg", "midpoint", "midprice", "ohlc4", "pwma", "rma", - "sinwma", "sma", "supertrend", "swma", "t3", "tema", "trima", "vwap", - "vwma", "wcp", "wma", "zlma" + "sinwma", "sma", "ssf", "supertrend", "swma", "t3", "tema", "trima", + "vwap", "vwma", "wcp", "wma", "zlma" ], # Performance "performance": ["log_return", "percent_return", "trend_return"], # Statistics "statistics": [ - "entropy", "kurtosis", "mad", "median", "quantile", "skew", - "stdev", "variance", "zscore" + "entropy", "kurtosis", "mad", "median", "quantile", "skew", "stdev", + "variance", "zscore" ], # Trend "trend": [ - "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", - "dpo", "increasing", "long_run", "psar", "qstick", "short_run", - "ttm_trend", "vortex" + "adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo", + "increasing", "long_run", "psar", "qstick", "short_run", "ttm_trend", + "vortex" ], # Volatility "volatility": [ diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 40b8f89..880debe 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1057,6 +1057,11 @@ class AnalysisIndicators(BasePandasObject): result = sma(close=close, length=length, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def ssf(self, length=None, poles=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = ssf(close=close, length=length, poles=poles, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def supertrend(self, length=None, multiplier=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 5a5ae90..2607014 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -16,6 +16,7 @@ from .pwma import pwma from .rma import rma from .sinwma import sinwma from .sma import sma +from .ssf import ssf from .supertrend import supertrend from .swma import swma from .t3 import t3 diff --git a/pandas_ta/overlap/ssf.py b/pandas_ta/overlap/ssf.py new file mode 100644 index 0000000..f07040c --- /dev/null +++ b/pandas_ta/overlap/ssf.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +from numpy import cos as npCos +from numpy import exp as npExp +from numpy import pi as npPi +from numpy import sqrt as npSqrt +from pandas import Series +from pandas_ta.utils import get_offset, verify_series + + +def ssf(close, length=None, poles=None, offset=None, **kwargs): + """Indicator: Ehler's Super Smoother Filter (SSF)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + poles = int(poles) if poles in [2, 3] else 2 + offset = get_offset(offset) + + # Calculate Result + m = close.size + ssf = close.copy() + ssf[poles:] = 0 + + if poles == 3: + x = npPi / length # x = PI / n + a0 = npExp(-x) # e^(-x) + b0 = 2 * a0 * npCos(npSqrt(3) * x) # 2e^(-x)*cos(3^(.5) * x) + c0 = a0 * a0 # e^(-2x) + + c4 = c0 * c0 # e^(-4x) + c3 = -c0 * (1 + b0) # -e^(-2x) * (1 + 2e^(-x)*cos(3^(.5) * x)) + c2 = c0 + b0 # e^(-2x) + 2e^(-x)*cos(3^(.5) * x) + c1 = 1 - c2 - c3 - c4 + + for i in range(0, m): + ssf.iloc[i] = c1 * close.iloc[i] + c2 * ssf.iloc[i - 1] + c3 * ssf.iloc[i - 2] + c4 * ssf.iloc[i - 3] + + else: # poles == 2 + x = npPi * npSqrt(2) / length # x = PI * 2^(.5) / n + a0 = npExp(-x) # e^(-x) + a1 = -a0 * a0 # -e^(-2x) + b1 = 2 * a0 * npCos(x) # 2e^(-x)*cos(x) + c1 = 1 - a1 - b1 # e^(-2x) - 2e^(-x)*cos(x) + 1 + + for i in range(0, m): + ssf.iloc[i] = c1 * close.iloc[i] + b1 * ssf.iloc[i - 1] + a1 * ssf.iloc[i - 2] + + # Offset + if offset != 0: + ssf = ssf.shift(offset) + + # Name & Category + ssf.name = f"SSF_{length}_{poles}" + ssf.category = "overlap" + + return ssf + + +ssf.__doc__ = \ +"""Ehler's Super Smoother Filter (SSF) + +Ehler's solution to reduce lag and remove aliasing noise with his research in +aerospace analog filter design. © 2013 John F. Ehlers + +Sources: + http://www.stockspotter.com/files/PredictiveIndicators.pdf + https://www.tradingview.com/script/VdJy0yBJ-Ehlers-Super-Smoother-Filter/ + https://www.mql5.com/en/code/588 + https://www.mql5.com/en/code/589 + +Calculation: + Default Inputs: + length=10, poles=[2, 3] + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + poles (int): The number of poles to use, either 2 or 3. 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.Series: New feature generated. +""" diff --git a/setup.py b/setup.py index 50d9a3f..c6bd229 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "2", "25b")), + version=".".join(("0", "2", "26b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_overlap_ext.py b/tests/test_ext_indicator_overlap_ext.py index 4e7ccf7..1cac7ff 100644 --- a/tests/test_ext_indicator_overlap_ext.py +++ b/tests/test_ext_indicator_overlap_ext.py @@ -103,6 +103,15 @@ class TestOverlapExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], "SMA_10") + def test_ssf_ext(self): + self.data.ta.ssf(append=True, poles=2) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], "SSF_10_2") + + self.data.ta.ssf(append=True, poles=3) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], "SSF_10_3") + def test_swma_ext(self): self.data.ta.swma(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index e6bb03e..88fe474 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -242,6 +242,15 @@ class TestOverlap(TestCase): except Exception as ex: error_analysis(result, CORRELATION, ex) + def test_ssf(self): + result = pandas_ta.ssf(self.close, poles=2) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "SSF_10_2") + + result = pandas_ta.ssf(self.close, poles=3) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "SSF_10_3") + def test_swma(self): result = pandas_ta.swma(self.close) self.assertIsInstance(result, Series)