Merge pull request #67 from twopirllc/development

MAINT PERF ENH ema, wma and macd
This commit is contained in:
Kevin Johnson
2020-07-05 15:23:39 -07:00
committed by GitHub
7 changed files with 221 additions and 204 deletions
+12 -5
View File
@@ -6,7 +6,7 @@
# __Technical Analysis Library in Python 3.7__
![Example Chart](/images/TA_Chart.png)
__Pandas Technical Analysis__ (Pandas TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators. These indicators are commonly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Simple Moving Average_ (*SMA*) _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more.
__Pandas Technical Analysis__ (Pandas TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators and Utility functions. These indicators are commonly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Simple Moving Average_ (*SMA*) _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more.
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'].
@@ -21,10 +21,13 @@ All the indicators return a named Series or a DataFrame in uppercase underscore
* Abbreviated Indicator names as listed below.
* __Extended Pandas DataFrame__ as 'ta'.
* Easily add prefixes or suffixes or both to columns names.
* Categories similar to [TA-lib](https://github.com/mrjbq7/ta-lib/tree/master/docs/func_groups).
* Categories similar to [TA-lib](https://github.com/mrjbq7/ta-lib/tree/master/docs/func_groups) and tightly correlated with TA Lib in testing.
## __Recent Changes__
* Improved the calculation performance of indicators: _Exponential Moving Averagage_
and _Weighted Moving Average_.
* Removed internal core optimizations when running ```df.ta.strategy('all')``` with multiprocessing. See the ```ta.strategy()``` method for more details.
### __New DataFrame Method:__
strategy (strategy)
@@ -61,8 +64,11 @@ All the indicators return a named Series or a DataFrame in uppercase underscore
Bollinger Bands (bbands)
Commodity Channel Index (cci)
Chande Momentum Oscillator (cmo)
Exponential Moving Average (ema)
Moving Average Convergence Divergence (macd)
Relative Vigor Index (rvgi)
Symmetric Weighted Moving Average (swma)
Weighted Moving Average (wma)
## What is a Pandas DataFrame Extension?
@@ -147,9 +153,10 @@ df.ta.strategy(verbose=True)
# Use timed if you want to see how long it takes to run.
df.ta.strategy(timed=True)
# You can change the number of cores to use. Though the
# default will usually be best
df.ta.strategy(cores=4)
# You can change the number of cores to use. The default is the the number of
# cpus you have. Not utilizing all your cores will result in quicker results.
# For instance if you have 4 CPUs, then cores=2 will be quicker.
df.ta.strategy(cores=2)
# Maybe you do not want certain indicators.
# Just exclude (a list of) them.
+165 -141
View File
File diff suppressed because one or more lines are too long
+9 -13
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from functools import wraps
from multiprocessing import cpu_count, Pool
from multiprocessing import cpu_count, Pool
from random import random
from time import perf_counter
@@ -17,9 +17,9 @@ from pandas_ta.volatility import *
from pandas_ta.volume import *
from pandas_ta.utils import *
version = ".".join(("0", "1", "73b"))
version = ".".join(("0", "1", "75b"))
def worker(args):
def mp_worker(args):
df, method, kwargs = args
if method != 'ichimoku':
@@ -350,13 +350,9 @@ class AnalysisIndicators(BasePandasObject):
current_columns = len(self._df.columns)
indicators = self.indicators(as_list=True, exclude=excluded)
# Core tuning
if cores <= 2: cores = 1
if cores == 3: cores = 2
if 4 <= cores <= 5: cores -= 2
print('[+] Strategy "All"')
if verbose:
print(f"[i] All indicators with the following arguments: {kwargs}")
print(f'[i] Indicators with the following arguments: {kwargs}')
print(f"[i] excluded[{len(excluded)}]: {', '.join(excluded)}")
if timed: stime = perf_counter()
@@ -373,7 +369,7 @@ class AnalysisIndicators(BasePandasObject):
print(f"[i] multiprocessing: {cores} of {cpu_count()} cores")
pool = Pool(cores)
result = pool.imap_unordered(
worker, ((self._df, ind, kwargs) for ind in indicators), cores
mp_worker, ((self._df, ind, kwargs) for ind in indicators), cores
)
pool.close()
pool.join()
@@ -384,7 +380,7 @@ class AnalysisIndicators(BasePandasObject):
self._append(r, **kwargs)
print(f"[i] total indicators: {len(indicators)}, columns added: {len(self._df.columns) - current_columns}")
print(f"[i] runtime: {final_time(stime)}") if timed else None
print(f"[i] runtime: {final_time(stime)}\n") if timed else None
def strategy(self, **kwargs):
@@ -660,10 +656,10 @@ class AnalysisIndicators(BasePandasObject):
return result
@finalize
def ema(self, close=None, length=None, offset=None, adjust=None, **kwargs):
def ema(self, close=None, length=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = ema(close=close, length=length, offset=offset, adjust=adjust, **kwargs)
result = ema(close=close, length=length, offset=offset, **kwargs)
return result
@finalize
+3 -4
View File
@@ -12,15 +12,14 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
signal = int(signal) if signal and signal > 0 else 9
if slow < fast:
fast, slow = slow, fast
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else fast
offset = get_offset(offset)
# Calculate Result
fastma = ema(close, length=fast, **kwargs)
slowma = ema(close, length=slow, **kwargs)
fastma = ema(close, length=fast)
slowma = ema(close, length=slow)
macd = fastma - slowma
signalma = ema(close=macd, length=signal, **kwargs)
signalma = ema(close=macd, length=signal)
histogram = macd - signalma
# Offset
+15 -30
View File
@@ -7,32 +7,19 @@ def ema(close, length=None, offset=None, **kwargs):
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 10
min_periods = kwargs.pop('min_periods', length)
adjust = kwargs.pop('adjust', True)
offset = get_offset(offset)
# min_periods = kwargs.pop('min_periods', length)
adjust = kwargs.pop('adjust', False)
sma = kwargs.pop('sma', True)
ewm = kwargs.pop('ewm', False)
win_type = kwargs.pop('win_type', None)
offset = get_offset(offset)
# Calculate Result
if ewm:
# Mathematical Implementation of an Exponential Weighted Moving Average
ema = close.ewm(span=length, min_periods=min_periods, adjust=adjust).mean()
else:
alpha = 2 / (length + 1)
if sma:
close = close.copy()
def ema_(series):
# Technical Anaylsis Definition of an Exponential Moving Average
# Slow for large series
series.iloc[1] = alpha * (series.iloc[1] - series.iloc[0]) + series.iloc[0]
return series.iloc[1]
seed = close[0:length].mean() if sma else close.iloc[0]
sma_nth = close[0:length].sum() / length
close[:length - 1] = npNaN
close.iloc[length - 1] = seed
ma = close[length - 1:].rolling(2, min_periods=2).apply(ema_, raw=False)
ema = close[:length].append(ma[1:])
close.iloc[length - 1] = sma_nth
ema = close.ewm(span=length, adjust=adjust).mean()
# Offset
if offset != 0:
@@ -61,13 +48,11 @@ Sources:
Calculation:
Default Inputs:
length=10
SMA = Simple Moving Average
if kwargs['presma']:
initial = SMA(close, length)
rest = close[length:]
close = initial + rest
length=10, adjust=False, sma=True
if sma:
sma_nth = close[0:length].sum() / length
close[:length - 1] = np.NaN
close.iloc[length - 1] = sma_nth
EMA = close.ewm(span=length, adjust=adjust).mean()
Args:
@@ -76,8 +61,8 @@ Args:
offset (int): How many periods to offset the result. Default: 0
Kwargs:
adjust (bool, optional): Default: True
sma (bool, optional): If True, uses SMA for initial value.
adjust (bool, optional): Default: False
sma (bool, optional): If True, uses SMA for initial value. Default: True
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
+2 -1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from numpy import arange as nparange
from numpy import dot as npdot
from pandas import Series
from ..utils import get_offset, verify_series
@@ -19,7 +20,7 @@ def wma(close, length=None, asc=None, offset=None, **kwargs):
def linear(w):
def _compute(x):
return (w * x).sum() / total_weight
return npdot(x, w) / total_weight
return _compute
close_ = close.rolling(length, min_periods=length)
+15 -10
View File
@@ -9,9 +9,9 @@ from functools import reduce
from operator import mul
from sys import float_info as sflt
TRADING_DAYS_IN_YEAR = 250
TRADING_HOURS_IN_DAY = 6.5
MINUTES_IN_HOUR = 60
TRADING_DAYS_PER_YEAR = 250
TRADING_HOURS_PER_DAY = 6.5
MINUTES_PER_HOUR = 60
def _above_below(
@@ -227,7 +227,10 @@ def df_error_analysis(dfA: pd.DataFrame, dfB: pd.DataFrame, **kwargs) -> pd.Data
# Find their differences
diff = dfA - dfB
df = pd.DataFrame({'diff': diff.describe()})
extra = pd.DataFrame([diff.var(), diff.mad(), diff.sem(), dfA.corr(dfB, method=corr_method)], index=['var', 'mad', 'sem', 'corr'])
extra = pd.DataFrame(
[diff.var(), diff.mad(), diff.sem(), dfA.corr(dfB, method=corr_method)],
index=['var', 'mad', 'sem', 'corr']
)
# Append the differences to the DataFrame
df = df['diff'].append(extra, ignore_index=False)[0]
@@ -343,8 +346,9 @@ def signed_series(series: pd.Series, initial: int =None) -> pd.Series:
"""Returns a Signed Series with or without an initial value
Default Example:
series = pd.Series([3, 2, 2, 1, 1, 5, 6, 6, 7, 5, 3]) and returns
sign = pd.Series([NaN, -1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0, -1.0])
series = pd.Series([3, 2, 2, 1, 1, 5, 6, 6, 7, 5])
and returns:
sign = pd.Series([NaN, -1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0])
"""
series = verify_series(series)
sign = series.diff(1)
@@ -387,9 +391,9 @@ def symmetric_triangle(n: int = None, **kwargs) -> list:
def unsigned_differences(series: pd.Series, amount: int = None, **kwargs) -> pd.Series:
"""Unsigned Differences
Returns two Series, an unsigned positive and unsigned negative series based on
the differences of the original series. The positive series are only the increases
and the negative series is only the decreases.
Returns two Series, an unsigned positive and unsigned negative series based
on the differences of the original series. The positive series are only the
increases and the negative series is only the decreases.
Default Example:
series = pd.Series([3, 2, 2, 1, 1, 5, 6, 6, 7, 5, 3]) and returns
@@ -427,5 +431,6 @@ def weights(w):
def zero(x: [int, float]) -> [int, float]:
"""If the value is close to zero, then return zero. Otherwise return the value."""
"""If the value is close to zero, then return zero.
Otherwise return the value."""
return 0 if -sflt.epsilon < x and x < sflt.epsilon else x