diff --git a/README.md b/README.md index 65e607f..f170546 100644 --- a/README.md +++ b/README.md @@ -136,12 +136,13 @@ help(pd.DataFrame().ta.log_return) |:--------:| | ![Example Chart](/images/TA_Chart.png) | -## _Performance_ (2) +## _Performance_ (3) Use parameter: cumulative=**True** for cumulative results. * _Log Return_: **log_return** * _Percent Return_: **percent_return** +* _Trend Return_: **trend_return** | _Percent Return_ (Cumulative) with _Simple Moving Average_ (SMA) | |:--------:| diff --git a/pandas_ta/core.py b/pandas_ta/core.py index d8e4888..04468e9 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -516,6 +516,12 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **kwargs) return result + def trend_return(self, close=None, trend=None, log=True, cumulative=True, offset=None, **kwargs): + close = self._get_column(close, 'close') + trend = self._get_column(trend, f"{trend}") + result = trend_return(close=close, trend=trend, log=log, cumulative=cumulative, offset=offset, **kwargs) + self._append(result, **kwargs) + return result # Statistics Indicators diff --git a/pandas_ta/performance.py b/pandas_ta/performance.py index 7e48479..a7d58fa 100644 --- a/pandas_ta/performance.py +++ b/pandas_ta/performance.py @@ -2,7 +2,7 @@ import numpy as np import pandas as pd -from .utils import get_offset, verify_series +from .utils import get_offset, verify_series, zero @@ -54,6 +54,49 @@ def percent_return(close, length=None, cumulative=False, offset=None, **kwargs): return pct_return +def trend_return(close, trend, trend_reset=0, log=True, cumulative=False, offset=None, **kwargs): + """Indicator: Trend Return""" + # Validate Arguments + close = verify_series(close) + trend = verify_series(trend) + offset = get_offset(offset) + variable = kwargs.pop('variable', False) + + # Calculate Result + returns = log_return(close, cumulative=False) if log else percent_return(close, cumulative=False) + m = trend.size + tsum = 0 + result = [] + returns = (trend * returns).apply(zero) + # trend = trend.astype(int) + for i in range(0, m): + if trend[i] == trend_reset: + tsum = 0 + else: + return_ = returns[i] + if cumulative: + tsum += return_ + else: + tsum = return_ + result.append(tsum) + + trend_return = pd.Series(result) + + # Experimental: Add the individual return flucuations to the cumulative returns + if variable and cumulative: + trend_return += returns + + # Offset + if offset != 0: + trend_return = trend_return.shift(offset) + + # Name & Category + trend_return.name = f"{'C' if cumulative else ''}{'L' if log else 'P'}TR" + trend_return.category = 'performance' + + return trend_return + + log_return.__doc__ = \ """Log Return @@ -66,14 +109,14 @@ Sources: Calculation: Default Inputs: - length=1, cummulative=False + length=1, cumulative=False LOGRET = log( close.diff(periods=length) ) - CUMLOGRET = LOGRET.cumsum() if cummulative + CUMLOGRET = LOGRET.cumsum() if cumulative Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 20 - cummulative (bool): If True, returns the cummulative returns. Default: False + cumulative (bool): If True, returns the cumulative returns. Default: False offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -96,20 +139,67 @@ Sources: Calculation: Default Inputs: - length=1, cummulative=False + length=1, cumulative=False PCTRET = close.pct_change(length) - CUMPCTRET = PCTRET.cumsum() if cummulative + CUMPCTRET = PCTRET.cumsum() if cumulative Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 20 - cummulative (bool): If True, returns the cummulative returns. Default: False + cumulative (bool): If True, returns the cumulative returns. Default: False 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. +""" + + +trend_return.__doc__ = \ +"""Trend Return + +Calculates the (Cumulative) Returns of a Trend as defined by some conditional. +By default it calculates log returns but can also use percent change. + +Sources: Kevin Johnson + +Calculation: + Default Inputs: + trend_reset=0, log=True, cumulative=False + + sum = 0 + returns = log_return if log else percent_return # These are not cumulative + returns = (trend * returns).apply(zero) + for i, in range(0, trend.size): + if item == trend_reset: + sum = 0 + else: + return_ = returns.iloc[i] + if cumulative: + sum += return_ + else: + sum = return_ + trend_return.append(sum) + + if cumulative and variable: + trend_return += returns + +Args: + close (pd.Series): Series of 'close's + trend (pd.Series): Series of 'trend's. Preferably 0's and 1's. + trend_reset (value): Value used to identify if a trend has ended. Default: 0 + log (bool): Calculate logarithmic returns. Default: True + cumulative (bool): If True, returns the cumulative returns. Default: False + 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 + variable (bool, optional): Whether to include if return fluxuations in the cumulative returns. + Returns: pd.Series: New feature generated. """ \ No newline at end of file diff --git a/setup.py b/setup.py index 678373b..eaff0df 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.12a", + version = "0.1.13a", description=long_description, long_description=long_description, author = "Kevin Johnson", diff --git a/tests/test_indicator_performance.py b/tests/test_indicator_performance.py index 3af1710..4fa32d2 100644 --- a/tests/test_indicator_performance.py +++ b/tests/test_indicator_performance.py @@ -11,11 +11,13 @@ class TestPerformace(TestCase): def setUpClass(cls): cls.data = sample_data cls.close = cls.data['close'] + cls.islong = cls.close > pandas_ta.sma(cls.close, length=50) @classmethod def tearDownClass(cls): del cls.data del cls.close + del cls.islong def setUp(self): @@ -41,4 +43,28 @@ class TestPerformace(TestCase): def test_cum_percent_return(self): result = self.performance.percent_return(self.close, cumulative=True) - self.assertEqual(result.name, 'CUMPCTRET_1') \ No newline at end of file + self.assertEqual(result.name, 'CUMPCTRET_1') + + def test_log_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=True, cumulative=False) + self.assertEqual(result.name, 'LTR') + + def test_cum_log_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=True, cumulative=True) + self.assertEqual(result.name, 'CLTR') + + def test_variable_cum_log_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=True, cumulative=True, variable=True) + self.assertEqual(result.name, 'CLTR') + + def test_pct_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=False, cumulative=False) + self.assertEqual(result.name, 'PTR') + + def test_cum_pct_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=False, cumulative=True) + self.assertEqual(result.name, 'CPTR') + + def test_variable_pct_log_trend_return(self): + result = self.performance.trend_return(self.close, self.islong, log=False, cumulative=True, variable=True) + self.assertEqual(result.name, 'CPTR') \ No newline at end of file diff --git a/tests/test_indicator_performance_ext.py b/tests/test_indicator_performance_ext.py index ffda021..215a89d 100644 --- a/tests/test_indicator_performance_ext.py +++ b/tests/test_indicator_performance_ext.py @@ -12,10 +12,12 @@ class TestPerformaceExtension(TestCase): @classmethod def setUpClass(cls): cls.data = sample_data + cls.islong = cls.data['close'] > pandas_ta.sma(cls.data['close'], length=50) @classmethod def tearDownClass(cls): del cls.data + del cls.islong def setUp(self): @@ -30,6 +32,7 @@ class TestPerformaceExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'LOGRET_1') + def test_cum_log_return_ext(self): self.data.ta.log_return(append=True, cumulative=True) self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'CUMLOGRET_1') @@ -39,6 +42,27 @@ class TestPerformaceExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'PCTRET_1') + def test_cum_percent_return_ext(self): self.data.ta.percent_return(append=True, cumulative=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1') \ No newline at end of file + self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1') + + def test_log_trend_return_ext(self): + self.data.ta.trend_return(trend=self.islong, log=True, cumulative=False, append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'LTR') + + def test_cum_log_trend_return_ext(self): + self.data.ta.trend_return(trend=self.islong, log=True, cumulative=True, append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'CLTR') + + def test_pct_trend_return_ext(self): + self.data.ta.trend_return(trend=self.islong, log=False, cumulative=False, append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'PTR') + + def test_cum_pct_trend_return_ext(self): + self.data.ta.trend_return(trend=self.islong, log=False, cumulative=True, append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'CPTR') \ No newline at end of file