Merge pull request #1 from twopirllc/performance-indicators

added performance indicators with basic tests
This commit is contained in:
Kevin Johnson
2019-02-21 10:34:50 -08:00
committed by GitHub
7 changed files with 321 additions and 10 deletions
+4 -1
View File
@@ -123,4 +123,7 @@ ta_extension.ipynb
Charts.ipynb
pandas_pips
reqs.txt
requirements.txt
requirements.txt
test_indicator_statistics.py
test_indicator_statistics_ext.py
+72
View File
@@ -6,6 +6,78 @@ Technical Analysis (TA) is an easy to use library that is built upon Python's Pa
This version contains both the orignal code branch as well as a newly refactored branch with the option to use [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html) mode.
All the indicators return a named Series or a DataFrame in uppercase underscore parameter format. For example, MACD(fast=12, slow=26, signal=9) will return a DataFrame with columns: ['MACD_12_26_9', 'MACDH_12_26_9', 'MACDS_12_26_9'].
## New Changes
* At 70+ indicators.
* Abbreviated Indicator names as listed below.
* *Extended Pandas DataFrame* as 'ta'. See examples below.
* Parameter names are more consistent.
* Former indicators still exist and are renamed with '_depreciated' append to it's name. For example, 'average_true_range' is now 'average_true_range_depreciated'.
* Refactoring indicators into categories similar to [TA-lib](https://github.com/mrjbq7/ta-lib/tree/master/docs/func_groups).
### What is a Pandas DataFrame Extension?
A [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html), extends a DataFrame allowing one to add more functionality and features to Pandas to suit your needs. As such, it is now easier to run Technical Analysis on existing Financial Time Series without leaving the current DataFrame. This extension by default returns the Indicator result or, inclusively, it can append the result to the existing DataFrame by including the parameter
'append=True' in the method call. See examples below.
# Getting Started and Examples
## **Quick Start** using the DataFrame Extension
```python
import pandas as pd
import pandas_ta as ta
# Load data
df = pd.read_csv('symbol.csv', sep=',')
# Calculate Returns and append to the df DataFrame
df.ta.log_return(cumulative=True, append=True)
df.ta.percent_return(cumulative=True, append=True)
# New Columns with results
df.columns
# Take a peek
df.tail()
# vv Continue Post Processing vv
```
## Module and Indicator Help
```python
import pandas as pd
import pandas_ta as ta
# Help about this, 'ta', extension
help(pd.DataFrame().ta)
# List of all indicators
pd.DataFrame().ta.indicators()
# Help about the log_return indicator
help(ta.log_return)
# Help about the log_return indicator as a DataFrame Extension
help(pd.DataFrame().ta.log_return)
```
# Technical Analysis Indicators (by Category)
## _Performance_ (2)
Use parameter: cumulative=**True** for cumulative results.
* _Log Return_: **log_return**
* _Percent Return_: **percent_return**
| _Percent Return_ (Cumulative) with _Simple Moving Average_ (SMA) |
|:--------:|
| ![Example Cumulative Percent Return](/images/SPY_CumulativePercentReturn.png) |
# Inspiration
+26 -7
View File
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
import time
import pandas as pd
from .utils import *
from pandas.core.base import PandasObject
from .utils import *
from .performance import *
class BasePandasObject(PandasObject):
@@ -122,12 +122,10 @@ class AnalysisIndicators(BasePandasObject):
return indicator
else:
# self.help()
pass
self.help()
except:
# self.help()
pass
self.help()
def _append(self, result=None, **kwargs):
@@ -209,6 +207,7 @@ class AnalysisIndicators(BasePandasObject):
header = f"pandas.ta - Technical Analysis Indicators"
helper_methods = ['indicators', 'constants'] # Public non-indicator methods
exclude_methods = kwargs.pop('exclude', None)
as_list = kwargs.pop('as_list', False)
ta_indicators = list((x for x in dir(pd.DataFrame().ta) if not x.startswith('_') and not x.endswith('_')))
for x in helper_methods:
@@ -218,6 +217,9 @@ class AnalysisIndicators(BasePandasObject):
for x in exclude_methods:
ta_indicators.remove(x)
if as_list:
return ta_indicators
total_indicators = len(ta_indicators)
s = f"{header}\nTotal Indicators: {total_indicators}\n"
if total_indicators > 0:
@@ -225,4 +227,21 @@ class AnalysisIndicators(BasePandasObject):
# print(f"{s}Abbreviations:\n {abbr_list}")
print(f"{s}Abbreviations:\n {abbr_list}")
else:
print(s)
print(s)
def log_return(self, close=None, length=None, cumulative=False, percent=False, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = log_return(close=close, length=length, cumulative=cumulative, percent=percent, offset=offset, **kwargs)
self._append(result, **kwargs)
# print(f"result:\n{result}")
return result
def percent_return(self, close=None, length=None, cumulative=False, percent=False, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = percent_return(close=close, length=length, cumulative=cumulative, percent=percent, offset=offset, **kwargs)
self._append(result, **kwargs)
return result
+114
View File
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from .utils import get_offset, verify_series
def log_return(close, length=None, cumulative=False, offset=None, **kwargs):
"""Indicator: Log Return"""
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 1
offset = get_offset(offset)
# Calculate Result
log_return = np.log(close).diff(periods=length)
if cumulative:
log_return = log_return.cumsum()
# Offset
if offset != 0:
log_return = log_return.shift(offset)
# Name & Category
log_return.name = f"{'CUM' if cumulative else ''}LOGRET_{length}"
log_return.category = 'performance'
return log_return
def percent_return(close, length=None, cumulative=False, offset=None, **kwargs):
"""Indicator: Percent Return"""
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 1
offset = get_offset(offset)
# Calculate Result
pct_return = close.pct_change(length)
if cumulative:
pct_return = pct_return.cumsum()
# Offset
if offset != 0:
pct_return = pct_return.shift(offset)
# Name & Category
pct_return.name = f"{'CUM' if cumulative else ''}PCTRET_{length}"
pct_return.category = 'performance'
return pct_return
log_return.__doc__ = \
"""Log Return
Calculates the logarithmic return of a Series.
See also: help(df.ta.log_return) for additional **kwargs a valid 'df'.
Sources:
https://stackoverflow.com/questions/31287552/logarithmic-returns-in-pandas-dataframe
Calculation:
Default Inputs:
length=1, cummulative=False
LOGRET = log( close.diff(periods=length) )
CUMLOGRET = LOGRET.cumsum() if cummulative
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
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.
"""
percent_return.__doc__ = \
"""Percent Return
Calculates the percent return of a Series.
See also: help(df.ta.percent_return) for additional **kwargs a valid 'df'.
Sources:
https://stackoverflow.com/questions/31287552/logarithmic-returns-in-pandas-dataframe
Calculation:
Default Inputs:
length=1, cummulative=False
PCTRET = close.pct_change(length)
CUMPCTRET = PCTRET.cumsum() if cummulative
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
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.
"""
+13 -2
View File
@@ -6,7 +6,7 @@ long_description = "A Python 3 Pandas Extension of Technical Analysis Indicators
setup(
name = "pandas_ta",
packages = ["pandas_ta"],
version = "0.0.3a",
version = "0.0.4a",
description=long_description,
long_description=long_description,
author = "Kevin Johnson",
@@ -28,5 +28,16 @@ setup(
'Intended Audience :: Financial and Insurance Industry',
'Topic :: Office/Business :: Financial :: Investment',
],
# zip_safe=False,
package_data={
'data': ['data/*.csv'],
},
install_requires=['pandas'],
# List additional groups of dependencies here (e.g. development dependencies).
# You can install these using the following syntax, for example:
# $ pip install -e .[dev,test]
extras_require = {
'dev': ['ta-lib', 'jupyterlab'],
'test': ['ta-lib'],
},
)
+43
View File
@@ -0,0 +1,43 @@
from unittest import TestCase
import numpy.testing as npt
import pandas.util.testing as pdt
from pandas import DataFrame, read_csv, Series
from pandas_ta import performance
class TestPerformace(TestCase):
@classmethod
def setUpClass(cls):
cls.data = read_csv('data/sample.csv', index_col=0, parse_dates=True, infer_datetime_format=False, keep_date_col=True)
cls.close = cls.data['close']
@classmethod
def tearDownClass(cls):
del cls.data
del cls.close
def setUp(self):
self.performance = performance
def tearDown(self):
del self.performance
def test_log_return(self):
log_return = self.performance.log_return(self.close)
self.assertIsInstance(log_return, Series)
self.assertEqual(log_return.name, 'LOGRET_1')
cumlog_return = self.performance.log_return(self.close, cumulative=True)
self.assertEqual(cumlog_return.name, 'CUMLOGRET_1')
def test_percent_return(self):
percent_return = self.performance.percent_return(self.close)
self.assertIsInstance(percent_return, Series)
self.assertEqual(percent_return.name, 'PCTRET_1')
cumpercent_return = self.performance.percent_return(self.close, cumulative=True)
self.assertEqual(cumpercent_return.name, 'CUMPCTRET_1')
+49
View File
@@ -0,0 +1,49 @@
from unittest import TestCase
import numpy.testing as npt
import pandas.util.testing as pdt
from pandas import DataFrame, read_csv, Series
import pandas_ta as ta
class TestPerformaceExtension(TestCase):
@classmethod
def setUpClass(cls):
cls.data = read_csv('data/sample.csv', index_col=0, parse_dates=True, infer_datetime_format=False, keep_date_col=True)
cls.close = cls.data['close']
@classmethod
def tearDownClass(cls):
del cls.data
del cls.close
def setUp(self):
pass
def tearDown(self):
pass
def test_log_return_ext(self):
self.data.ta.log_return(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'LOGRET_1')
self.data.ta.log_return(append=True, cumulative=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'CUMLOGRET_1')
def test_percent_return_ext(self):
self.data.ta.percent_return(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'PCTRET_1')
self.data.ta.percent_return(append=True, cumulative=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1')
# Example
# pta_sma = self.data[self.data.columns[-1]]
# tal_sma = tal.SMA(self.close)
# pdt.assert_series_equal(pta_sma, tal_sma)