From 198521ebae00f2f1d7cd0ddf9858abab9760d547 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Wed, 22 May 2019 14:52:47 -0700 Subject: [PATCH] fisher transform indicator and tests --- README.md | 3 +- pandas_ta/__init__.py | 1 + pandas_ta/core.py | 9 +++- pandas_ta/momentum/fisher.py | 78 ++++++++++++++++++++++++++++ setup.py | 2 +- tests/test_indicator_momentum.py | 5 ++ tests/test_indicator_momentum_ext.py | 5 ++ 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 pandas_ta/momentum/fisher.py diff --git a/README.md b/README.md index e7d9b86..3935b5f 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ help(pd.DataFrame().ta.log_return) # Technical Analysis Indicators (by Category) -## _Momentum_ (18) +## _Momentum_ (19) * _Awesome Oscillator_: **ao** * _Absolute Price Oscillator_: **apo** @@ -88,6 +88,7 @@ help(pd.DataFrame().ta.log_return) * _Commodity Channel Index_: **cci** * _Chande Momentum Oscillator_: **cmo** * _Coppock Curve_: **coppock** +* _Fisher Transform_: **fisher** * _KST Oscillator_: **kst** * _Moving Average Convergence Divergence_: **macd** * _Momentum_: **mom** diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 932aca4..5ebeb30 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -25,6 +25,7 @@ from .momentum.bop import bop from .momentum.cci import cci from .momentum.cmo import cmo from .momentum.coppock import coppock +from .momentum.fisher import fisher from .momentum.kst import kst from .momentum.macd import macd from .momentum.mom import mom diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 554d9c1..c9c612f 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -2,7 +2,6 @@ import time import pandas as pd from pandas.core.base import PandasObject - from .utils import * class BasePandasObject(PandasObject): @@ -279,6 +278,14 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **kwargs) return result + def fisher(self, high=None, low=None, length=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + from .momentum.fisher import fisher + result = fisher(high=high, low=low, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + def kst(self, close=None, roc1=None, roc2=None, roc3=None, roc4=None, sma1=None, sma2=None, sma3=None, sma4=None, signal=None, offset=None, **kwargs): close = self._get_column(close, 'close') from .momentum.kst import kst diff --git a/pandas_ta/momentum/fisher.py b/pandas_ta/momentum/fisher.py new file mode 100644 index 0000000..71cf013 --- /dev/null +++ b/pandas_ta/momentum/fisher.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +from numpy import log as nplog +from numpy import NaN as npNaN +from pandas import Series +from ..overlap.hl2 import hl2 +from ..utils import get_offset, verify_series, zero + +def fisher(high, low, length=None, offset=None, **kwargs): + """Indicator: Fisher Transform (FISHT)""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + length = int(length) if length and length > 0 else 5 + offset = get_offset(offset) + + # Calculate Result + m = high.size + hl2_ = hl2(high, low) + max_high = hl2_.rolling(length).max() + min_low = hl2_.rolling(length).min() + hl2_range = max_high - min_low + hl2_range[hl2_range < 1e-5] = 0.001 + position = (hl2_ - min_low) / hl2_range + + v = 0 + fish = 0 + result = [npNaN for _ in range(0, length - 1)] + for i in range(length - 1, m): + v = 0.66 * (position[i] - 0.5) + 0.67 * v + if v > 0.99: v = 0.999 + if v < -0.99: v = -0.999 + fish = 0.5 * (fish + nplog((1 + v) / (1 - v))) + result.append(fish) + + fisher = Series(result) + + # Offset + if offset != 0: + fisher = fisher.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + fisher.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + fisher.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + fisher.name = f"FISHERT_{length}" + fisher.category = 'momentum' + + return fisher + + + +fisher.__doc__ = \ +"""Fisher Transform (FISHT) + +Attempts to identify trend reversals. + +Sources: + https://tulipindicators.org/fisher + +Calculation: + Default Inputs: + drift=1 + +Args: + close (pd.Series): Series of 'close's + drift (int): The short period. Default: 1 + 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. +""" \ No newline at end of file diff --git a/setup.py b/setup.py index 65186ed..991c365 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "An easy to use Python 3 Pandas Extension of Technical Analys setup( name = "pandas_ta", packages = ["pandas_ta"], - version = "0.1.27b", + version = "0.1.28b", description=long_description, long_description=long_description, author = "Kevin Johnson", diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 580251d..bfda283 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -104,6 +104,11 @@ class TestMomentum(TestCase): self.assertIsInstance(result, Series) self.assertEqual(result.name, 'COPC_11_14_10') + def test_fisher(self): + result = pandas_ta.fisher(self.high, self.low) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, 'FISHERT_5') + def test_kst(self): result = pandas_ta.kst(self.close) self.assertIsInstance(result, DataFrame) diff --git a/tests/test_indicator_momentum_ext.py b/tests/test_indicator_momentum_ext.py index a9b77c4..1c4cf3d 100644 --- a/tests/test_indicator_momentum_ext.py +++ b/tests/test_indicator_momentum_ext.py @@ -53,6 +53,11 @@ class TestMomentumExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'COPC_11_14_10') + def test_fisher_ext(self): + self.data.ta.fisher(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'FISHERT_5') + def test_kst_ext(self): self.data.ta.kst(append=True) self.assertIsInstance(self.data, DataFrame)