mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-09-12 12:40:39 +08:00
Merge pull request #67 from twopirllc/development
MAINT PERF ENH ema, wma and macd
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
# __Technical Analysis Library in Python 3.7__
|
||||

|
||||
|
||||
__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
File diff suppressed because one or more lines are too long
+9
-13
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user