From 028aafbe85e327f9d3df010a8bbecae94cdb9916 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Tue, 19 May 2020 16:00:14 -0700 Subject: [PATCH] ENH added Chande Kroll Stop indicator --- README.md | 9 +++- pandas_ta/__init__.py | 1 + pandas_ta/core.py | 9 ++++ pandas_ta/trend/cksp.py | 89 +++++++++++++++++++++++++++++++ setup.py | 3 +- tests/test_indicator_overlap.py | 2 +- tests/test_indicator_trend.py | 5 ++ tests/test_indicator_trend_ext.py | 5 ++ 8 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 pandas_ta/trend/cksp.py diff --git a/README.md b/README.md index 2522549..edf6dca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +[![Python Version](https://img.shields.io/pypi/pyversions/pandas_ta.svg)](https://pypi.org/project/pandas_ta/) +[![PyPi Version](https://img.shields.io/pypi/v/pandas_ta.svg)](https://pypi.org/project/pandas_ta/) +[![Package Status](https://img.shields.io/pypi/status/pandas_ta.svg)](https://pypi.org/project/pandas_ta/) +[![Downloads](https://img.shields.io/pypi/dm/pandas_ta.svg?style=flat)](https://pypistats.org/packages/pandas_ta) + # Technical Analysis Library in Python 3.7 ![Example Chart](/images/TA_Chart.png) @@ -21,6 +26,7 @@ All the indicators return a named Series or a DataFrame in uppercase underscore * Added indicators: - __Bias__ (bias) - __Choppiness Index__ (chop) + - __Chande Kroll Stop__ (cksp) - __KDJ__ (kdj) - __Parabolic Stop and Reverse__ (psar) - __Price Distance__ (pdist) @@ -212,12 +218,13 @@ Use parameter: cumulative=**True** for cumulative results. |:--------:| | ![Example Z Score](/images/SPY_ZScore.png) | -## _Trend_ (13) +## _Trend_ (14) * _Average Directional Movement Index_: **adx** * _Archer Moving Averages Trends_: **amat** * _Aroon Oscillator_: **aroon** * _Choppiness Index_: **chop** +* _Chande Kroll Stop_: **cksp** * _Decreasing_: **decreasing** * _Detrended Price Oscillator_: **dpo** * _Increasing_: **increasing** diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 64d5511..8b55df2 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -92,6 +92,7 @@ from .trend.adx import adx from .trend.amat import amat from .trend.aroon import aroon from .trend.chop import chop +from .trend.cksp import cksp from .trend.decreasing import decreasing from .trend.dpo import dpo from .trend.increasing import increasing diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 698b280..8b976da 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -767,6 +767,15 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **kwargs) return result + def cksp(self, high=None, low=None, close=None, p=None, x=None, q=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + from pandas_ta.trend.cksp import cksp + result = cksp(high=high, low=low, close=close, p=p, x=x, q=q, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + def decreasing(self, close=None, length=None, asint=True, offset=None, **kwargs): close = self._get_column(close, 'close') from pandas_ta.trend.decreasing import decreasing diff --git a/pandas_ta/trend/cksp.py b/pandas_ta/trend/cksp.py new file mode 100644 index 0000000..fb1bc07 --- /dev/null +++ b/pandas_ta/trend/cksp.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from ..volatility.atr import atr +from ..utils import get_offset, verify_series + +def cksp(high, low, close, p=None, x=None, q=None, offset=None, **kwargs): + """Indicator: Chande Kroll Stop (CKSP)""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + p = int(p) if p and p > 0 else 10 + x = float(x) if x and x > 0 else 1 + q = int(q) if q and q > 0 else 9 + offset = get_offset(offset) + + # Calculate Result + atr_ = atr(high=high, low=low, close=close, length=p) + + long_stop_ = high.rolling(p).max() - x * atr_ + long_stop = long_stop_.rolling(q).max() + + short_stop_ = high.rolling(p).min() + x * atr_ + short_stop = short_stop_.rolling(q).min() + + # Offset + if offset != 0: + long_stop = long_stop.shift(offset) + short_stop = short_stop.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + long_stop.fillna(kwargs['fillna'], inplace=True) + short_stop.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + long_stop.fillna(method=kwargs['fill_method'], inplace=True) + short_stop.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + _props = f"_{p}_{x}_{q}" + long_stop.name = f"CKSPl{_props}" + short_stop.name = f"CKSPs{_props}" + long_stop.category = short_stop.category = 'trend' + + # Prepare DataFrame to return + ckspdf = DataFrame({long_stop.name: long_stop, short_stop.name: short_stop}) + ckspdf.name = f"CKSP{_props}" + ckspdf.category = 'trend' + + return ckspdf + + + +cksp.__doc__ = \ +"""Chande Kroll Stop (CKSP) + +The Tushar Chande and Stanley Kroll in their book +“The New Technical Trader”. It is a trend-following indicator, +identifying your stop by calculating the average true range of +the recent market volatility. + +Sources: + https://www.multicharts.com/discussion/viewtopic.php?t=48914 + +Calculation: + Default Inputs: + p=10, x=1, q=9 + ATR = Average True Range + + LS0 = high.rolling(p).max() - x * ATR(length=p) + LS = LS0.rolling(q).max() + + SS0 = high.rolling(p).min() + x * ATR(length=p) + SS = SS0.rolling(q).min() + +Args: + close (pd.Series): Series of 'close's + p (int): ATR and first stop period. Default: 10 + x (float): ATR scalar. Default: 1 + q (int): Second stop period. Default: 9 + 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.DataFrame: long and short columns. +""" \ No newline at end of file diff --git a/setup.py b/setup.py index 84edc3f..984933d 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "An easy to use Python 3 Pandas Extension with 95+ Technical setup( name ="pandas_ta", packages =['pandas_ta', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], - version ="0.1.45b", + version ="0.1.46b", description =long_description, long_description =long_description, author ="Kevin Johnson", @@ -20,6 +20,7 @@ setup( license ="The MIT License (MIT)", classifiers =[ 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Development Status :: 4 - Beta', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index 6d8526f..30890ac 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -34,7 +34,7 @@ class TestOverlap(TestCase): def setUp(self): pass def tearDown(self): pass - + def test_dema(self): result = pandas_ta.dema(self.close) diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py index 3aa6810..a76cf28 100644 --- a/tests/test_indicator_trend.py +++ b/tests/test_indicator_trend.py @@ -81,6 +81,11 @@ class TestTrend(TestCase): self.assertIsInstance(result, Series) self.assertEqual(result.name, 'CHOP_14_1_100') + def test_cksp(self): + result = pandas_ta.cksp(self.high, self.low, self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, 'CKSP_10_1_9') + def test_decreasing(self): result = pandas_ta.decreasing(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_trend_ext.py b/tests/test_indicator_trend_ext.py index d6b0885..06e2a7c 100644 --- a/tests/test_indicator_trend_ext.py +++ b/tests/test_indicator_trend_ext.py @@ -43,6 +43,11 @@ class TestTrendExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'CHOP_14_1_100') + def test_cksp_ext(self): + self.data.ta.cksp(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'CKSPs_10_1_9') + def test_decreasing_ext(self): self.data.ta.decreasing(append=True) self.assertIsInstance(self.data, DataFrame)