From 37282662cdeb86ca35131ff1ab829ef0d1e5fced Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Sun, 20 Jun 2021 10:23:00 -0700 Subject: [PATCH] ENH tos_stdevall initial code in dev --- README.md | 6 +- pandas_ta/__init__.py | 1 + pandas_ta/core.py | 5 ++ pandas_ta/statistics/__init__.py | 1 + pandas_ta/statistics/tos_stdevall.py | 104 +++++++++++++++++++++++++ setup.py | 2 +- tests/test_ext_indicator_momentum.py | 3 +- tests/test_ext_indicator_statistics.py | 13 +++- tests/test_indicator_statistics.py | 21 ++++- 9 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 pandas_ta/statistics/tos_stdevall.py diff --git a/README.md b/README.md index fafef46..52a211e 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Momentum](#momentum-40) * [Overlap](#overlap-32) * [Performance](#performance-3) - * [Statistics](#statistics-9) + * [Statistics](#statistics-10) * [Trend](#trend-18) * [Utility](#utility-5) * [Volatility](#volatility-14) @@ -110,7 +110,7 @@ $ pip install pandas_ta Latest Version -------------- -Best choice! Version: *0.2.92b* +Best choice! Version: *0.2.93b* ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta ``` @@ -774,7 +774,7 @@ Use parameter: cumulative=**True** for cumulative results. | ![Example Cumulative Percent Return](/images/SPY_CumulativePercentReturn.png) |
-### **Statistics** (9) +### **Statistics** (10) * _Entropy_: **entropy** * _Kurtosis_: **kurtosis** diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 9a54d91..8ac5197 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -65,6 +65,7 @@ Category = { # Statistics "statistics": [ "entropy", "kurtosis", "mad", "median", "quantile", "skew", "stdev", + # "tos_stdevall", "variance", "zscore" ], # Trend diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 1866aec..8569ee6 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1365,6 +1365,11 @@ class AnalysisIndicators(BasePandasObject): result = stdev(close=close, length=length, offset=offset, **kwargs) return self._post_process(result, **kwargs) + # def tos_stdevall(self, length=None, stds=None, offset=None, **kwargs): + # close = self._get_column(kwargs.pop("close", "close")) + # result = tos_stdevall(close=close, length=length, stds=stds, offset=offset, **kwargs) + # return self._post_process(result, **kwargs) + def variance(self, length=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = variance(close=close, length=length, offset=offset, **kwargs) diff --git a/pandas_ta/statistics/__init__.py b/pandas_ta/statistics/__init__.py index b7d8202..d55a7ea 100644 --- a/pandas_ta/statistics/__init__.py +++ b/pandas_ta/statistics/__init__.py @@ -6,5 +6,6 @@ from .median import median from .quantile import quantile from .skew import skew from .stdev import stdev +from .tos_stdevall import tos_stdevall from .variance import variance from .zscore import zscore diff --git a/pandas_ta/statistics/tos_stdevall.py b/pandas_ta/statistics/tos_stdevall.py new file mode 100644 index 0000000..d52a05f --- /dev/null +++ b/pandas_ta/statistics/tos_stdevall.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +from numpy import array as npArray +from numpy import arange as npArange +from numpy import polyfit as npPolyfit +from numpy import std as npStd +from pandas import DataFrame, DatetimeIndex, Series +# from pandas_ta import Imports +from .stdev import stdev as stdev +from pandas_ta.utils import get_offset, verify_series + +def tos_stdevall(close, length=None, stds=None, ddof=None, offset=None, **kwargs): + """Indicator: TD Ameritrade's Think or Swim Standard Deviation All""" + # Validate Arguments + stds = stds if isinstance(stds, list) and len(stds) > 0 else [1, 2, 3] + if min(stds) <= 0: return + if not all(i < j for i, j in zip(stds, stds[1:])): + stds = stds[::-1] + ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 1 + offset = get_offset(offset) + + if length is None: + length = close.size + _props = f"STDEVALL" + else: + length = int(length) if length and length > 2 else 30 + close = close.iloc[-length:] + _props = f"STDEVALL_{length}" + + close = verify_series(close, length) + + if close is None: return + + # Calculate Result + if isinstance(close.index, DatetimeIndex): + close_ = npArray(close) + np_index = npArange(length) + m, b = npPolyfit(np_index, close_, 1) + lr_ = m * np_index + b + else: + m, b = npPolyfit(close.index, close, 1) + lr_ = m * close.index + b + + lr = Series(lr_, index=close.index) + stdevall = stdev(Series(close), length=length, ddof=ddof) + # std = npStd(close, ddof=ddof) + + # Name and Categorize it + df = DataFrame({f"{_props}_LR": lr}, index=close.index) + for i in stds: + df[f"{_props}_L_{i}"] = lr - i * stdevall.iloc[-1] + df[f"{_props}_U_{i}"] = lr + i * stdevall.iloc[-1] + df[f"{_props}_L_{i}"].name = df[f"{_props}_U_{i}"].name = f"{_props}" + df[f"{_props}_L_{i}"].category = df[f"{_props}_U_{i}"].category = "statistics" + + # Offset + if offset != 0: + df = df.shift(offset) + + # Handle fills + if "fillna" in kwargs: + df.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + df.fillna(method=kwargs["fill_method"], inplace=True) + + # Prepare DataFrame to return + df.name = f"{_props}" + df.category = "statistics" + + return df + + +tos_stdevall.__doc__ = \ +"""TD Ameritrade's Think or Swim Standard Deviation All (TOS_STDEV) + +**UNDER DEVELOPMENT** + +A port of TD Ameritrade's Think or Swim Standard Deviation All indicator which +returns the standard deviation of data for the entire plot or for the interval +of the last bars defined by the length parameter. + +Sources: + https://tlc.thinkorswim.com/center/reference/thinkScript/Functions/Statistical/StDevAll + +Calculation: + Default Inputs: + length=30 + VAR = Variance + STDEV = variance(close, length).apply(np.sqrt) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 30 + ddof (int): Delta Degrees of Freedom. + The divisor used in calculations is N - ddof, + where N represents the number of elements. 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. +""" diff --git a/setup.py b/setup.py index f288545..2c42273 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "2", "92b")), + version=".".join(("0", "2", "93b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_momentum.py b/tests/test_ext_indicator_momentum.py index 2b3b352..ce3a5a5 100644 --- a/tests/test_ext_indicator_momentum.py +++ b/tests/test_ext_indicator_momentum.py @@ -197,8 +197,7 @@ class TestMomentumExtension(TestCase): self.data.ta.squeeze(tr=False, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual( - list(self.data.columns[-4:]), + self.assertEqual(list(self.data.columns[-4:]), ["SQZ_ON", "SQZ_OFF", "SQZ_NO", "SQZhlr_20_2.0_20_1.5"] ) diff --git a/tests/test_ext_indicator_statistics.py b/tests/test_ext_indicator_statistics.py index 436b119..c6a219d 100644 --- a/tests/test_ext_indicator_statistics.py +++ b/tests/test_ext_indicator_statistics.py @@ -1,7 +1,7 @@ from .config import sample_data from .context import pandas_ta -from unittest import TestCase +from unittest import skip, TestCase from pandas import DataFrame @@ -53,6 +53,17 @@ class TestStatisticsExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], "STDEV_30") + @skip + def test_tos_stdevall_ext(self): + self.data.ta.tos_stdevall(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-7:]), [ + "STDEVALL_LR", + "STDEVALL_L_1", "STDEVALL_U_1", + "STDEVALL_L_2", "STDEVALL_U_2", + "STDEVALL_L_3", "STDEVALL_U_3" + ]) + def test_variance_ext(self): self.data.ta.variance(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_indicator_statistics.py b/tests/test_indicator_statistics.py index 58a229a..a01beda 100644 --- a/tests/test_indicator_statistics.py +++ b/tests/test_indicator_statistics.py @@ -1,9 +1,9 @@ from .config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE from .context import pandas_ta -from unittest import TestCase, skip +from unittest import skip, TestCase import pandas.testing as pdt -from pandas import Series +from pandas import DataFrame, Series import talib as tal @@ -79,6 +79,23 @@ class TestStatistics(TestCase): except Exception as ex: error_analysis(result, CORRELATION, ex) + @skip + def test_tos_sdtevall(self): + result = pandas_ta.tos_stdevall(self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "STDEVALL") + self.assertEqual(len(result.columns), 7) + + result = pandas_ta.tos_stdevall(self.close, length=30) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "STDEVALL_30") + self.assertEqual(len(result.columns), 7) + + result = pandas_ta.tos_stdevall(self.close, length=30, stds=[1, 2]) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "STDEVALL_30") + self.assertEqual(len(result.columns), 5) + def test_variance(self): result = pandas_ta.variance(self.close) self.assertIsInstance(result, Series)