diff --git a/.gitignore b/.gitignore index 5d129902..dc05d781 100644 --- a/.gitignore +++ b/.gitignore @@ -131,7 +131,6 @@ AlphaVantageAPI/ # Data & NB Exclusions *.csv -jnb/*.ipynb data/datas.csv data/f500.csv data/GLD_D_tv.csv @@ -142,4 +141,6 @@ data/SPY_D_TV2.csv data/SPY_D_TV3.csv data/TV_5min.csv data/tulip.csv -examples/*.csv \ No newline at end of file +examples/*.csv +jnb/*.ipynb +jnb/*.txt \ No newline at end of file diff --git a/README.md b/README.md index 0bfd4544..fe3e28b7 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,11 @@ Pandas TA - A Technical Analysis Library in Python 3 [![Downloads](https://img.shields.io/pypi/dm/pandas_ta?style=flat)](https://pypistats.org/packages/pandas_ta) [![Stars](https://img.shields.io/github/stars/twopirllc/pandas-ta?style=flat)](#stars) [![Forks](https://img.shields.io/github/forks/twopirllc/pandas-ta?style=flat)](#forks) -[![Used By](https://img.shields.io/badge/used_by-146-orange.svg?style=flat)](#usedby) +[![Used By](https://img.shields.io/badge/used_by-170-orange.svg?style=flat)](#usedby) [![Contributors](https://img.shields.io/github/contributors/twopirllc/pandas-ta?style=flat)](#contributors) [![Issues](https://img.shields.io/github/issues-raw/twopirllc/pandas-ta?style=flat)](#issues) [![Closed Issues](https://img.shields.io/github/issues-closed-raw/twopirllc/pandas-ta?style=flat)](#closed-issues) +[![Buy Me a Coffee](https://img.shields.io/badge/buy_me_a_coffee-orange.svg?style=flat)](https://www.buymeacoffee.com/twopirllc) @@ -55,7 +56,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Candles](#candles-64) * [Cycles](#cycles-1) * [Momentum](#momentum-41) - * [Overlap](#overlap-32) + * [Overlap](#overlap-33) * [Performance](#performance-3) * [Statistics](#statistics-11) * [Trend](#trend-18) @@ -68,11 +69,10 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Breaking Indicators](#breaking-indicators) * [New Indicators](#new-indicators) * [Updated Indicators](#updated-indicators) +* [Sources](#sources) +* [Support](#support) - - -
@@ -80,21 +80,23 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * Has 130+ indicators and utility functions. * **BETA** Also Pandas TA will run TA Lib's version, this includes TA Lib's 63 Chart Patterns. -* Indicators are tightly correlated with the de facto [TA Lib](https://github.com/mrjbq7/ta-lib) if they share common indicators. +* Indicators in Python are tightly correlated with the _de facto_ [TA Lib](https://github.com/mrjbq7/ta-lib) if they share common indicators. +* If TA Lib is also installed, TA Lib computations are enabled by default but can be disabled disabled per indicator by using the argument ```talib=False```. + * For instance to disable TA Lib calculation for **stdev**: ```ta.stdev(df["close"], length=30, talib=False)```. +* **NEW**! Include External Custom Indicators independent of the builtin Pandas TA indicators. For more information, see ```import_dir``` documentation under ```/pandas_ta/custom.py```. * Example Jupyter Notebook with **vectorbt** Portfolio Backtesting with Pandas TA's ```ta.tsignals``` method. * Have the need for speed? By using the DataFrame _strategy_ method, you get **multiprocessing** for free! __Conditions permitting__. * Easily add _prefixes_ or _suffixes_ or _both_ to columns names. Useful for Custom Chained Strategies. * Example Jupyter Notebooks under the [examples](https://github.com/twopirllc/pandas-ta/tree/main/examples) directory, including how to create Custom Strategies using the new [__Strategy__ Class](https://github.com/twopirllc/pandas-ta/tree/main/examples/PandaTA_Strategy_Examples.ipynb) -* Potential Data Leaks: **ichimoku** and **dpo**. See indicator list below for details. +* Potential Data Leaks: **dpo** and **ichimoku**. See indicator list below for details. Set ```lookahead=False``` to disable.
**Under Development** =================== -**Pandas TA** checks if the user has some common trading packages installed including but not limited to: [**TA Lib**](https://mrjbq7.github.io/ta-lib/), [**Vector BT**](https://github.com/polakowo/vectorbt), [**YFinance**](https://github.com/ranaroussi/yfinance) ... Much of which is experimental and likely to break until it stabilizes more. +**Pandas TA** checks if the user has some common trading packages installed including but not limited to: [**TA Lib**](https://mrjbq7.github.io/ta-lib/), [**Vector BT**](https://github.com/polakowo/vectorbt), [**YFinance**](https://github.com/ranaroussi/yfinance) ... Much of which is _experimental_ and likely to break until it stabilizes more. * If **TA Lib** installed, existing indicators will _eventually_ get a **TA Lib** version. * Easy Downloading of _ohlcv_ data using [yfinance](https://github.com/ranaroussi/yfinance). See ```help(ta.ticker)``` and ```help(ta.yf)``` and examples below. -* Hopefully soon a Pandas TA _YAML_ configuration file contained in ```~/pandas_ta/``` can be implemented. To see the proposed specification and leave comments and suggestions on it's implementation, see Issue [#258](https://github.com/twopirllc/pandas-ta/issues/258). * Some Common Performance Metrics
@@ -104,14 +106,14 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever Stable ------ -The ```pip``` version is the last stable release. Version: *0.3.02b* +The ```pip``` version is the last stable release. Version: *0.3.14b* ```sh $ pip install pandas_ta ``` Latest Version -------------- -Best choice! Version: *0.3.02b* +Best choice! Version: *0.3.14b* * Includes all fixes and updates between **pypi** and what is covered in this README. ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta @@ -158,6 +160,8 @@ df.tail()
# **Help** +**Some** indicator arguments have been reordered for consistency. Use ```help(ta.indicator_name)``` for more information or make a Pull Request to improve documentation. + ```python import pandas as pd import pandas_ta as ta @@ -208,9 +212,9 @@ Thanks for using **Pandas TA**! **Contributors** ================ -_Thank you for your contributions!_ +_Thank you for your contributions!_ -[AbyssAlora](https://github.com/AbyssAlora) | [alexonab](https://github.com/alexonab) | [allahyarzadeh](https://github.com/allahyarzadeh) | [bizso09](https://github.com/bizso09) | [CMobley7](https://github.com/CMobley7) | [codesutras](https://github.com/codesutras) | [DannyMartens](https://github.com/DannyMartens) | [DrPaprikaa](https://github.com/DrPaprikaa) | [daikts](https://github.com/daikts) | [delicateear](https://github.com/delicateear) | [dorren](https://github.com/dorren) | [edwardwang1](https://github.com/edwardwang1) | [FGU1](https://github.com/FGU1) | [ffhirata](https://github.com/ffhirata) | [floatinghotpot](https://github.com/floatinghotpot) | [GSlinger](https://github.com/gslinger) | [JoeSchr](https://github.com/JoeSchr) | [lluissalord](https://github.com/lluissalord) | [luisbarrancos](https://github.com/luisbarrancos) | [M6stafa](https://github.com/M6stafa) | [maxdignan](https://github.com/maxdignan) | [mchant](https://github.com/mchant) | [moritzgun](https://github.com/moritzgun) | [NkosenhleDuma](https://github.com/NkosenhleDuma) | [nicoloridulfo](https://github.com/nicoloridulfo) | [pbrumblay](https://github.com/pbrumblay) | [RajeshDhalange](https://github.com/RajeshDhalange) | [rengel8](https://github.com/rengel8) | [rluong003](https://github.com/rluong003) | [SoftDevDanial](https://github.com/SoftDevDanial) | [tg12](https://github.com/tg12) | [twrobel](https://github.com/twrobel) | [WellMaybeItIs](https://github.com/WellMaybeItIs) | [whubsch](https://github.com/whubsch) | [witokondoria](https://github.com/witokondoria) | [wouldayajustlookatit](https://github.com/wouldayajustlookatit) | [YuvalWein](https://github.com/YuvalWein) | [zlpatel](https://github.com/zlpatel) +
@@ -719,7 +723,7 @@ df = df.ta.cdl_pattern(name=["doji", "inside"])
-### **Overlap** (32) +### **Overlap** (33) * _Arnaud Legoux Moving Average_: **alma** * _Double Exponential Moving Average_: **dema** @@ -733,7 +737,8 @@ df = df.ta.cdl_pattern(name=["doji", "inside"]) * _Holt-Winter Moving Average_: **hwma** * _Ichimoku Kinkō Hyō_: **ichimoku** * Returns two DataFrames. For more information: ```help(ta.ichimoku)```. - * Drop the Chikou Span Column, the final column of the first resultant DataFrame, remove potential data leak. + * ```lookahead=False``` drops the Chikou Span Column to prevent potential data leak. +* _Jurik Moving Average_: **jma** * _Kaufman's Adaptive Moving Average_: **kama** * _Linear Regression_: **linreg** * _McGinley Dynamic_: **mcgd** @@ -807,7 +812,7 @@ Use parameter: cumulative=**True** for cumulative results. * Formally: **linear_decay** * _Decreasing_: **decreasing** * _Detrended Price Oscillator_: **dpo** - * Set ```centered=False``` to remove potential data leak. + * Set ```lookahead=False``` to disable centering and remove potential data leak. * _Increasing_: **increasing** * _Long Run_: **long_run** * _Parabolic Stop and Reverse_: **psar** @@ -964,6 +969,7 @@ trading account, or fund. See ```help(ta.drawdown)``` * _Cross Signals_ (**xsignals**) was created by Kevin Johnson. It is a wrapper of Trade Signals that returns Trends, Trades, Entries and Exits. Cross Signals are commonly used for **bbands**, **rsi**, **zscore** crossing some value either above or below two values at different times. See ```help(ta.xsignals)``` * _Directional Movement_ (**dm**) developed by J. Welles Wilder in 1978 attempts to determine which direction the price of an asset is moving. See ```help(ta.dm)``` * _Even Better Sinewave_ (**ebsw**) measures market cycles and uses a low pass filter to remove noise. See: ```help(ta.ebsw)``` +* _Jurik Moving Average_ (**jma**) attempts to eliminate noise to see the "true" underlying activity.. See: ```help(ta.jma)``` * _Klinger Volume Oscillator_ (**kvo**) was developed by Stephen J. Klinger. It is designed to predict price reversals in a market by comparing volume to price.. See ```help(ta.kvo)``` * _Schaff Trend Cycle_ (**stc**) is an evolution of the popular MACD incorportating two cascaded stochastic calculations with additional smoothing. See ```help(ta.stc)``` * _Squeeze Pro_ (**squeeze_pro**) is an extended version of "TTM Squeeze" from John Carter. See ```help(ta.squeeze_pro)``` @@ -976,22 +982,40 @@ of the last bars defined by the length parameter. See ```help(ta.tos_stdevall)``
## **Updated Indicators** + +* _Acceleration Bands_ (**accbands**) Argument ```mamode``` renamed to ```mode```. See ```help(ta.accbands)```. * _ADX_ (**adx**): Added ```mamode``` with default "**RMA**" and with the same ```mamode``` options as TradingView. New argument ```lensig``` so it behaves like TradingView's builtin ADX indicator. See ```help(ta.adx)```. * _Archer Moving Averages Trends_ (**amat**): Added ```drift``` argument and more descriptive column names. * _Average True Range_ (**atr**): The default ```mamode``` is now "**RMA**" and with the same ```mamode``` options as TradingView. See ```help(ta.atr)```. * _Bollinger Bands_ (**bbands**): New argument ```ddoff``` to control the Degrees of Freedom. Also included BB Percent (BBP) as the final column. Default is 0. See ```help(ta.bbands)```. * _Choppiness Index_ (**chop**): New argument ```ln``` to use Natural Logarithm (True) instead of the Standard Logarithm (False). Default is False. See ```help(ta.chop)```. * _Chande Kroll Stop_ (**cksp**): Added ```tvmode``` with default ```True```. When ```tvmode=False```, **cksp** implements “The New Technical Trader” with default values. See ```help(ta.cksp)```. +* _Chande Momentum Oscillator_ (**cmo**): New argument ```talib``` will use TA Lib's version and if TA Lib is installed. Default is True. See ```help(ta.cmo)```. * _Decreasing_ (**decreasing**): New argument ```strict``` checks if the series is continuously decreasing over period ```length``` with a faster calculation. Default: ```False```. The ```percent``` argument has also been added with default None. See ```help(ta.decreasing)```. * _Increasing_ (**increasing**): New argument ```strict``` checks if the series is continuously increasing over period ```length``` with a faster calculation. Default: ```False```. The ```percent``` argument has also been added with default None. See ```help(ta.increasing)```. +* _Klinger Volume Oscillator_ (**kvo**): Implements TradingView's Klinger Volume Oscillator version. See ```help(ta.kvo)```. +* _Linear Regression_ (**linreg**): Checks **numpy**'s version to determine whether to utilize the ```as_strided``` method or the newer ```sliding_window_view``` method. This should resolve Issues with Google Colab and it's delayed dependency updates as well as TensorFlow's dependencies as discussed in Issues [#285](https://github.com/twopirllc/pandas-ta/issues/285) and [#329](https://github.com/twopirllc/pandas-ta/issues/329). +* _Moving Average Convergence Divergence_ (**macd**): New argument ```asmode``` enables AS version of MACD. Default is False. See ```help(ta.macd)```. * _Parabolic Stop and Reverse_ (**psar**): Bug fix and adjustment to match TradingView's ```sar```. New argument ```af0``` to initialize the Acceleration Factor. See ```help(ta.psar)```. * _Percentage Price Oscillator_ (**ppo**): Included new argument ```mamode``` as an option. Default is **sma** to match TA Lib. See ```help(ta.ppo)```. +* _True Strength Index_ (**tsi**): Added ```signal``` with default ```13``` and Signal MA Mode ```mamode``` with default **ema** as arguments. See ```help(ta.tsi)```. * _Volume Profile_ (**vp**): Calculation improvements. See [Pull Request #320](https://github.com/twopirllc/pandas-ta/pull/320) See ```help(ta.vp)```. * _Volume Weighted Moving Average_ (**vwma**): Fixed bug in DataFrame Extension call. See ```help(ta.vwma)```. * _Volume Weighted Average Price_ (**vwap**): Added a new parameter called ```anchor```. Default: "D" for "Daily". See [Timeseries Offset Aliases](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#timeseries-offset-aliases) for additional options. **Requires** the DataFrame index to be a DatetimeIndex. See ```help(ta.vwap)```. +* _Volume Weighted Moving Average_ (**vwma**): Fixed bug in DataFrame Extension call. See ```help(ta.vwma)```. * _Z Score_ (**zscore**): Changed return column name from ```Z_length``` to ```ZS_length```. See ```help(ta.zscore)```.
# **Sources** [Original TA-LIB](http://ta-lib.org/) | [TradingView](http://www.tradingview.com) | [Sierra Chart](https://search.sierrachart.com/?Query=indicators&submitted=true) | [MQL5](https://www.mql5.com) | [FM Labs](https://www.fmlabs.com/reference/default.htm) | [Pro Real Code](https://www.prorealcode.com/prorealtime-indicators) | [User 42](https://user42.tuxfamily.org/chart/manual/index.html) + +
+ +# **Support** + +Feeling generous, like the package or want to see it become more a mature package? + +### Consider +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/twopirllc) + diff --git a/examples/ni.py b/examples/ni.py new file mode 100644 index 00000000..41aedc0c --- /dev/null +++ b/examples/ni.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from pandas_ta.overlap import sma +from pandas_ta.utils import get_offset, verify_series + +# - Standard definition of your custom indicator function (including docs)- + +def ni(close, length=None, centered=False, offset=None, **kwargs): + """ + Example indicator ni + """ + # Validate Arguments + length = int(length) if length and length > 0 else 20 + close = verify_series(close, length) + offset = get_offset(offset) + + if close is None: return + + # Calculate Result + t = int(0.5 * length) + 1 + ma = sma(close, length) + + ni = close - ma.shift(t) + if centered: + ni = (close.shift(t) - ma).shift(-t) + + # Offset + if offset != 0: + ni = ni.shift(offset) + + # Handle fills + if "fillna" in kwargs: + ni.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + ni.fillna(method=kwargs["fill_method"], inplace=True) + + # Name and Categorize it + ni.name = f"ni_{length}" + ni.category = "trend" + + return ni + +ni.__doc__ = \ +"""Example indicator (NI) + +Is an indicator provided solely as an example + +Sources: + https://github.com/twopirllc/pandas-ta/issues/264 + +Calculation: + Default Inputs: + length=20, centered=False + SMA = Simple Moving Average + t = int(0.5 * length) + 1 + + ni = close.shift(t) - SMA(close, length) + if centered: + ni = ni.shift(-t) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 20 + centered (bool): Shift the ni back by int(0.5 * length) + 1. 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. +""" + +# - Define a matching class method -------------------------------------------- + +def ni_method(self, length=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = ni(close=close, length=length, offset=offset, **kwargs) + return self._post_process(result, **kwargs) \ No newline at end of file diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index d3a5d317..0e740b84 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -55,9 +55,9 @@ # Overlap "overlap": [ "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3", "hma", "ichimoku", - "kama", "linreg", "mcgd", "midpoint", "midprice", "ohlc4", "pwma", "rma", - "sinwma", "sma", "ssf", "supertrend", "swma", "t3", "tema", "trima", - "vidya", "vwap", "vwma", "wcp", "wma", "zlma" + "jma", "kama", "linreg", "mcgd", "midpoint", "midprice", "ohlc4", + "pwma", "rma", "sinwma", "sma", "ssf", "supertrend", "swma", "t3", + "tema", "trima", "vidya", "vwap", "vwma", "wcp", "wma", "zlma" ], # Performance "performance": ["log_return", "percent_return"], diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 7eba78e6..fdee64ab 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -4,6 +4,7 @@ from pathlib import Path from time import perf_counter from typing import List, Tuple +from warnings import simplefilter import pandas as pd from numpy import log10 as npLog10 @@ -250,15 +251,15 @@ class AnalysisIndicators(BasePandasObject): _time_range = "years" _last_run = get_time(_exchange, to_string=True) - # def __init__(self, pandas_obj): - # # self._validate(pandas_obj) - # self._df = pandas_obj - # self._last_run = get_time(self._exchange, to_string=True) + def __init__(self, pandas_obj): + self._validate(pandas_obj) + self._df = pandas_obj + self._last_run = get_time(self._exchange, to_string=True) - # @staticmethod - # def _validate(df: Tuple[pd.DataFrame, pd.Series]): - # if isinstance(df, pd.Series) or isinstance(df, pd.DataFrame): - # raise AttributeError("[X] Must be either a Pandas Series or DataFrame.") + @staticmethod + def _validate(obj: Tuple[pd.DataFrame, pd.Series]): + if not isinstance(obj, pd.DataFrame) and not isinstance(obj, pd.Series): + raise AttributeError("[X] Must be either a Pandas Series or DataFrame.") # DataFrame Behavioral Methods def __call__( @@ -400,8 +401,9 @@ def _append(self, result=None, **kwargs) -> None: df = self._df if df is None or result is None: return else: + simplefilter(action="ignore", category=pd.errors.PerformanceWarning) if "col_names" in kwargs and not isinstance(kwargs["col_names"], tuple): - kwargs["col_names"] = (kwargs["col_names"],) + kwargs["col_names"] = (kwargs["col_names"],) # Note: tuple(kwargs["col_names"]) doesn't work if isinstance(result, pd.DataFrame): # If specified in kwargs, rename the columns. @@ -761,10 +763,10 @@ def strategy(self, *args, **kwargs): else: # Without multiprocessing: if verbose: + _col_msg = f"[i] No mulitproccessing (cores = 0)." if has_col_names: - print(f"[i] No mulitproccessing support for 'col_names' option.") - else: - print(f"[i] No mulitproccessing (cores = 0).") + _col_msg = f"[i] No mulitproccessing support for 'col_names' option." + print(_col_msg) if mode["custom"]: if Imports["tqdm"] and verbose: @@ -784,6 +786,7 @@ def strategy(self, *args, **kwargs): else: for ind in ta: getattr(self, ind)(*tuple(), **kwargs) + self._last_run = get_time(self.exchange, to_string=True) # Apply prefixes/suffixes and appends indicator results to the DataFrame [self._post_process(r, **kwargs) for r in results] @@ -900,9 +903,9 @@ def ao(self, fast=None, slow=None, offset=None, **kwargs): result = ao(high=high, low=low, fast=fast, slow=slow, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def apo(self, fast=None, slow=None, offset=None, **kwargs): + def apo(self, fast=None, slow=None, mamode=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) - result = apo(close=close, fast=fast, slow=slow, offset=offset, **kwargs) + result = apo(close=close, fast=fast, slow=slow, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def bias(self, length=None, mamode=None, offset=None, **kwargs): @@ -958,10 +961,10 @@ def cti(self, length=None, offset=None, **kwargs): result = cti(close=close, length=length, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def dm(self, drift=None, offset=None, **kwargs): + def dm(self, drift=None, offset=None, mamode=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) - result = dm(high=high, low=low, drift=drift, offset=offset, **kwargs) + result = dm(high=high, low=low, drift=drift, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def er(self, length=None, drift=None, offset=None, **kwargs): @@ -1078,18 +1081,18 @@ def smi(self, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwa result = smi(close=close, fast=fast, slow=slow, signal=signal, scalar=scalar, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def squeeze(self, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs): + def squeeze(self, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, mamode=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result = squeeze(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar=kc_scalar, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs) + result = squeeze(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar=kc_scalar, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def squeeze_pro(self, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs): + def squeeze_pro(self, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, mamode=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result = squeeze_pro(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar_wide=kc_scalar_wide, kc_scalar_normal=kc_scalar_normal, kc_scalar_narrow=kc_scalar_narrow, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs) + result = squeeze_pro(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar_wide=kc_scalar_wide, kc_scalar_normal=kc_scalar_normal, kc_scalar_narrow=kc_scalar_narrow, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def stc(self, ma1=None, ma2=None, osc=None, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs): @@ -1097,18 +1100,18 @@ def stc(self, ma1=None, ma2=None, osc=None, tclength=None, fast=None, slow=None, result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclength=tclength, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def stoch(self, fast_k=None, slow_k=None, slow_d=None, offset=None, **kwargs): + def stoch(self, fast_k=None, slow_k=None, slow_d=None, mamode=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result = stoch(high=high, low=low, close=close, fast_k=fast_k, slow_k=slow_k, slow_d=slow_d, offset=offset, **kwargs) + result = stoch(high=high, low=low, close=close, fast_k=fast_k, slow_k=slow_k, slow_d=slow_d, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def stochrsi(self, length=None, rsi_length=None, k=None, d=None, offset=None, **kwargs): + def stochrsi(self, length=None, rsi_length=None, k=None, d=None, mamode=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result = stochrsi(high=high, low=low, close=close, length=length, rsi_length=rsi_length, k=k, d=d, offset=offset, **kwargs) + result = stochrsi(high=high, low=low, close=close, length=length, rsi_length=rsi_length, k=k, d=d, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def td_seq(self, asint=None, offset=None, show_all=None, **kwargs): @@ -1121,9 +1124,9 @@ def trix(self, length=None, signal=None, scalar=None, drift=None, offset=None, * result = trix(close=close, length=length, signal=signal, scalar=scalar, drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def tsi(self, fast=None, slow=None, drift=None, offset=None, **kwargs): + def tsi(self, fast=None, slow=None, drift=None, mamode=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) - result = tsi(close=close, fast=fast, slow=slow, drift=drift, offset=offset, **kwargs) + result = tsi(close=close, fast=fast, slow=slow, drift=drift, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def uo(self, fast=None, medium=None, slow=None, fast_w=None, medium_w=None, slow_w=None, drift=None, offset=None, **kwargs): @@ -1191,16 +1194,21 @@ def hwma(self, na=None, nb=None, nc=None, offset=None, **kwargs): result = hwma(close=close, na=na, nb=nb, nc=nc, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def jma(self, length=None, phase=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = jma(close=close, length=length, phase=phase, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def kama(self, length=None, fast=None, slow=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = kama(close=close, length=length, fast=fast, slow=slow, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def ichimoku(self, tenkan=None, kijun=None, senkou=None, offset=None, **kwargs): + def ichimoku(self, tenkan=None, kijun=None, senkou=None, include_chikou=True, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result, span = ichimoku(high=high, low=low, close=close, tenkan=tenkan, kijun=kijun, senkou=senkou, offset=offset, **kwargs) + result, span = ichimoku(high=high, low=low, close=close, tenkan=tenkan, kijun=kijun, senkou=senkou, include_chikou=include_chikou, offset=offset, **kwargs) self._add_prefix_suffix(result, **kwargs) self._add_prefix_suffix(span, **kwargs) self._append(result, **kwargs) @@ -1416,11 +1424,11 @@ def chop(self, length=None, atr_length=None, scalar=None, drift=None, offset=Non result = chop(high=high, low=low, close=close, length=length, atr_length=atr_length, scalar=scalar, drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def cksp(self, p=None, x=None, q=None, offset=None, **kwargs): + def cksp(self, p=None, x=None, q=None, mamode=None, offset=None, **kwargs): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) close = self._get_column(kwargs.pop("close", "close")) - result = cksp(high=high, low=low, close=close, p=p, x=x, q=q, offset=offset, **kwargs) + result = cksp(high=high, low=low, close=close, p=p, x=x, q=q, mamode=mamode, offset=offset, **kwargs) return self._post_process(result, **kwargs) def decay(self, length=None, mode=None, offset=None, **kwargs): diff --git a/pandas_ta/custom.py b/pandas_ta/custom.py new file mode 100644 index 00000000..4af5cc5f --- /dev/null +++ b/pandas_ta/custom.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +import importlib +import os +import sys +import types + +from os.path import abspath, join, exists, basename, splitext +from glob import glob + +import pandas_ta +from pandas_ta import AnalysisIndicators + + +def bind(function_name, function, method): + """ + Helper function to bind the function and class method defined in a custom + indicator module to the active pandas_ta instance. + + Args: + function_name (str): The name of the indicator within pandas_ta + function (fcn): The indicator function + method (fcn): The class method corresponding to the passed function + """ + setattr(pandas_ta, function_name, function) + setattr(AnalysisIndicators, function_name, method) + + +def create_dir(path, create_categories=True, verbose=True): + """ + Helper function to setup a suitable folder structure for working with + custom indicators. You only need to call this once whenever you want to + setup a new custom indicators folder. + + Args: + path (str): Full path to where you want your indicator tree + create_categories (bool): If True create category sub-folders + verbose (bool): If True print verbose output of results + """ + + # ensure that the passed directory exists / is readable + if not exists(path): + os.makedirs(path) + if verbose: + print(f"[i] Created main directory '{path}'.") + + # list the contents of the directory + # dirs = glob(abspath(join(path, '*'))) + + # optionally add any missing category subdirectories + if create_categories: + for sd in [*pandas_ta.Category]: + d = abspath(join(path, sd)) + if not exists(d): + os.makedirs(d) + if verbose: + dirname = basename(d) + print(f"[i] Created an empty sub-directory '{dirname}'.") + + +def get_module_functions(module): + """ + Helper function to get the functions of an imported module as a dictionary. + + Args: + module: python module + + Returns: + dict: module functions mapping + { + "func1_name": func1, + "func2_name": func2,... + } + """ + module_functions = {} + + for name, item in vars(module).items(): + if isinstance(item, types.FunctionType): + module_functions[name] = item + + return module_functions + + +def import_dir(path, verbose=True): + # ensure that the passed directory exists / is readable + if not exists(path): + print(f"[X] Unable to read the directory '{path}'.") + return + + # list the contents of the directory + dirs = glob(abspath(join(path, "*"))) + + # traverse full directory, importing all modules found there + for d in dirs: + dirname = basename(d) + + # only look in directories which are valid pandas_ta categories + if dirname not in [*pandas_ta.Category]: + if verbose: + print(f"[i] Skipping the sub-directory '{dirname}' since it's not a valid pandas_ta category.") + continue + + # for each module found in that category (directory)... + for module in glob(abspath(join(path, dirname, "*.py"))): + module_name = splitext(basename(module))[0] + + # ensure that the supplied path is included in our python path + if d not in sys.path: + sys.path.append(d) + + # (re)load the indicator module + module_functions = load_indicator_module(module_name) + + # figure out which of the modules functions to bind to pandas_ta + fcn_callable = module_functions.get(module_name, None) + fcn_method_callable = module_functions.get(f"{module_name}_method", None) + + if fcn_callable == None: + print(f"[X] Unable to find a function named '{module_name}' in the module '{module_name}.py'.") + continue + if fcn_method_callable == None: + missing_method = f"{module_name}_method" + print(f"[X] Unable to find a method function named '{missing_method}' in the module '{module_name}.py'.") + continue + + # add it to the correct category if it's not there yet + if module_name not in pandas_ta.Category[dirname]: + pandas_ta.Category[dirname].append(module_name) + + bind(module_name, fcn_callable, fcn_method_callable) + if verbose: + print(f"[i] Successfully imported the custom indicator '{module}' into category '{dirname}'.") + + +import_dir.__doc__ = \ +""" +Import a directory of custom indicators into pandas_ta + +Args: + path (str): Full path to your indicator tree + verbose (bool): If True verbose output of results + +This method allows you to experiment and develop your own technical analysis +indicators in a separate local directory of your choice but use them seamlessly +together with the existing pandas_ta functions just like if they were part of +pandas_ta. + +If you at some late point would like to push them into the pandas_ta library +you can do so very easily by following the step by step instruction here +https://github.com/twopirllc/pandas-ta/issues/355. + +A brief example of usage: + +1. Loading the 'ta' module: +>>> import pandas as pd +>>> import pandas_ta as ta + +2. Create an empty directory on your machine where you want to work with your +indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with +sub-folders for all available indicator categories, e.g.: + +>>> import os +>>> from os.path import abspath, join, expanduser +>>> from pandas_ta.custom import create_dir, import_dir +>>> ta_dir = abspath(join(expanduser("~"), "my_indicators")) +>>> create_dir(ta_dir) + +3. You can now create your own custom indicator e.g. by copying existing +ones from pandas_ta core module and modifying them. + +IMPORTANT: Each custom indicator should have a unique name and have both +a) a function named exactly as the module, e.g. 'ni' if the module is ni.py +b) a matching method used by AnalysisIndicators named as the module but + ending with '_method'. E.g. 'ni_method' + +In essence these modules should look exactly like the standard indicators +available in categories under the pandas_ta-folder. The only difference will +be an addition of a matching class method. + +For an example of the correct structure, look at the example ni.py in the +examples folder. + +The ni.py indicator is a trend indicator so therefore we drop it into the +sub-folder named trend. Thus we have a folder structure like this: + +~/my_indicators/ +│ +├── candles/ +. +. +└── trend/ +. └── ni.py +. +└── volume/ + +4. We can now dynamically load all our custom indicators located in our +designated indicators directory like this: + +>>> import_dir(ta_dir) + +If your custom indicator(s) loaded succesfully then it should behave exactly +like all other native indicators in pandas_ta, including help functions. +""" + + +def load_indicator_module(name): + """ + Helper function to (re)load an indicator module. + + Returns: + dict: module functions mapping + { + "func1_name": func1, + "func2_name": func2,... + } + + """ + # load module + try: + module = importlib.import_module(name) + except Exception as ex: + print(f"[X] An error occurred when attempting to load module {name}: {ex}") + sys.exit(1) + + # reload to refresh previously loaded module + module = importlib.reload(module) + return get_module_functions(module) \ No newline at end of file diff --git a/pandas_ta/momentum/apo.py b/pandas_ta/momentum/apo.py index 60ceaf10..55c55e02 100644 --- a/pandas_ta/momentum/apo.py +++ b/pandas_ta/momentum/apo.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from pandas_ta import Imports -from pandas_ta.overlap import sma -from pandas_ta.utils import get_offset, verify_series +from pandas_ta.overlap import ma +from pandas_ta.utils import get_offset, tal_ma, verify_series -def apo(close, fast=None, slow=None, offset=None, **kwargs): +def apo(close, fast=None, slow=None, mamode=None, talib=None, offset=None, **kwargs): """Indicator: Absolute Price Oscillator (APO)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 12 @@ -12,17 +12,19 @@ def apo(close, fast=None, slow=None, offset=None, **kwargs): if slow < fast: fast, slow = slow, fast close = verify_series(close, max(fast, slow)) + mamode = mamode if isinstance(mamode, str) else "sma" offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import APO - apo = APO(close, fast, slow) + apo = APO(close, fast, slow, tal_ma(mamode)) else: - fastma = sma(close, length=fast) - slowma = sma(close, length=slow) + fastma = ma(mamode, close, length=fast) + slowma = ma(mamode, close, length=slow) apo = fastma - slowma # Offset @@ -62,6 +64,9 @@ def apo(close, fast=None, slow=None, offset=None, **kwargs): close (pd.Series): Series of 'close's fast (int): The short period. Default: 12 slow (int): The long period. Default: 26 + mamode (str): See ```help(ta.ma)```. Default: 'sma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/bias.py b/pandas_ta/momentum/bias.py index 093dbeac..3b300039 100644 --- a/pandas_ta/momentum/bias.py +++ b/pandas_ta/momentum/bias.py @@ -53,7 +53,7 @@ def bias(close, length=None, mamode=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): The period. Default: 26 - mamode (str): Options: 'ema', 'hma', 'rma', 'sma', 'wma'. Default: 'sma' + mamode (str): See ```help(ta.ma)```. Default: 'sma' drift (int): The short period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/bop.py b/pandas_ta/momentum/bop.py index 5f901af1..8a8f3834 100644 --- a/pandas_ta/momentum/bop.py +++ b/pandas_ta/momentum/bop.py @@ -3,7 +3,7 @@ from pandas_ta.utils import get_offset, non_zero_range, verify_series -def bop(open_, high, low, close, scalar=None, offset=None, **kwargs): +def bop(open_, high, low, close, scalar=None, talib=None, offset=None, **kwargs): """Indicator: Balance of Power (BOP)""" # Validate Arguments open_ = verify_series(open_) @@ -12,9 +12,10 @@ def bop(open_, high, low, close, scalar=None, offset=None, **kwargs): close = verify_series(close) scalar = float(scalar) if scalar else 1 offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import BOP bop = BOP(open_, high, low, close) else: @@ -56,6 +57,8 @@ def bop(open_, high, low, close, scalar=None, offset=None, **kwargs): low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's scalar (float): How much to magnify. Default: 1 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/cci.py b/pandas_ta/momentum/cci.py index f8eec173..5b5370c3 100644 --- a/pandas_ta/momentum/cci.py +++ b/pandas_ta/momentum/cci.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_offset, verify_series -def cci(high, low, close, length=None, c=None, offset=None, **kwargs): +def cci(high, low, close, length=None, c=None, talib=None, offset=None, **kwargs): """Indicator: Commodity Channel Index (CCI)""" # Validate Arguments length = int(length) if length and length > 0 else 14 @@ -14,11 +14,12 @@ def cci(high, low, close, length=None, c=None, offset=None, **kwargs): low = verify_series(low, length) close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import CCI cci = CCI(high, low, close, length) else: @@ -71,6 +72,8 @@ def cci(high, low, close, length=None, c=None, offset=None, **kwargs): close (pd.Series): Series of 'close's length (int): It's period. Default: 14 c (float): Scaling Constant. Default: 0.015 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/cmo.py b/pandas_ta/momentum/cmo.py index 6c02ced9..b14402fc 100644 --- a/pandas_ta/momentum/cmo.py +++ b/pandas_ta/momentum/cmo.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): +def cmo(close, length=None, scalar=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Chande Momentum Oscillator (CMO)""" # Validate Arguments length = int(length) if length and length > 0 else 14 @@ -12,11 +12,12 @@ def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import CMO cmo = CMO(close, length) else: @@ -24,8 +25,7 @@ def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): positive = mom.copy().clip(lower=0) negative = mom.copy().clip(upper=0).abs() - talib = kwargs.pop("talib", True) - if talib: + if mode_tal: pos_ = rma(positive, length) neg_ = rma(negative, length) else: @@ -71,6 +71,9 @@ def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's scalar (float): How much to magnify. Default: 100 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. If TA Lib is not installed but talib is True, it runs the Python + version TA Lib. Default: True drift (int): The short period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/dm.py b/pandas_ta/momentum/dm.py index 0c234c70..3f442fa7 100644 --- a/pandas_ta/momentum/dm.py +++ b/pandas_ta/momentum/dm.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_offset, verify_series, get_drift, zero -def dm(high, low, length=None, mamode=None, drift=None, offset=None, **kwargs): +def dm(high, low, length=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: DM""" # Validate Arguments length = int(length) if length and length > 0 else 14 @@ -14,13 +14,15 @@ def dm(high, low, length=None, mamode=None, drift=None, offset=None, **kwargs): low = verify_series(low) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None: return - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MINUS_DM, PLUS_DM - pos, neg = PLUS_DM(high, low), MINUS_DM(high, low) + pos = PLUS_DM(high, low, length) + neg = MINUS_DM(high, low, length) else: up = high - high.shift(drift) dn = low.shift(drift) - low @@ -85,6 +87,9 @@ def dm(high, low, length=None, mamode=None, drift=None, offset=None, **kwargs): Args: high (pd.Series): Series of 'high's low (pd.Series): Series of 'low's + mamode (str): See ```help(ta.ma)```. Default: 'rma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/fisher.py b/pandas_ta/momentum/fisher.py index e15ac100..662bb37b 100644 --- a/pandas_ta/momentum/fisher.py +++ b/pandas_ta/momentum/fisher.py @@ -3,7 +3,7 @@ from numpy import nan as npNaN from pandas import DataFrame, Series from pandas_ta.overlap import hl2 -from pandas_ta.utils import get_offset, high_low_range, verify_series, zero +from pandas_ta.utils import get_offset, high_low_range, verify_series def fisher(high, low, length=None, signal=None, offset=None, **kwargs): diff --git a/pandas_ta/momentum/inertia.py b/pandas_ta/momentum/inertia.py index fd580e5a..a8cd7266 100644 --- a/pandas_ta/momentum/inertia.py +++ b/pandas_ta/momentum/inertia.py @@ -78,6 +78,9 @@ def inertia(close=None, high=None, low=None, length=None, rvi_length=None, scala close (pd.Series): Series of 'close's length (int): It's period. Default: 20 rvi_length (int): RVI period. Default: 14 + refined (bool): Use 'refined' calculation. Default: False + thirds (bool): Use 'thirds' calculation. Default: False + mamode (str): See ```help(ta.ma)```. Default: 'ema' drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/macd.py b/pandas_ta/momentum/macd.py index 694bd453..8e6e2ced 100644 --- a/pandas_ta/momentum/macd.py +++ b/pandas_ta/momentum/macd.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_offset, verify_series, signals -def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): +def macd(close, fast=None, slow=None, signal=None, talib=None, offset=None, **kwargs): """Indicator: Moving Average, Convergence/Divergence (MACD)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 12 @@ -15,13 +15,16 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): fast, slow = slow, fast close = verify_series(close, max(fast, slow, signal)) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return + as_mode = kwargs.setdefault("asmode", False) + # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MACD - macd, signalma, histogram = MACD(close, fast, slow) + macd, signalma, histogram = MACD(close, fast, slow, signal) else: fastma = ema(close, length=fast) slowma = ema(close, length=slow) @@ -30,6 +33,11 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): signalma = ema(close=macd.loc[macd.first_valid_index():,], length=signal) histogram = macd - signalma + if as_mode: + macd = macd - signalma + signalma = ema(close=macd.loc[macd.first_valid_index():,], length=signal) + histogram = macd - signalma + # Offset if offset != 0: macd = macd.shift(offset) @@ -47,16 +55,17 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): signalma.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it + _asmode = "AS" if as_mode else "" _props = f"_{fast}_{slow}_{signal}" - macd.name = f"MACD{_props}" - histogram.name = f"MACDh{_props}" - signalma.name = f"MACDs{_props}" + macd.name = f"MACD{_asmode}{_props}" + histogram.name = f"MACD{_asmode}h{_props}" + signalma.name = f"MACD{_asmode}s{_props}" macd.category = histogram.category = signalma.category = "momentum" # Prepare DataFrame to return data = {macd.name: macd, histogram.name: histogram, signalma.name: signalma} df = DataFrame(data) - df.name = f"MACD{_props}" + df.name = f"MACD{_asmode}{_props}" df.category = macd.category signal_indicators = kwargs.pop("signal_indicators", False) @@ -105,6 +114,7 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): Sources: https://www.tradingview.com/wiki/MACD_(Moving_Average_Convergence/Divergence) + AS Mode: https://tr.tradingview.com/script/YFlKXHnP/ Calculation: Default Inputs: @@ -114,14 +124,23 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): Signal = EMA(MACD, signal) Histogram = MACD - Signal + if asmode: + MACD = MACD - Signal + Signal = EMA(MACD, signal) + Histogram = MACD - Signal + Args: close (pd.Series): Series of 'close's fast (int): The short period. Default: 12 slow (int): The long period. Default: 26 signal (int): The signal period. Default: 9 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: + asmode (value, optional): When True, enables AS version of MACD. + Default: False fillna (value, optional): pd.DataFrame.fillna(value) fill_method (value, optional): Type of fill method diff --git a/pandas_ta/momentum/mom.py b/pandas_ta/momentum/mom.py index 3c1a8f52..e2f66382 100644 --- a/pandas_ta/momentum/mom.py +++ b/pandas_ta/momentum/mom.py @@ -3,17 +3,18 @@ from pandas_ta.utils import get_offset, verify_series -def mom(close, length=None, offset=None, **kwargs): +def mom(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Momentum (MOM)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MOM mom = MOM(close, length) else: @@ -53,6 +54,8 @@ def mom(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 1 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/ppo.py b/pandas_ta/momentum/ppo.py index a144f585..a92f3ca1 100644 --- a/pandas_ta/momentum/ppo.py +++ b/pandas_ta/momentum/ppo.py @@ -2,10 +2,10 @@ from pandas import DataFrame from pandas_ta import Imports from pandas_ta.overlap import ma -from pandas_ta.utils import get_offset, verify_series +from pandas_ta.utils import get_offset, tal_ma, verify_series -def ppo(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, offset=None, **kwargs): +def ppo(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, talib=None, offset=None, **kwargs): """Indicator: Percentage Price Oscillator (PPO)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 12 @@ -17,13 +17,14 @@ def ppo(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, offs fast, slow = slow, fast close = verify_series(close, max(fast, slow, signal)) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import PPO - ppo = PPO(close, fast, slow) + ppo = PPO(close, fast, slow, tal_ma(mamode)) else: fastma = ma(mamode, close, length=fast) slowma = ma(mamode, close, length=slow) @@ -90,7 +91,9 @@ def ppo(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, offs slow(int): The long period. Default: 26 signal(int): The signal period. Default: 9 scalar (float): How much to magnify. Default: 100 - mamode (str): Options: 'ema', 'hma', 'rma', 'sma', 'wma'. Default: 'sma' + mamode (str): See ```help(ta.ma)```. Default: 'sma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset(int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/qqe.py b/pandas_ta/momentum/qqe.py index 1badab68..69be112f 100644 --- a/pandas_ta/momentum/qqe.py +++ b/pandas_ta/momentum/qqe.py @@ -143,8 +143,7 @@ def qqe(close, length=None, smooth=None, factor=None, mamode=None, drift=None, o length (int): RSI period. Default: 14 smooth (int): RSI smoothing period. Default: 5 factor (float): QQE Factor. Default: 4.236 - mamode (str): Smoothing MA type: "ema", "hma", "rma", "sma" or "wma". - Default: "ema" + mamode (str): See ```help(ta.ma)```. Default: 'sma' drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/roc.py b/pandas_ta/momentum/roc.py index 1fb62e7b..75f30d24 100644 --- a/pandas_ta/momentum/roc.py +++ b/pandas_ta/momentum/roc.py @@ -4,18 +4,19 @@ from pandas_ta.utils import get_offset, verify_series -def roc(close, length=None, scalar=None, offset=None, **kwargs): +def roc(close, length=None, scalar=None, talib=None, offset=None, **kwargs): """Indicator: Rate of Change (ROC)""" # Validate Arguments length = int(length) if length and length > 0 else 10 scalar = float(scalar) if scalar and scalar > 0 else 100 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import ROC roc = ROC(close, length) else: @@ -57,6 +58,9 @@ def roc(close, length=None, scalar=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 1 + scalar (float): How much to magnify. Default: 100 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/rsi.py b/pandas_ta/momentum/rsi.py index c2b747a8..a8978efb 100644 --- a/pandas_ta/momentum/rsi.py +++ b/pandas_ta/momentum/rsi.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series, signals -def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): +def rsi(close, length=None, scalar=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Relative Strength Index (RSI)""" # Validate arguments length = int(length) if length and length > 0 else 14 @@ -13,11 +13,12 @@ def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import RSI rsi = RSI(close, length) else: @@ -99,6 +100,8 @@ def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): close (pd.Series): Series of 'close's length (int): It's period. Default: 14 scalar (float): How much to magnify. Default: 100 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/smi.py b/pandas_ta/momentum/smi.py index a652ccad..ba8c5257 100644 --- a/pandas_ta/momentum/smi.py +++ b/pandas_ta/momentum/smi.py @@ -20,8 +20,9 @@ def smi(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kw if close is None: return # Calculate Result - smi = tsi(close, fast=fast, slow=slow, scalar=scalar) - signalma = ema(smi, signal) + tsi_df = tsi(close, fast=fast, slow=slow, signal=signal, scalar=scalar) + smi = tsi_df.iloc[:, 0] + signalma = tsi_df.iloc[:, 1] osc = smi - signalma # Offset diff --git a/pandas_ta/momentum/squeeze.py b/pandas_ta/momentum/squeeze.py index 514d149a..2fc3ace3 100644 --- a/pandas_ta/momentum/squeeze.py +++ b/pandas_ta/momentum/squeeze.py @@ -9,7 +9,7 @@ from pandas_ta.utils import unsigned_differences, verify_series -def squeeze(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs): +def squeeze(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, mamode=None, offset=None, **kwargs): """Indicator: Squeeze Momentum (SQZ)""" # Validate arguments bb_length = int(bb_length) if bb_length and bb_length > 0 else 20 @@ -30,7 +30,7 @@ def squeeze(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_sc asint = kwargs.pop("asint", True) detailed = kwargs.pop("detailed", False) lazybear = kwargs.pop("lazybear", False) - mamode = kwargs.pop("mamode", "sma").lower() + mamode = mamode if isinstance(mamode, str) else "sma" def simplify_columns(df, n=3): df.columns = df.columns.str.lower() diff --git a/pandas_ta/momentum/squeeze_pro.py b/pandas_ta/momentum/squeeze_pro.py index 61cefc68..d2d591f5 100644 --- a/pandas_ta/momentum/squeeze_pro.py +++ b/pandas_ta/momentum/squeeze_pro.py @@ -9,7 +9,7 @@ from pandas_ta.utils import unsigned_differences, verify_series -def squeeze_pro(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs): +def squeeze_pro(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, mamode=None, offset=None, **kwargs): """Indicator: Squeeze Momentum (SQZ) PRO""" # Validate arguments bb_length = int(bb_length) if bb_length and bb_length > 0 else 20 @@ -35,7 +35,7 @@ def squeeze_pro(high, low, close, bb_length=None, bb_std=None, kc_length=None, k use_tr = kwargs.setdefault("tr", True) asint = kwargs.pop("asint", True) detailed = kwargs.pop("detailed", False) - mamode = kwargs.pop("mamode", "sma").lower() + mamode = mamode if isinstance(mamode, str) else "sma" def simplify_columns(df, n=3): df.columns = df.columns.str.lower() diff --git a/pandas_ta/momentum/stoch.py b/pandas_ta/momentum/stoch.py index 2b55b55f..823c3329 100644 --- a/pandas_ta/momentum/stoch.py +++ b/pandas_ta/momentum/stoch.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from pandas import DataFrame -from pandas_ta.overlap import sma +from pandas_ta.overlap import ma from pandas_ta.utils import get_offset, non_zero_range, verify_series -def stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs): +def stoch(high, low, close, k=None, d=None, smooth_k=None, mamode=None, offset=None, **kwargs): """Indicator: Stochastic Oscillator (STOCH)""" # Validate arguments k = k if k and k > 0 else 14 @@ -15,6 +15,7 @@ def stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) + mamode = mamode if isinstance(mamode, str) else "sma" if high is None or low is None or close is None: return @@ -25,8 +26,8 @@ def stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs stoch = 100 * (close - lowest_low) stoch /= non_zero_range(highest_high, lowest_low) - stoch_k = sma(stoch, length=smooth_k) - stoch_d = sma(stoch_k, length=d) + stoch_k = ma(mamode, stoch.loc[stoch.first_valid_index():,], length=smooth_k) + stoch_d = ma(mamode, stoch_k.loc[stoch_k.first_valid_index():,], length=d) # Offset if offset != 0: @@ -53,7 +54,6 @@ def stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs df = DataFrame(data) df.name = f"{_name}{_props}" df.category = stoch_k.category - return df @@ -91,6 +91,7 @@ def stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs k (int): The Fast %K period. Default: 14 d (int): The Slow %K period. Default: 3 smooth_k (int): The Slow %D period. Default: 3 + mamode (str): See ```help(ta.ma)```. Default: 'sma' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/stochrsi.py b/pandas_ta/momentum/stochrsi.py index cdd32b0f..08414848 100644 --- a/pandas_ta/momentum/stochrsi.py +++ b/pandas_ta/momentum/stochrsi.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from pandas import DataFrame from .rsi import rsi -from pandas_ta.overlap import sma +from pandas_ta.overlap import ma from pandas_ta.utils import get_offset, non_zero_range, verify_series -def stochrsi(close, length=None, rsi_length=None, k=None, d=None, offset=None, **kwargs): +def stochrsi(close, length=None, rsi_length=None, k=None, d=None, mamode=None, offset=None, **kwargs): """Indicator: Stochastic RSI Oscillator (STOCHRSI)""" # Validate arguments length = length if length and length > 0 else 14 @@ -14,6 +14,7 @@ def stochrsi(close, length=None, rsi_length=None, k=None, d=None, offset=None, * d = d if d and d > 0 else 3 close = verify_series(close, max(length, rsi_length, k, d)) offset = get_offset(offset) + mamode = mamode if isinstance(mamode, str) else "sma" if close is None: return @@ -25,8 +26,8 @@ def stochrsi(close, length=None, rsi_length=None, k=None, d=None, offset=None, * stoch = 100 * (rsi_ - lowest_rsi) stoch /= non_zero_range(highest_rsi, lowest_rsi) - stochrsi_k = sma(stoch, length=k) - stochrsi_d = sma(stochrsi_k, length=d) + stochrsi_k = ma(mamode, stoch, length=k) + stochrsi_d = ma(mamode, stochrsi_k, length=d) # Offset if offset != 0: @@ -92,6 +93,7 @@ def stochrsi(close, length=None, rsi_length=None, k=None, d=None, offset=None, * rsi_length (int): RSI period. Default: 14 k (int): The Fast %K period. Default: 3 d (int): The Slow %K period. Default: 3 + mamode (str): See ```help(ta.ma)```. Default: 'sma' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/momentum/tsi.py b/pandas_ta/momentum/tsi.py index 587e3ebd..6747c7d1 100644 --- a/pandas_ta/momentum/tsi.py +++ b/pandas_ta/momentum/tsi.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- -from pandas_ta.overlap import ema +from pandas import DataFrame +from pandas_ta.overlap import ema, ma from pandas_ta.utils import get_drift, get_offset, verify_series -def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwargs): +def tsi(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: True Strength Index (TSI)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 13 slow = int(slow) if slow and slow > 0 else 25 + signal = int(signal) if signal and signal > 0 else 13 # if slow < fast: # fast, slow = slow, fast scalar = float(scalar) if scalar else 100 close = verify_series(close, max(fast, slow)) drift = get_drift(drift) offset = get_offset(offset) + mamode = mamode if isinstance(mamode, str) else "ema" if "length" in kwargs: kwargs.pop("length") if close is None: return @@ -28,22 +31,32 @@ def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwa abs_fast_slow_ema = ema(close=abs_slow_ema, length=fast, **kwargs) tsi = scalar * fast_slow_ema / abs_fast_slow_ema + tsi_signal = ma(mamode, tsi, length=signal) # Offset if offset != 0: tsi = tsi.shift(offset) + tsi_signal = tsi_signal.shift(offset) # Handle fills if "fillna" in kwargs: tsi.fillna(kwargs["fillna"], inplace=True) + tsi_signal.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: tsi.fillna(method=kwargs["fill_method"], inplace=True) + tsi_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it - tsi.name = f"TSI_{fast}_{slow}" - tsi.category = "momentum" + tsi.name = f"TSI_{fast}_{slow}_{signal}" + tsi_signal.name = f"TSIs_{fast}_{slow}_{signal}" + tsi.category = tsi_signal.category = "momentum" - return tsi + # Prepare DataFrame to return + df = DataFrame({tsi.name: tsi, tsi_signal.name: tsi_signal}) + df.name = f"TSI_{fast}_{slow}_{signal}" + df.category = "momentum" + + return df tsi.__doc__ = \ @@ -58,7 +71,7 @@ def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwa Calculation: Default Inputs: - fast=13, slow=25, scalar=100, drift=1 + fast=13, slow=25, signal=13, scalar=100, drift=1 EMA = Exponential Moving Average diff = close.diff(drift) @@ -69,12 +82,16 @@ def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwa abema = abs_diff_fast_slow_ema = EMA(abs_diff_slow_ema, fast) TSI = scalar * fast_slow_ema / abema + Signal = EMA(TSI, signal) Args: close (pd.Series): Series of 'close's fast (int): The short period. Default: 13 slow (int): The long period. Default: 25 + signal (int): The signal period. Default: 13 scalar (float): How much to magnify. Default: 100 + mamode (str): Moving Average of TSI Signal Line. + See ```help(ta.ma)```. Default: 'ema' drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 @@ -83,5 +100,5 @@ def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwa fill_method (value, optional): Type of fill method Returns: - pd.Series: New feature generated. + pd.DataFrame: tsi, signal. """ diff --git a/pandas_ta/momentum/uo.py b/pandas_ta/momentum/uo.py index 3da23d7b..f58691a7 100644 --- a/pandas_ta/momentum/uo.py +++ b/pandas_ta/momentum/uo.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def uo(high, low, close, fast=None, medium=None, slow=None, fast_w=None, medium_w=None, slow_w=None, drift=None, offset=None, **kwargs): +def uo(high, low, close, fast=None, medium=None, slow=None, fast_w=None, medium_w=None, slow_w=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Ultimate Oscillator (UO)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 7 @@ -19,13 +19,14 @@ def uo(high, low, close, fast=None, medium=None, slow=None, fast_w=None, medium_ close = verify_series(close, _length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import ULTOSC - uo = ULTOSC(high, low, close) + uo = ULTOSC(high, low, close, fast, medium, slow) else: tdf = DataFrame({ "high": high, @@ -101,6 +102,8 @@ def uo(high, low, close, fast=None, medium=None, slow=None, fast_w=None, medium_ fast_w (float): The Fast %K period. Default: 4.0 medium_w (float): The Slow %K period. Default: 2.0 slow_w (float): The Slow %D period. Default: 1.0 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/willr.py b/pandas_ta/momentum/willr.py index fe69cb77..d3e87f77 100644 --- a/pandas_ta/momentum/willr.py +++ b/pandas_ta/momentum/willr.py @@ -3,7 +3,7 @@ from pandas_ta.utils import get_offset, verify_series -def willr(high, low, close, length=None, offset=None, **kwargs): +def willr(high, low, close, length=None, talib=None, offset=None, **kwargs): """Indicator: William's Percent R (WILLR)""" # Validate arguments length = int(length) if length and length > 0 else 14 @@ -13,11 +13,12 @@ def willr(high, low, close, length=None, offset=None, **kwargs): low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import WILLR willr = WILLR(high, low, close, length) else: @@ -65,6 +66,8 @@ def willr(high, low, close, length=None, offset=None, **kwargs): low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's length (int): It's period. Default: 14 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 4c098ff7..7a0f5ec2 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -9,6 +9,7 @@ from .hma import hma from .hwma import hwma from .ichimoku import ichimoku +from .jma import jma from .kama import kama from .linreg import linreg from .ma import ma diff --git a/pandas_ta/overlap/dema.py b/pandas_ta/overlap/dema.py index 29deb5e4..5ce8e038 100644 --- a/pandas_ta/overlap/dema.py +++ b/pandas_ta/overlap/dema.py @@ -4,17 +4,18 @@ from pandas_ta.utils import get_offset, verify_series -def dema(close, length=None, offset=None, **kwargs): +def dema(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Double Exponential Moving Average (DEMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import DEMA dema = DEMA(close, length) else: @@ -60,6 +61,8 @@ def dema(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 10 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/ema.py b/pandas_ta/overlap/ema.py index 105f856f..507aa9f2 100644 --- a/pandas_ta/overlap/ema.py +++ b/pandas_ta/overlap/ema.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_offset, verify_series -def ema(close, length=None, offset=None, **kwargs): +def ema(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Exponential Moving Average (EMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 @@ -12,11 +12,12 @@ def ema(close, length=None, offset=None, **kwargs): sma = kwargs.pop("sma", True) close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import EMA ema = EMA(close, length) else: @@ -69,6 +70,8 @@ def ema(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 10 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/hilo.py b/pandas_ta/overlap/hilo.py index 5c7250ae..128c9ef1 100644 --- a/pandas_ta/overlap/hilo.py +++ b/pandas_ta/overlap/hilo.py @@ -115,7 +115,7 @@ def hilo(high, low, close, high_length=None, low_length=None, mamode=None, offse close (pd.Series): Series of 'close's high_length (int): It's period. Default: 13 low_length (int): It's period. Default: 21 - mamode (str): Options: 'sma' or 'ema'. Default: 'sma' + mamode (str): See ```help(ta.ma)```. Default: 'sma' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/hlc3.py b/pandas_ta/overlap/hlc3.py index e22046bb..087ed1dc 100644 --- a/pandas_ta/overlap/hlc3.py +++ b/pandas_ta/overlap/hlc3.py @@ -3,16 +3,17 @@ from pandas_ta.utils import get_offset, verify_series -def hlc3(high, low, close, offset=None, **kwargs): +def hlc3(high, low, close, talib=None, offset=None, **kwargs): """Indicator: HLC3""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import TYPPRICE hlc3 = TYPPRICE(high, low, close) else: diff --git a/pandas_ta/overlap/ichimoku.py b/pandas_ta/overlap/ichimoku.py index 24547419..7f2e9257 100644 --- a/pandas_ta/overlap/ichimoku.py +++ b/pandas_ta/overlap/ichimoku.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_offset, verify_series -def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, offset=None, **kwargs): +def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, include_chikou=True, offset=None, **kwargs): """Indicator: Ichimoku Kinkō Hyō (Ichimoku)""" tenkan = int(tenkan) if tenkan and tenkan > 0 else 9 kijun = int(kijun) if kijun and kijun > 0 else 26 @@ -14,6 +14,8 @@ def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, offset=None low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) + if not kwargs.get("lookahead", True): + include_chikou = False if high is None or low is None or close is None: return None, None @@ -65,8 +67,10 @@ def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, offset=None span_b.name: span_b, tenkan_sen.name: tenkan_sen, kijun_sen.name: kijun_sen, - chikou_span.name: chikou_span, } + if include_chikou: + data[chikou_span.name] = chikou_span + ichimokudf = DataFrame(data) ichimokudf.name = f"ICHIMOKU_{tenkan}_{kijun}_{senkou}" ichimokudf.category = "overlap" @@ -121,6 +125,7 @@ def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, offset=None tenkan (int): Tenkan period. Default: 9 kijun (int): Kijun period. Default: 26 senkou (int): Senkou period. Default: 52 + include_chikou (bool): Whether to include chikou component. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/jma.py b/pandas_ta/overlap/jma.py new file mode 100644 index 00000000..28afbab2 --- /dev/null +++ b/pandas_ta/overlap/jma.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +from numpy import average as npAverage +from numpy import nan as npNaN +from numpy import log as npLog +from numpy import power as npPower +from numpy import sqrt as npSqrt +from numpy import zeros_like as npZeroslike +from pandas import Series +from pandas_ta.utils import get_offset, verify_series + + +def jma(close, length=None, phase=None, offset=None, **kwargs): + """Indicator: Jurik Moving Average (JMA)""" + # Validate Arguments + _length = int(length) if length and length > 0 else 7 + phase = float(phase) if phase and phase != 0 else 0 + close = verify_series(close, _length) + offset = get_offset(offset) + if close is None: return + + # Define base variables + jma = npZeroslike(close) + volty = npZeroslike(close) + v_sum = npZeroslike(close) + + kv = det0 = det1 = ma2 = 0.0 + jma[0] = ma1 = uBand = lBand = close[0] + + # Static variables + sum_length = 10 + length = 0.5 * (_length - 1) + pr = 0.5 if phase < -100 else 2.5 if phase > 100 else 1.5 + phase * 0.01 + length1 = max((npLog(npSqrt(length)) / npLog(2.0)) + 2.0, 0) + pow1 = max(length1 - 2.0, 0.5) + length2 = length1 * npSqrt(length) + bet = length2 / (length2 + 1) + beta = 0.45 * (_length - 1) / (0.45 * (_length - 1) + 2.0) + + m = close.shape[0] + for i in range(1, m): + price = close[i] + + # Price volatility + del1 = price - uBand + del2 = price - lBand + volty[i] = max(abs(del1),abs(del2)) if abs(del1)!=abs(del2) else 0 + + # Relative price volatility factor + v_sum[i] = v_sum[i - 1] + (volty[i] - volty[max(i - sum_length, 0)]) / sum_length + avg_volty = npAverage(v_sum[max(i - 65, 0):i + 1]) + d_volty = 0 if avg_volty ==0 else volty[i] / avg_volty + r_volty = max(1.0, min(npPower(length1, 1 / pow1), d_volty)) + + # Jurik volatility bands + pow2 = npPower(r_volty, pow1) + kv = npPower(bet, npSqrt(pow2)) + uBand = price if (del1 > 0) else price - (kv * del1) + lBand = price if (del2 < 0) else price - (kv * del2) + + # Jurik Dynamic Factor + power = npPower(r_volty, pow1) + alpha = npPower(beta, power) + + # 1st stage - prelimimary smoothing by adaptive EMA + ma1 = ((1 - alpha) * price) + (alpha * ma1) + + # 2nd stage - one more prelimimary smoothing by Kalman filter + det0 = ((price - ma1) * (1 - beta)) + (beta * det0) + ma2 = ma1 + pr * det0 + + # 3rd stage - final smoothing by unique Jurik adaptive filter + det1 = ((ma2 - jma[i - 1]) * (1 - alpha) * (1 - alpha)) + (alpha * alpha * det1) + jma[i] = jma[i-1] + det1 + + # Remove initial lookback data and convert to pandas frame + jma[0:_length - 1] = npNaN + jma = Series(jma, index=close.index) + + # Offset + if offset != 0: + jma = jma.shift(offset) + + # Handle fills + if "fillna" in kwargs: + jma.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + jma.fillna(method=kwargs["fill_method"], inplace=True) + + # Name & Category + jma.name = f"JMA_{_length}_{phase}" + jma.category = "overlap" + + return jma + + +jma.__doc__ = \ +"""Jurik Moving Average Average (JMA) + +Mark Jurik's Moving Average (JMA) attempts to eliminate noise to see the "true" +underlying activity. It has extremely low lag, is very smooth and is responsive +to market gaps. + +Sources: + https://c.mql5.com/forextsd/forum/164/jurik_1.pdf + https://www.prorealcode.com/prorealtime-indicators/jurik-volatility-bands/ + +Calculation: + Default Inputs: + length=7, phase=0 + +Args: + close (pd.Series): Series of 'close's + length (int): Period of calculation. Default: 7 + phase (float): How heavy/light the average is [-100, 100]. Default: 0 + offset (int): How many lengths 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. +""" diff --git a/pandas_ta/overlap/linreg.py b/pandas_ta/overlap/linreg.py index 00f943b5..c6f00876 100644 --- a/pandas_ta/overlap/linreg.py +++ b/pandas_ta/overlap/linreg.py @@ -3,7 +3,7 @@ from numpy import arctan as npAtan from numpy import nan as npNaN from numpy import pi as npPi -from numpy.lib.stride_tricks import sliding_window_view +from numpy.version import version as npVersion from pandas import Series from pandas_ta.utils import get_offset, verify_series @@ -54,7 +54,19 @@ def linear_regression(series): return m * length + b if tsf else m * (length - 1) + b - linreg_ = [linear_regression(_) for _ in sliding_window_view(npArray(close), length)] + def rolling_window(array, length): + """https://github.com/twopirllc/pandas-ta/issues/285""" + strides = array.strides + (array.strides[-1],) + shape = array.shape[:-1] + (array.shape[-1] - length + 1, length) + return as_strided(array, shape=shape, strides=strides) + + if npVersion >= "1.20.0": + from numpy.lib.stride_tricks import sliding_window_view + linreg_ = [linear_regression(_) for _ in sliding_window_view(npArray(close), length)] + else: + from numpy.lib.stride_tricks import as_strided + linreg_ = [linear_regression(_) for _ in rolling_window(npArray(close), length)] + linreg = Series([npNaN] * (length - 1) + linreg_, index=close.index) # Offset diff --git a/pandas_ta/overlap/midpoint.py b/pandas_ta/overlap/midpoint.py index 26dad4ae..efd9cc11 100644 --- a/pandas_ta/overlap/midpoint.py +++ b/pandas_ta/overlap/midpoint.py @@ -3,18 +3,19 @@ from pandas_ta.utils import get_offset, verify_series -def midpoint(close, length=None, offset=None, **kwargs): +def midpoint(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Midpoint""" # Validate arguments length = int(length) if length and length > 0 else 2 min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length close = verify_series(close, max(length, min_periods)) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MIDPOINT midpoint = MIDPOINT(close, length) else: diff --git a/pandas_ta/overlap/midprice.py b/pandas_ta/overlap/midprice.py index 633f1338..39b775e6 100644 --- a/pandas_ta/overlap/midprice.py +++ b/pandas_ta/overlap/midprice.py @@ -3,7 +3,7 @@ from pandas_ta.utils import get_offset, verify_series -def midprice(high, low, length=None, offset=None, **kwargs): +def midprice(high, low, length=None, talib=None, offset=None, **kwargs): """Indicator: Midprice""" # Validate arguments length = int(length) if length and length > 0 else 2 @@ -12,11 +12,12 @@ def midprice(high, low, length=None, offset=None, **kwargs): high = verify_series(high, _length) low = verify_series(low, _length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MIDPRICE midprice = MIDPRICE(high, low, length) else: diff --git a/pandas_ta/overlap/sma.py b/pandas_ta/overlap/sma.py index 72760de2..dbcf8cbe 100644 --- a/pandas_ta/overlap/sma.py +++ b/pandas_ta/overlap/sma.py @@ -3,18 +3,19 @@ from pandas_ta.utils import get_offset, verify_series -def sma(close, length=None, offset=None, **kwargs): +def sma(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Simple Moving Average (SMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length close = verify_series(close, max(length, min_periods)) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import SMA sma = SMA(close, length) else: @@ -54,6 +55,8 @@ def sma(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 10 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/t3.py b/pandas_ta/overlap/t3.py index eabec716..0ceae2d0 100644 --- a/pandas_ta/overlap/t3.py +++ b/pandas_ta/overlap/t3.py @@ -4,20 +4,21 @@ from pandas_ta.utils import get_offset, verify_series -def t3(close, length=None, a=None, offset=None, **kwargs): +def t3(close, length=None, a=None, talib=None, offset=None, **kwargs): """Indicator: T3""" # Validate Arguments length = int(length) if length and length > 0 else 10 a = float(a) if a and a > 0 and a < 1 else 0.7 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import T3 - t3 = T3(close, length) + t3 = T3(close, length, a) else: c1 = -a * a**2 c2 = 3 * a**2 + 3 * a**3 @@ -77,6 +78,8 @@ def t3(close, length=None, a=None, offset=None, **kwargs): close (pd.Series): Series of 'close's length (int): It's period. Default: 10 a (float): 0 < a < 1. Default: 0.7 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/tema.py b/pandas_ta/overlap/tema.py index 833cae5e..dfd043ac 100644 --- a/pandas_ta/overlap/tema.py +++ b/pandas_ta/overlap/tema.py @@ -4,17 +4,18 @@ from pandas_ta.utils import get_offset, verify_series -def tema(close, length=None, offset=None, **kwargs): +def tema(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Triple Exponential Moving Average (TEMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import TEMA tema = TEMA(close, length) else: @@ -60,6 +61,8 @@ def tema(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 10 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/trima.py b/pandas_ta/overlap/trima.py index e424e591..f7a36725 100644 --- a/pandas_ta/overlap/trima.py +++ b/pandas_ta/overlap/trima.py @@ -4,17 +4,18 @@ from pandas_ta.utils import get_offset, verify_series -def trima(close, length=None, offset=None, **kwargs): +def trima(close, length=None, talib=None, offset=None, **kwargs): """Indicator: Triangular Moving Average (TRIMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import TRIMA trima = TRIMA(close, length) else: @@ -61,6 +62,8 @@ def trima(close, length=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 10 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/wcp.py b/pandas_ta/overlap/wcp.py index 5d92ce84..fa781707 100644 --- a/pandas_ta/overlap/wcp.py +++ b/pandas_ta/overlap/wcp.py @@ -3,16 +3,17 @@ from pandas_ta.utils import get_offset, verify_series -def wcp(high, low, close, offset=None, **kwargs): +def wcp(high, low, close, talib=None, offset=None, **kwargs): """Indicator: Weighted Closing Price (WCP)""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import WCLPRICE wcp = WCLPRICE(high, low, close) else: @@ -51,6 +52,8 @@ def wcp(high, low, close, offset=None, **kwargs): high (pd.Series): Series of 'high's low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/overlap/wma.py b/pandas_ta/overlap/wma.py index a5cfe601..6f8dcb68 100644 --- a/pandas_ta/overlap/wma.py +++ b/pandas_ta/overlap/wma.py @@ -4,18 +4,19 @@ from pandas_ta.utils import get_offset, verify_series -def wma(close, length=None, asc=None, offset=None, **kwargs): +def wma(close, length=None, asc=None, talib=None, offset=None, **kwargs): """Indicator: Weighted Moving Average (WMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 asc = asc if asc else True close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import WMA wma = WMA(close, length) else: @@ -78,6 +79,8 @@ def _compute(x): close (pd.Series): Series of 'close's length (int): It's period. Default: 10 asc (bool): Recent values weigh more. Default: True + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/statistics/stdev.py b/pandas_ta/statistics/stdev.py index 9fff9288..d5c2ddfa 100644 --- a/pandas_ta/statistics/stdev.py +++ b/pandas_ta/statistics/stdev.py @@ -5,18 +5,19 @@ from pandas_ta.utils import get_offset, verify_series -def stdev(close, length=None, ddof=None, offset=None, **kwargs): +def stdev(close, length=None, ddof=None, talib=None, offset=None, **kwargs): """Indicator: Standard Deviation""" # Validate Arguments length = int(length) if length and length > 0 else 30 - ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 1 + ddof = int(ddof) if isinstance(ddof, int) and ddof >= 0 and ddof < length else 1 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import STDDEV stdev = STDDEV(close, length) else: @@ -56,6 +57,8 @@ def stdev(close, length=None, ddof=None, offset=None, **kwargs): ddof (int): Delta Degrees of Freedom. The divisor used in calculations is N - ddof, where N represents the number of elements. Default: 1 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/statistics/variance.py b/pandas_ta/statistics/variance.py index ffa6c99c..520f7da0 100644 --- a/pandas_ta/statistics/variance.py +++ b/pandas_ta/statistics/variance.py @@ -3,19 +3,20 @@ from pandas_ta.utils import get_offset, verify_series -def variance(close, length=None, ddof=None, offset=None, **kwargs): +def variance(close, length=None, ddof=None, talib=None, offset=None, **kwargs): """Indicator: Variance""" # Validate Arguments length = int(length) if length and length > 1 else 30 - ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 0 + ddof = int(ddof) if isinstance(ddof, int) and ddof >= 0 and ddof < length else 1 min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length close = verify_series(close, max(length, min_periods)) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import VAR variance = VAR(close, length) else: @@ -54,6 +55,8 @@ def variance(close, length=None, ddof=None, offset=None, **kwargs): ddof (int): Delta Degrees of Freedom. The divisor used in calculations is N - ddof, where N represents the number of elements. Default: 0 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/trend/adx.py b/pandas_ta/trend/adx.py index aace7c2f..99063246 100644 --- a/pandas_ta/trend/adx.py +++ b/pandas_ta/trend/adx.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series, zero -def adx(high, low, close, length=None, lensig=None, mamode=None, scalar=None, drift=None, offset=None, **kwargs): +def adx(high, low, close, length=None, lensig=None, scalar=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: ADX""" # Validate Arguments length = length if length and length > 0 else 14 @@ -138,6 +138,7 @@ def adx(high, low, close, length=None, lensig=None, mamode=None, scalar=None, dr length (int): It's period. Default: 14 lensig (int): Signal Length. Like TradingView's default ADX. Default: length scalar (float): How much to magnify. Default: 100 + mamode (str): See ```help(ta.ma)```. Default: 'rma' drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/trend/amat.py b/pandas_ta/trend/amat.py index aba60f0b..42f903bb 100644 --- a/pandas_ta/trend/amat.py +++ b/pandas_ta/trend/amat.py @@ -6,7 +6,7 @@ from pandas_ta.utils import get_offset, verify_series -def amat(close=None, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): +def amat(close=None, fast=None, slow=None, lookback=None, mamode=None, offset=None, **kwargs): """Indicator: Archer Moving Averages Trends (AMAT)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 8 diff --git a/pandas_ta/trend/aroon.py b/pandas_ta/trend/aroon.py index d74317ca..9e139178 100644 --- a/pandas_ta/trend/aroon.py +++ b/pandas_ta/trend/aroon.py @@ -5,7 +5,7 @@ from pandas_ta.utils import recent_maximum_index, recent_minimum_index -def aroon(high, low, length=None, scalar=None, offset=None, **kwargs): +def aroon(high, low, length=None, scalar=None, talib=None, offset=None, **kwargs): """Indicator: Aroon & Aroon Oscillator""" # Validate Arguments length = length if length and length > 0 else 14 @@ -13,11 +13,12 @@ def aroon(high, low, length=None, scalar=None, offset=None, **kwargs): high = verify_series(high, length) low = verify_series(low, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import AROON, AROONOSC aroon_down, aroon_up = AROON(high, low, length) aroon_osc = AROONOSC(high, low, length) @@ -94,6 +95,8 @@ def aroon(high, low, length=None, scalar=None, offset=None, **kwargs): close (pd.Series): Series of 'close's length (int): It's period. Default: 14 scalar (float): How much to magnify. Default: 100 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/trend/cksp.py b/pandas_ta/trend/cksp.py index 36461712..ff484a18 100644 --- a/pandas_ta/trend/cksp.py +++ b/pandas_ta/trend/cksp.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_offset, verify_series -def cksp(high, low, close, p=None, x=None, q=None, offset=None, tvmode=None, **kwargs): +def cksp(high, low, close, p=None, x=None, q=None, tvmode=None, offset=None, **kwargs): """Indicator: Chande Kroll Stop (CKSP)""" # Validate Arguments # TV defaults=(10,1,9), book defaults = (10,3,20) @@ -23,7 +23,7 @@ def cksp(high, low, close, p=None, x=None, q=None, offset=None, tvmode=None, **k mamode = "rma" if tvmode is True else "sma" # Calculate Result - atr_ = atr(high=high, low=low, close=close, length=p, mamode = mamode) + atr_ = atr(high=high, low=low, close=close, length=p, mamode=mamode) long_stop_ = high.rolling(p).max() - x * atr_ long_stop = long_stop_.rolling(q).max() @@ -90,8 +90,8 @@ def cksp(high, low, close, p=None, x=None, q=None, offset=None, tvmode=None, **k p (int): ATR and first stop period. Default: 10 in both modes x (float): ATR scalar. Default: 1 in TV mode, 3 otherwise q (int): Second stop period. Default: 9 in TV mode, 20 otherwise - offset (int): How many periods to offset the result. Default: 0 tvmode (bool): Trading View or book implementation mode. Default: True + offset (int): How many periods to offset the result. Default: 0 Kwargs: fillna (value, optional): pd.DataFrame.fillna(value) diff --git a/pandas_ta/trend/decay.py b/pandas_ta/trend/decay.py index f4b57fbe..65fd9689 100644 --- a/pandas_ta/trend/decay.py +++ b/pandas_ta/trend/decay.py @@ -63,7 +63,7 @@ def decay(close, kind=None, length=None, mode=None, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 1 - mamode (str): Option "exponential" ("exp"). Default: 'linear' or None + mode (str): If 'exp' then "exponential" decay. Default: 'linear' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/trend/dpo.py b/pandas_ta/trend/dpo.py index a3534690..91ca667e 100644 --- a/pandas_ta/trend/dpo.py +++ b/pandas_ta/trend/dpo.py @@ -9,6 +9,8 @@ def dpo(close, length=None, centered=True, offset=None, **kwargs): length = int(length) if length and length > 0 else 20 close = verify_series(close, length) offset = get_offset(offset) + if not kwargs.get("lookahead", True): + centered = False if close is None: return diff --git a/pandas_ta/trend/psar.py b/pandas_ta/trend/psar.py index 5ef278ee..a3fc9c6b 100644 --- a/pandas_ta/trend/psar.py +++ b/pandas_ta/trend/psar.py @@ -37,7 +37,7 @@ def _falling(high, low, drift:int=1): long = Series(npNaN, index=high.index) short = long.copy() - reversal = Series(False, index=high.index) + reversal = Series(0, index=high.index) _af = long.copy() _af.iloc[0:2] = af0 @@ -81,7 +81,7 @@ def _falling(high, low, drift:int=1): long.iloc[row] = sar _af.iloc[row] = af - reversal.iloc[row] = reverse + reversal.iloc[row] = int(reverse) # Offset if offset != 0: diff --git a/pandas_ta/utils/_core.py b/pandas_ta/utils/_core.py index 14c895e3..bcb269c9 100644 --- a/pandas_ta/utils/_core.py +++ b/pandas_ta/utils/_core.py @@ -6,6 +6,7 @@ from numpy import argmax, argmin from pandas import DataFrame, Series from pandas.api.types import is_datetime64_any_dtype +from pandas_ta import Imports def _camelCase2Title(x: str): @@ -82,6 +83,23 @@ def signed_series(series: Series, initial: int = None) -> Series: return sign +def tal_ma(name: str) -> int: + """Helper Function that returns the Enum value for TA Lib's MA Type""" + if Imports["talib"] and isinstance(name, str) and len(name) > 1: + from talib import MA_Type + name = name.lower() + if name == "sma": return MA_Type.SMA # 0 + elif name == "ema": return MA_Type.EMA # 1 + elif name == "wma": return MA_Type.WMA # 2 + elif name == "dema": return MA_Type.DEMA # 3 + elif name == "tema": return MA_Type.TEMA # 4 + elif name == "trima": return MA_Type.TRIMA # 5 + elif name == "kama": return MA_Type.KAMA # 6 + elif name == "mama": return MA_Type.MAMA # 7 + elif name == "t3": return MA_Type.T3 # 8 + return 0 # Default: SMA -> 0 + + def unsigned_differences(series: Series, amount: int = None, **kwargs) -> Series: """Unsigned Differences Returns two Series, an unsigned positive and unsigned negative series based diff --git a/pandas_ta/utils/data/yahoofinance.py b/pandas_ta/utils/data/yahoofinance.py index 10c37672..3fa464e2 100644 --- a/pandas_ta/utils/data/yahoofinance.py +++ b/pandas_ta/utils/data/yahoofinance.py @@ -87,7 +87,14 @@ def yf(ticker: str, **kwargs): # Ticker Info & Chart History yfd = yfra.Ticker(ticker) - df = yfd.history(period=period, interval=interval, proxy=proxy, **kwargs) + + try: + df = yfd.history(period=period, interval=interval, proxy=proxy, **kwargs) + except: + if yfra.__version__ == "0.1.60": + print(f"[!] If history is not downloading, see yfinance Issue #760 by user djl0.") + print(f"[!] https://github.com/ranaroussi/yfinance/issues/760#issuecomment-877355832") + return if df.empty: return df.name = ticker diff --git a/pandas_ta/volatility/accbands.py b/pandas_ta/volatility/accbands.py index 1ee50457..920986ba 100644 --- a/pandas_ta/volatility/accbands.py +++ b/pandas_ta/volatility/accbands.py @@ -93,7 +93,7 @@ def accbands(high, low, close, length=None, c=None, drift=None, mamode=None, off close (pd.Series): Series of 'close's length (int): It's period. Default: 10 c (int): Multiplier. Default: 4 - mamode (str): Two options: None or 'ema'. Default: 'ema' + mamode (str): See ```help(ta.ma)```. Default: 'sma' drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/volatility/atr.py b/pandas_ta/volatility/atr.py index ea861ea7..d29cf209 100644 --- a/pandas_ta/volatility/atr.py +++ b/pandas_ta/volatility/atr.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def atr(high, low, close, length=None, mamode=None, drift=None, offset=None, **kwargs): +def atr(high, low, close, length=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Average True Range (ATR)""" # Validate arguments length = int(length) if length and length > 0 else 14 @@ -15,13 +15,14 @@ def atr(high, low, close, length=None, mamode=None, drift=None, offset=None, **k close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import ATR - atr = ATR(high, low, close) + atr = ATR(high, low, close, length) else: tr = true_range(high=high, low=low, close=close, drift=drift) atr = ma(mamode, tr, length=length) @@ -83,7 +84,9 @@ def atr(high, low, close, length=None, mamode=None, drift=None, offset=None, **k low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's length (int): It's period. Default: 14 - mamode (str): "sma", "ema", "wma" or "rma". Default: "rma" + mamode (str): See ```help(ta.ma)```. Default: 'rma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/volatility/bbands.py b/pandas_ta/volatility/bbands.py index bfbc489f..e142a5ea 100644 --- a/pandas_ta/volatility/bbands.py +++ b/pandas_ta/volatility/bbands.py @@ -3,10 +3,10 @@ from pandas_ta import Imports from pandas_ta.overlap import ma from pandas_ta.statistics import stdev -from pandas_ta.utils import get_offset, non_zero_range, verify_series +from pandas_ta.utils import get_offset, non_zero_range, tal_ma, verify_series -def bbands(close, length=None, std=None, mamode=None, ddof=0, offset=None, **kwargs): +def bbands(close, length=None, std=None, ddof=0, mamode=None, talib=None, offset=None, **kwargs): """Indicator: Bollinger Bands (BBANDS)""" # Validate arguments length = int(length) if length and length > 0 else 5 @@ -15,13 +15,14 @@ def bbands(close, length=None, std=None, mamode=None, ddof=0, offset=None, **kwa ddof = int(ddof) if ddof >= 0 and ddof < length else 1 close = verify_series(close, length) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import BBANDS - upper, mid, lower = BBANDS(close, length) + upper, mid, lower = BBANDS(close, length, std, std, tal_ma(mamode)) else: standard_deviation = stdev(close=close, length=length, ddof=ddof) deviations = std * standard_deviation @@ -108,8 +109,10 @@ def bbands(close, length=None, std=None, mamode=None, ddof=0, offset=None, **kwa close (pd.Series): Series of 'close's length (int): The short period. Default: 5 std (int): The long period. Default: 2 - mamode (str): Two options: "sma" or "ema". Default: "sma" ddof (int): Degrees of Freedom to use. Default: 0 + mamode (str): See ```help(ta.ma)```. Default: 'sma' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volatility/hwc.py b/pandas_ta/volatility/hwc.py index e531aaeb..b539c9c0 100644 --- a/pandas_ta/volatility/hwc.py +++ b/pandas_ta/volatility/hwc.py @@ -85,25 +85,25 @@ def hwc(close, na=None, nb=None, nc=None, nd=None, scalar=None, channel_eval=Non # Name and Categorize it # suffix = f'{str(na).replace(".", "")}-{str(nb).replace(".", "")}-{str(nc).replace(".", "")}' - hwc.name = 'HW-MID' - hwc_upper.name = "HW-UPPER" - hwc_lower.name = "HW-LOWER" + hwc.name = "HWM" + hwc_upper.name = "HWU" + hwc_lower.name = "HWL" hwc.category = hwc_upper.category = hwc_lower.category = "volatility" if channel_eval: - hwc_width.name = 'HW-WIDTH' - hwc_pctwidth.name = 'HW-PCTW' + hwc_width.name = "HWW" + hwc_pctwidth.name = "HWPCT" # Prepare DataFrame to return if channel_eval: data = {hwc.name: hwc, hwc_upper.name: hwc_upper, hwc_lower.name: hwc_lower, hwc_width.name: hwc_width, hwc_pctwidth.name: hwc_pctwidth} df = DataFrame(data) - df.name = "hwc" + df.name = "HWC" df.category = hwc.category else: data = {hwc.name: hwc, hwc_upper.name: hwc_upper, hwc_lower.name: hwc_lower} df = DataFrame(data) - df.name = "hwc" + df.name = "HWC" df.category = hwc.category return df @@ -149,5 +149,5 @@ def hwc(close, na=None, nb=None, nc=None, nd=None, scalar=None, channel_eval=Non fillna (value, optional): pd.DataFrame.fillna(value) fill_method (value, optional): Type of fill method Returns: - pd.DataFrame: HW-MID, HW-UPPER, HW-LOWER columns. + pd.DataFrame: HWM (Mid), HWU (Upper), HWL (Lower) columns. """ diff --git a/pandas_ta/volatility/kc.py b/pandas_ta/volatility/kc.py index 31a81153..ba8e43c5 100644 --- a/pandas_ta/volatility/kc.py +++ b/pandas_ta/volatility/kc.py @@ -100,7 +100,7 @@ def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **k close (pd.Series): Series of 'close's length (int): The short period. Default: 20 scalar (float): A positive float to scale the bands. Default: 2 - mamode (str): Two options: "sma" or "ema". Default: "ema" + mamode (str): See ```help(ta.ma)```. Default: 'ema' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volatility/natr.py b/pandas_ta/volatility/natr.py index abb1de5b..be4993fa 100644 --- a/pandas_ta/volatility/natr.py +++ b/pandas_ta/volatility/natr.py @@ -4,7 +4,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def natr(high, low, close, length=None, mamode=None, scalar=None, drift=None, offset=None, **kwargs): +def natr(high, low, close, length=None, scalar=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Normalized Average True Range (NATR)""" # Validate arguments length = int(length) if length and length > 0 else 14 @@ -15,13 +15,14 @@ def natr(high, low, close, length=None, mamode=None, scalar=None, drift=None, of close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import NATR - natr = NATR(high, low, close) + natr = NATR(high, low, close, length) else: natr = scalar / close natr *= atr(high=high, low=low, close=close, length=length, mamode=mamode, drift=drift, offset=offset, **kwargs) @@ -63,6 +64,9 @@ def natr(high, low, close, length=None, mamode=None, scalar=None, drift=None, of close (pd.Series): Series of 'close's length (int): The short period. Default: 20 scalar (float): How much to magnify. Default: 100 + mamode (str): See ```help(ta.ma)```. Default: 'ema' + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volatility/rvi.py b/pandas_ta/volatility/rvi.py index 29225c29..7df65380 100644 --- a/pandas_ta/volatility/rvi.py +++ b/pandas_ta/volatility/rvi.py @@ -101,10 +101,10 @@ def _rvi(source, length, scalar, mode, drift): close (pd.Series): Series of 'close's length (int): The short period. Default: 14 scalar (float): A positive float to scale the bands. Default: 100 - mamode (str): Options: 'sma' or 'ema'. Default: 'sma' refined (bool): Use 'refined' calculation which is the average of RVI(high) and RVI(low) instead of RVI(close). Default: False thirds (bool): Average of high, low and close. Default: False + mamode (str): See ```help(ta.ma)```. Default: 'ema' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volatility/thermo.py b/pandas_ta/volatility/thermo.py index d1e539e7..b628033e 100644 --- a/pandas_ta/volatility/thermo.py +++ b/pandas_ta/volatility/thermo.py @@ -112,8 +112,8 @@ def thermo(high, low, length=None, long=None, short=None, mamode=None, drift=Non long(int): The buy factor short(float): The sell factor length (int): The period. Default: 20 + mamode (str): See ```help(ta.ma)```. Default: 'ema' drift (int): The diff period. Default: 1 - mamode (str): Three options: "ema", "sma", or "hma". Default: "ema" offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volatility/true_range.py b/pandas_ta/volatility/true_range.py index 687b6307..f2c0331e 100644 --- a/pandas_ta/volatility/true_range.py +++ b/pandas_ta/volatility/true_range.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series -def true_range(high, low, close, drift=None, offset=None, **kwargs): +def true_range(high, low, close, talib=None, drift=None, offset=None, **kwargs): """Indicator: True Range""" # Validate arguments high = verify_series(high) @@ -13,9 +13,10 @@ def true_range(high, low, close, drift=None, offset=None, **kwargs): close = verify_series(close) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import TRANGE true_range = TRANGE(high, low, close) else: @@ -63,6 +64,8 @@ def true_range(high, low, close, drift=None, offset=None, **kwargs): high (pd.Series): Series of 'high's low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The shift period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/volume/ad.py b/pandas_ta/volume/ad.py index 557acdea..4c0e9755 100644 --- a/pandas_ta/volume/ad.py +++ b/pandas_ta/volume/ad.py @@ -3,7 +3,7 @@ from pandas_ta.utils import get_offset, non_zero_range, verify_series -def ad(high, low, close, volume, open_=None, offset=None, **kwargs): +def ad(high, low, close, volume, open_=None, talib=None, offset=None, **kwargs): """Indicator: Accumulation/Distribution (AD)""" # Validate Arguments high = verify_series(high) @@ -11,9 +11,10 @@ def ad(high, low, close, volume, open_=None, offset=None, **kwargs): close = verify_series(close) volume = verify_series(volume) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import AD ad = AD(high, low, close, volume) else: @@ -70,6 +71,8 @@ def ad(high, low, close, volume, open_=None, offset=None, **kwargs): close (pd.Series): Series of 'close's volume (pd.Series): Series of 'volume's open (pd.Series): Series of 'open's + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volume/adosc.py b/pandas_ta/volume/adosc.py index 0f1ddca5..b315579b 100644 --- a/pandas_ta/volume/adosc.py +++ b/pandas_ta/volume/adosc.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_offset, verify_series -def adosc(high, low, close, volume, open_=None, fast=None, slow=None, offset=None, **kwargs): +def adosc(high, low, close, volume, open_=None, fast=None, slow=None, talib=None, offset=None, **kwargs): """Indicator: Accumulation/Distribution Oscillator""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 3 @@ -17,13 +17,14 @@ def adosc(high, low, close, volume, open_=None, fast=None, slow=None, offset=Non volume = verify_series(volume, _length) offset = get_offset(offset) if "length" in kwargs: kwargs.pop("length") + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None or volume is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import ADOSC - adosc = ADOSC(high, low, close, volume) + adosc = ADOSC(high, low, close, volume, fast, slow) else: ad_ = ad(high=high, low=low, close=close, volume=volume, open_=open_) fast_ad = ema(close=ad_, length=fast, **kwargs) @@ -74,6 +75,8 @@ def adosc(high, low, close, volume, open_=None, fast=None, slow=None, offset=Non volume (pd.Series): Series of 'volume's fast (int): The short period. Default: 12 slow (int): The long period. Default: 26 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volume/aobv.py b/pandas_ta/volume/aobv.py index a2c06990..70c3539c 100644 --- a/pandas_ta/volume/aobv.py +++ b/pandas_ta/volume/aobv.py @@ -6,7 +6,7 @@ from pandas_ta.utils import get_offset, verify_series -def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, min_lookback=None, offset=None, **kwargs): +def aobv(close, volume, fast=None, slow=None, max_lookback=None, min_lookback=None, mamode=None, offset=None, **kwargs): """Indicator: Archer On Balance Volume (AOBV)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 4 diff --git a/pandas_ta/volume/efi.py b/pandas_ta/volume/efi.py index 63643456..79d567ed 100644 --- a/pandas_ta/volume/efi.py +++ b/pandas_ta/volume/efi.py @@ -3,7 +3,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwargs): +def efi(close, volume, length=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Elder's Force Index (EFI)""" # Validate arguments length = int(length) if length and length > 0 else 13 @@ -63,7 +63,7 @@ def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwar volume (pd.Series): Series of 'volume's length (int): The short period. Default: 13 drift (int): The diff period. Default: 1 - mamode (str): Two options: None or "sma". Default: None + mamode (str): See ```help(ta.ma)```. Default: 'ema' offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volume/kvo.py b/pandas_ta/volume/kvo.py index a497a02a..927dd048 100644 --- a/pandas_ta/volume/kvo.py +++ b/pandas_ta/volume/kvo.py @@ -1,18 +1,17 @@ # -*- coding: utf-8 -*- -from numpy import where as npWhere from pandas import DataFrame from pandas_ta.overlap import hlc3, ma -from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series +from pandas_ta.utils import get_drift, get_offset, signed_series, verify_series -def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode=None, drift=None, offset=None, **kwargs): +def kvo(high, low, close, volume, fast=None, slow=None, signal=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Klinger Volume Oscillator (KVO)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 34 slow = int(slow) if slow and slow > 0 else 55 - length_sig = int(length_sig) if length_sig and length_sig > 0 else 13 + signal = int(signal) if signal and signal > 0 else 13 mamode = mamode.lower() if mamode and isinstance(mamode, str) else "ema" - _length = max(fast, slow, length_sig) + _length = max(fast, slow, signal) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) @@ -23,19 +22,10 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode= if high is None or low is None or close is None or volume is None: return # Calculate Result - mom = hlc3(high, low, close).diff(drift) - trend = npWhere(mom > 0, 1, 0) + npWhere(mom < 0, -1, 0) - dm = non_zero_range(high, low) - - m = high.size - cm = [0] * m - for i in range(1, m): - cm[i] = (cm[i - 1] + dm[i]) if trend[i] == trend[i - 1] else (dm[i - 1] + dm[i]) - - vf = 100 * volume * trend * abs(2 * dm / cm - 1) - - kvo = ma(mamode, vf, length=fast) - ma(mamode, vf, length=slow) - kvo_signal = ma(mamode, kvo, length=length_sig) + signed_volume = volume * signed_series(hlc3(high, low, close), 1) + sv = signed_volume.loc[signed_volume.first_valid_index():,] + kvo = ma(mamode, sv, length=fast) - ma(mamode, sv, length=slow) + kvo_signal = ma(mamode, kvo.loc[kvo.first_valid_index():,], length=signal) # Offset if offset != 0: @@ -51,17 +41,18 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode= kvo_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it - kvo.name = f"KVO_{fast}_{slow}" - kvo_signal.name = f"KVOSig_{length_sig}" + _props = f"_{fast}_{slow}_{signal}" + kvo.name = f"KVO{_props}" + kvo_signal.name = f"KVOs{_props}" kvo.category = kvo_signal.category = "volume" # Prepare DataFrame to return data = {kvo.name: kvo, kvo_signal.name: kvo_signal} - kvoandsig = DataFrame(data) - kvoandsig.name = f"KVO_{fast}_{slow}_{length_sig}" - kvoandsig.category = kvo.category + df = DataFrame(data) + df.name = f"KVO{_props}" + df.category = kvo.category - return kvoandsig + return df kvo.__doc__ = \ @@ -71,23 +62,17 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode= price reversals in a market by comparing volume to price. Sources: - https://www.tradingview.com/script/Qnn7ymRK-Klinger-Volume-Oscillator/ + https://www.investopedia.com/terms/k/klingeroscillator.asp https://www.daytrading.com/klinger-volume-oscillator Calculation: Default Inputs: - fast=34, slow=55, length_sig=13, drift=1 - MOM = HLC3.diff(drift) - NEG_TREND = -1 if MOM < 0 else 0 - POS_TREND = 1 if MOM > 0 else 0 - TREND = POS_TREND + NEG_TREND - DM = high - low - CM = [CMt-1 + DMt if TRENDt == TRENDt-1 else DMt-1 + DMt] - - vf = 100 * volume * TREND * abs(2 * dm / cm - 1) - kvo = ema(vf, fast) - ema(vf, slow) - kvo_signal = ema(kvo, length_sig) + fast=34, slow=55, signal=13, drift=1 + EMA = Exponential Moving Average + SV = volume * signed_series(HLC3, 1) + KVO = EMA(SV, fast) - EMA(SV, slow) + Signal = EMA(KVO, signal) Args: high (pd.Series): Series of 'high's @@ -97,7 +82,7 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode= fast (int): The fast period. Default: 34 long (int): The long period. Default: 55 length_sig (int): The signal period. Default: 13 - mamode (str): "sma", "ema", "wma" or "rma". Default: "ema" + mamode (str): See ```help(ta.ma)```. Default: 'ema' offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -105,5 +90,5 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode= fill_method (value, optional): Type of fill method Returns: - pd.DataFrame: kvo and kvo_signal columns. + pd.DataFrame: KVO and Signal columns. """ diff --git a/pandas_ta/volume/mfi.py b/pandas_ta/volume/mfi.py index 72c93131..1363b00a 100644 --- a/pandas_ta/volume/mfi.py +++ b/pandas_ta/volume/mfi.py @@ -5,7 +5,7 @@ from pandas_ta.utils import get_drift, get_offset, verify_series -def mfi(high, low, close, volume, length=None, drift=None, offset=None, **kwargs): +def mfi(high, low, close, volume, length=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Money Flow Index (MFI)""" # Validate arguments length = int(length) if length and length > 0 else 14 @@ -15,13 +15,14 @@ def mfi(high, low, close, volume, length=None, drift=None, offset=None, **kwargs volume = verify_series(volume, length) drift = get_drift(drift) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None or volume is None: return # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import MFI - mfi = MFI(high, low, close, volume) + mfi = MFI(high, low, close, volume, length) else: typical_price = hlc3(high=high, low=low, close=close) raw_money_flow = typical_price * volume @@ -84,6 +85,8 @@ def mfi(high, low, close, volume, length=None, drift=None, offset=None, **kwargs close (pd.Series): Series of 'close's volume (pd.Series): Series of 'volume's length (int): The sum period. Default: 14 + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True drift (int): The difference period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/volume/nvi.py b/pandas_ta/volume/nvi.py index fb08b318..1c1ceadb 100644 --- a/pandas_ta/volume/nvi.py +++ b/pandas_ta/volume/nvi.py @@ -17,7 +17,7 @@ def nvi(close, volume, length=None, initial=None, offset=None, **kwargs): # Calculate Result roc_ = roc(close=close, length=length) - signed_volume = signed_series(volume, initial=1) + signed_volume = signed_series(volume, 1) nvi = signed_volume[signed_volume < 0].abs() * roc_ nvi.fillna(0, inplace=True) nvi.iloc[0] = initial diff --git a/pandas_ta/volume/obv.py b/pandas_ta/volume/obv.py index 539b09b8..ec15be17 100644 --- a/pandas_ta/volume/obv.py +++ b/pandas_ta/volume/obv.py @@ -3,15 +3,16 @@ from pandas_ta.utils import get_offset, signed_series, verify_series -def obv(close, volume, offset=None, **kwargs): +def obv(close, volume, talib=None, offset=None, **kwargs): """Indicator: On Balance Volume (OBV)""" # Validate arguments close = verify_series(close) volume = verify_series(volume) offset = get_offset(offset) + mode_tal = bool(talib) if isinstance(talib, bool) else True # Calculate Result - if Imports["talib"]: + if Imports["talib"] and mode_tal: from talib import OBV obv = OBV(close, volume) else: @@ -53,6 +54,8 @@ def obv(close, volume, offset=None, **kwargs): Args: close (pd.Series): Series of 'close's volume (pd.Series): Series of 'volume's + talib (bool): If TA Lib is installed and talib is True, Returns the TA Lib + version. Default: True offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volume/pvi.py b/pandas_ta/volume/pvi.py index 8ab2da91..302d378e 100644 --- a/pandas_ta/volume/pvi.py +++ b/pandas_ta/volume/pvi.py @@ -16,9 +16,8 @@ def pvi(close, volume, length=None, initial=None, offset=None, **kwargs): if close is None or volume is None: return # Calculate Result - roc_ = roc(close=close, length=length) - signed_volume = signed_series(volume, initial=1) - pvi = signed_volume[signed_volume > 0].abs() * roc_ + signed_volume = signed_series(volume, 1) + pvi = roc(close=close, length=length) * signed_volume[signed_volume > 0].abs() pvi.fillna(0, inplace=True) pvi.iloc[0] = initial pvi = pvi.cumsum() diff --git a/pandas_ta/volume/pvol.py b/pandas_ta/volume/pvol.py index 8168943d..aba68324 100644 --- a/pandas_ta/volume/pvol.py +++ b/pandas_ta/volume/pvol.py @@ -11,10 +11,9 @@ def pvol(close, volume, offset=None, **kwargs): signed = kwargs.pop("signed", False) # Calculate Result + pvol = close * volume if signed: - pvol = signed_series(close, 1) * close * volume - else: - pvol = close * volume + pvol *= signed_series(close, 1) # Offset if offset != 0: diff --git a/pandas_ta/volume/vp.py b/pandas_ta/volume/vp.py index b6f52735..7a5d7cef 100644 --- a/pandas_ta/volume/vp.py +++ b/pandas_ta/volume/vp.py @@ -16,10 +16,10 @@ def vp(close, volume, width=None, **kwargs): if close is None or volume is None: return # Setup - signed_price = signed_series(close, initial=1) - pos_volume = signed_price[signed_price > 0] * volume + signed_price = signed_series(close, 1) + pos_volume = volume * signed_price[signed_price > 0] pos_volume.name = volume.name - neg_volume = signed_price[signed_price < 0] * -volume + neg_volume = -volume * signed_price[signed_price < 0] neg_volume.name = volume.name vp = concat([close, pos_volume, neg_volume], axis=1) diff --git a/requirements.txt b/requirements.txt index 21c37a1f..74f7aec7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ -numpy>=1.20.2 -pandas>=1.2.4 \ No newline at end of file +numpy==1.19.5 +pandas==1.2.0 +python-dateutil==2.8.1 +pytz==2021.1 +six==1.16.0 diff --git a/setup.py b/setup.py index 0837bedf..0dc65944 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "3", "02b")), + version=".".join(("0", "3", "14b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_candle.py b/tests/test_ext_indicator_candle.py index 787040c0..55ba9d70 100644 --- a/tests/test_ext_indicator_candle.py +++ b/tests/test_ext_indicator_candle.py @@ -1,7 +1,7 @@ from .config import sample_data from .context import pandas_ta -from unittest import TestCase +from unittest import TestCase, skip from pandas import DataFrame @@ -17,7 +17,6 @@ def tearDownClass(cls): def setUp(self): pass def tearDown(self): pass - def test_cdl_doji_ext(self): self.data.ta.cdl_pattern("doji", append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_ext_indicator_momentum.py b/tests/test_ext_indicator_momentum.py index e8e8c01d..fee7d091 100644 --- a/tests/test_ext_indicator_momentum.py +++ b/tests/test_ext_indicator_momentum.py @@ -247,7 +247,7 @@ def test_trix_ext(self): def test_tsi_ext(self): self.data.ta.tsi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], "TSI_13_25") + self.assertEqual(list(self.data.columns[-2:]), ["TSI_13_25_13", "TSIs_13_25_13"]) def test_uo_ext(self): self.data.ta.uo(append=True) diff --git a/tests/test_ext_indicator_overlap_ext.py b/tests/test_ext_indicator_overlap_ext.py index f56ba411..e6bdebc5 100644 --- a/tests/test_ext_indicator_overlap_ext.py +++ b/tests/test_ext_indicator_overlap_ext.py @@ -63,6 +63,11 @@ def test_hwma_ext(self): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], "HWMA_0.2_0.1_0.1") + def test_jma_ext(self): + self.data.ta.jma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], "JMA_7_0") + def test_kama_ext(self): self.data.ta.kama(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_ext_indicator_volume.py b/tests/test_ext_indicator_volume.py index a95292ce..1ca417ec 100644 --- a/tests/test_ext_indicator_volume.py +++ b/tests/test_ext_indicator_volume.py @@ -63,7 +63,7 @@ def test_eom_ext(self): def test_kvo_ext(self): self.data.ta.kvo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], "KVOSig_13") + self.assertEqual(list(self.data.columns[-2:]), ["KVO_34_55_13", "KVOs_34_55_13"]) def test_mfi_ext(self): self.data.ta.mfi(append=True) diff --git a/tests/test_indicator_candle.py b/tests/test_indicator_candle.py index 083ef67f..619bc6e2 100644 --- a/tests/test_indicator_candle.py +++ b/tests/test_indicator_candle.py @@ -58,7 +58,7 @@ def test_cdl_doji(self): try: expected = tal.CDLDOJI(self.open, self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 84e4b689..a11e7872 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -65,60 +65,72 @@ def test_ao(self): self.assertEqual(result.name, "AO_5_34") def test_apo(self): - result = pandas_ta.apo(self.close) + result = pandas_ta.apo(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "APO_12_26") try: expected = tal.APO(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.apo(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "APO_12_26") + def test_bias(self): result = pandas_ta.bias(self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, "BIAS_SMA_26") def test_bop(self): - result = pandas_ta.bop(self.open, self.high, self.low, self.close) + result = pandas_ta.bop(self.open, self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "BOP") try: expected = tal.BOP(self.open, self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.bop(self.open, self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "BOP") + def test_brar(self): result = pandas_ta.brar(self.open, self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "BRAR_26") def test_cci(self): - result = pandas_ta.cci(self.high, self.low, self.close) + result = pandas_ta.cci(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "CCI_14_0.015") try: expected = tal.CCI(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.cci(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "CCI_14_0.015") + def test_cfo(self): result = pandas_ta.cfo(self.close) self.assertIsInstance(result, Series) @@ -137,13 +149,17 @@ def test_cmo(self): try: expected = tal.CMO(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.cmo(self.close, talib=False) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "CMO_14") + def test_coppock(self): result = pandas_ta.coppock(self.close) self.assertIsInstance(result, Series) @@ -160,7 +176,7 @@ def test_er(self): self.assertEqual(result.name, "ER_10") def test_dm(self): - result = pandas_ta.dm(self.high, self.low) + result = pandas_ta.dm(self.high, self.low, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "DM_14") @@ -169,7 +185,7 @@ def test_dm(self): expected_neg = tal.MINUS_DM(self.high, self.low) expecteddf = DataFrame({"DMP_14": expected_pos, "DMN_14": expected_neg}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: dmp = pandas_ta.utils.df_error_analysis(result.iloc[:,0], expecteddf.iloc[:,0], col=CORRELATION) self.assertGreater(dmp, CORRELATION_THRESHOLD) @@ -182,6 +198,10 @@ def test_dm(self): except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.dm(self.high, self.low) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "DM_14") + def test_eri(self): result = pandas_ta.eri(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) @@ -216,7 +236,7 @@ def test_kst(self): self.assertEqual(result.name, "KST_10_15_20_30_10_10_10_15_9") def test_macd(self): - result = pandas_ta.macd(self.close) + result = pandas_ta.macd(self.close, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "MACD_12_26_9") @@ -224,7 +244,7 @@ def test_macd(self): expected = tal.MACD(self.close) expecteddf = DataFrame({"MACD_12_26_9": expected[0], "MACDh_12_26_9": expected[2], "MACDs_12_26_9": expected[1]}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: macd_corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expecteddf.iloc[:, 0], col=CORRELATION) self.assertGreater(macd_corr, CORRELATION_THRESHOLD) @@ -243,41 +263,58 @@ def test_macd(self): except Exception as ex: error_analysis(result.iloc[:, 2], CORRELATION, ex, newline=False) + result = pandas_ta.macd(self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "MACD_12_26_9") + + def test_macdas(self): + result = pandas_ta.macd(self.close, asmode=True) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "MACDAS_12_26_9") + def test_mom(self): - result = pandas_ta.mom(self.close) + result = pandas_ta.mom(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "MOM_10") try: expected = tal.MOM(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.mom(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "MOM_10") + def test_pgo(self): result = pandas_ta.pgo(self.high, self.low, self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, "PGO_14") def test_ppo(self): - result = pandas_ta.ppo(self.close) + result = pandas_ta.ppo(self.close, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "PPO_12_26_9") try: expected = tal.PPO(self.close) pdt.assert_series_equal(result["PPO_12_26_9"], expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result["PPO_12_26_9"], expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result["PPO_12_26_9"], CORRELATION, ex) + result = pandas_ta.ppo(self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "PPO_12_26_9") + def test_psl(self): result = pandas_ta.psl(self.close) self.assertIsInstance(result, Series) @@ -294,35 +331,43 @@ def test_qqe(self): self.assertEqual(result.name, "QQE_14_5_4.236") def test_roc(self): - result = pandas_ta.roc(self.close) + result = pandas_ta.roc(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "ROC_10") try: expected = tal.ROC(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.roc(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "ROC_10") + def test_rsi(self): - result = pandas_ta.rsi(self.close) + result = pandas_ta.rsi(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "RSI_14") try: expected = tal.RSI(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.rsi(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "RSI_14") + def test_rsx(self): result = pandas_ta.rsx(self.close) self.assertIsInstance(result, Series) @@ -406,10 +451,10 @@ def test_stoch(self): self.assertEqual(result.name, "STOCH_14_3_3") try: - expected = tal.STOCH(self.high, self.low, self.close, 14, 3, 0, 3) - expecteddf = DataFrame({"STOCHk_14_3_0_3": expected[0], "STOCHd_14_3_0_3": expected[1]}) + expected = tal.STOCH(self.high, self.low, self.close, 14, 3, 0, 3, 0) + expecteddf = DataFrame({"STOCHk_14_3_0_3_0": expected[0], "STOCHd_14_3_0_3": expected[1]}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: stochk_corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expecteddf.iloc[:, 0], col=CORRELATION) self.assertGreater(stochk_corr, CORRELATION_THRESHOLD) @@ -432,7 +477,7 @@ def test_stochrsi(self): expected = tal.STOCHRSI(self.close, 14, 14, 3, 0) expecteddf = DataFrame({"STOCHRSIk_14_14_0_3": expected[0], "STOCHRSId_14_14_3_0": expected[1]}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: stochrsid_corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expecteddf.iloc[:, 1], col=CORRELATION) self.assertGreater(stochrsid_corr, CORRELATION_THRESHOLD) @@ -453,35 +498,43 @@ def test_trix(self): def test_tsi(self): result = pandas_ta.tsi(self.close) - self.assertIsInstance(result, Series) - self.assertEqual(result.name, "TSI_13_25") + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "TSI_13_25_13") def test_uo(self): - result = pandas_ta.uo(self.high, self.low, self.close) + result = pandas_ta.uo(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "UO_7_14_28") try: expected = tal.ULTOSC(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.uo(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "UO_7_14_28") + def test_willr(self): - result = pandas_ta.willr(self.high, self.low, self.close) + result = pandas_ta.willr(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "WILLR_14") try: expected = tal.WILLR(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + + result = pandas_ta.willr(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "WILLR_14") diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index b8bf9dc3..58489697 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -40,20 +40,24 @@ def test_alma(self): self.assertEqual(result.name, "ALMA_10_6.0_0.85") def test_dema(self): - result = pandas_ta.dema(self.close) + result = pandas_ta.dema(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "DEMA_10") try: expected = tal.DEMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.dema(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "DEMA_10") + def test_ema(self): result = pandas_ta.ema(self.close, presma=False) self.assertIsInstance(result, Series) @@ -62,13 +66,30 @@ def test_ema(self): try: expected = tal.EMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: + try: + corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) + self.assertGreater(corr, CORRELATION_THRESHOLD) + except Exception as ex: + error_analysis(result, CORRELATION, ex) + + result = pandas_ta.ema(self.close, talib=False) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "EMA_10") + + try: + pdt.assert_series_equal(result, expected, check_names=False) + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.ema(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "EMA_10") + def test_fwma(self): result = pandas_ta.fwma(self.close) self.assertIsInstance(result, Series) @@ -85,20 +106,24 @@ def test_hl2(self): self.assertEqual(result.name, "HL2") def test_hlc3(self): - result = pandas_ta.hlc3(self.high, self.low, self.close) + result = pandas_ta.hlc3(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "HLC3") try: expected = tal.TYPPRICE(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.hlc3(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "HLC3") + def test_hma(self): result = pandas_ta.hma(self.close) self.assertIsInstance(result, Series) @@ -114,6 +139,11 @@ def test_kama(self): self.assertIsInstance(result, Series) self.assertEqual(result.name, "KAMA_10_2_30") + def test_jma(self): + result = pandas_ta.jma(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "JMA_7_0") + def test_ichimoku(self): ichimoku, span = pandas_ta.ichimoku(self.high, self.low, self.close) self.assertIsInstance(ichimoku, DataFrame) @@ -122,70 +152,86 @@ def test_ichimoku(self): self.assertEqual(span.name, "ICHISPAN_9_26") def test_linreg(self): - result = pandas_ta.linreg(self.close) + result = pandas_ta.linreg(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "LR_14") try: expected = tal.LINEARREG(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.linreg(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "LR_14") + def test_linreg_angle(self): - result = pandas_ta.linreg(self.close, angle=True) + result = pandas_ta.linreg(self.close, angle=True, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "LRa_14") try: expected = tal.LINEARREG_ANGLE(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.linreg(self.close, angle=True) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "LRa_14") + def test_linreg_intercept(self): - result = pandas_ta.linreg(self.close, intercept=True) + result = pandas_ta.linreg(self.close, intercept=True, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "LRb_14") try: expected = tal.LINEARREG_INTERCEPT(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.linreg(self.close, intercept=True) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "LRb_14") + def test_linreg_r(self): result = pandas_ta.linreg(self.close, r=True) self.assertIsInstance(result, Series) self.assertEqual(result.name, "LRr_14") def test_linreg_slope(self): - result = pandas_ta.linreg(self.close, slope=True) + result = pandas_ta.linreg(self.close, slope=True, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "LRm_14") try: expected = tal.LINEARREG_SLOPE(self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.linreg(self.close, slope=True) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "LRm_14") + def test_ma(self): result = pandas_ta.ma() self.assertIsInstance(result, list) @@ -205,35 +251,43 @@ def test_mcgd(self): self.assertEqual(result.name, "MCGD_10") def test_midpoint(self): - result = pandas_ta.midpoint(self.close) + result = pandas_ta.midpoint(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "MIDPOINT_2") try: expected = tal.MIDPOINT(self.close, 2) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.midpoint(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "MIDPOINT_2") + def test_midprice(self): - result = pandas_ta.midprice(self.high, self.low) + result = pandas_ta.midprice(self.high, self.low, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "MIDPRICE_2") try: expected = tal.MIDPRICE(self.high, self.low, 2) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.midprice(self.high, self.low) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "MIDPRICE_2") + def test_ohlc4(self): result = pandas_ta.ohlc4(self.open, self.high, self.low, self.close) self.assertIsInstance(result, Series) @@ -255,20 +309,24 @@ def test_sinwma(self): self.assertEqual(result.name, "SINWMA_14") def test_sma(self): - result = pandas_ta.sma(self.close) + result = pandas_ta.sma(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "SMA_10") try: expected = tal.SMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.sma(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "SMA_10") + def test_ssf(self): result = pandas_ta.ssf(self.close, poles=2) self.assertIsInstance(result, Series) @@ -289,50 +347,62 @@ def test_supertrend(self): self.assertEqual(result.name, "SUPERT_7_3.0") def test_t3(self): - result = pandas_ta.t3(self.close) + result = pandas_ta.t3(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "T3_10_0.7") try: expected = tal.T3(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.t3(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "T3_10_0.7") + def test_tema(self): - result = pandas_ta.tema(self.close) + result = pandas_ta.tema(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "TEMA_10") try: expected = tal.TEMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.tema(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "TEMA_10") + def test_trima(self): - result = pandas_ta.trima(self.close) + result = pandas_ta.trima(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "TRIMA_10") try: expected = tal.TRIMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.trima(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "TRIMA_10") + def test_vidya(self): result = pandas_ta.vidya(self.close) self.assertIsInstance(result, Series) @@ -349,35 +419,43 @@ def test_vwma(self): self.assertEqual(result.name, "VWMA_10") def test_wcp(self): - result = pandas_ta.wcp(self.high, self.low, self.close) + result = pandas_ta.wcp(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "WCP") try: expected = tal.WCLPRICE(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.wcp(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "WCP") + def test_wma(self): - result = pandas_ta.wma(self.close) + result = pandas_ta.wma(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "WMA_10") try: expected = tal.WMA(self.close, 10) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.wma(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "WMA_10") + def test_zlma(self): result = pandas_ta.zlma(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_statistics.py b/tests/test_indicator_statistics.py index d580a5c2..b572b570 100644 --- a/tests/test_indicator_statistics.py +++ b/tests/test_indicator_statistics.py @@ -65,20 +65,24 @@ def test_skew(self): self.assertEqual(result.name, "SKEW_30") def test_stdev(self): - result = pandas_ta.stdev(self.close) + result = pandas_ta.stdev(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "STDEV_30") try: expected = tal.STDDEV(self.close, 30) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.stdev(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "STDEV_30") + def test_tos_sdtevall(self): result = pandas_ta.tos_stdevall(self.close) self.assertIsInstance(result, DataFrame) @@ -96,20 +100,24 @@ def test_tos_sdtevall(self): self.assertEqual(len(result.columns), 5) def test_variance(self): - result = pandas_ta.variance(self.close) + result = pandas_ta.variance(self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "VAR_30") try: expected = tal.VAR(self.close, 30) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.variance(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "VAR_30") + def test_zscore(self): result = pandas_ta.zscore(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py index 85419347..cf01c733 100644 --- a/tests/test_indicator_trend.py +++ b/tests/test_indicator_trend.py @@ -35,27 +35,31 @@ def tearDown(self): pass def test_adx(self): - result = pandas_ta.adx(self.high, self.low, self.close) + result = pandas_ta.adx(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "ADX_14") try: expected = tal.ADX(self.high, self.low, self.close) pdt.assert_series_equal(result.iloc[:, 0], expected) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.adx(self.high, self.low, self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "ADX_14") + def test_amat(self): result = pandas_ta.amat(self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "AMATe_8_21_2") def test_aroon(self): - result = pandas_ta.aroon(self.high, self.low) + result = pandas_ta.aroon(self.high, self.low, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "AROON_14") @@ -63,7 +67,7 @@ def test_aroon(self): expected = tal.AROON(self.high, self.low) expecteddf = DataFrame({"AROOND_14": expected[0], "AROONU_14": expected[1]}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: aroond_corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expecteddf.iloc[:, 0], col=CORRELATION) self.assertGreater(aroond_corr, CORRELATION_THRESHOLD) @@ -76,13 +80,19 @@ def test_aroon(self): except Exception as ex: error_analysis(result.iloc[:, 1], CORRELATION, ex, newline=False) + result = pandas_ta.aroon(self.high, self.low) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "AROON_14") + def test_aroon_osc(self): result = pandas_ta.aroon(self.high, self.low) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "AROON_14") try: expected = tal.AROONOSC(self.high, self.low) pdt.assert_series_equal(result.iloc[:, 2], expected) - except AssertionError as ae: + except AssertionError: try: aroond_corr = pandas_ta.utils.df_error_analysis(result.iloc[:,2], expected,col=CORRELATION) self.assertGreater(aroond_corr, CORRELATION_THRESHOLD) @@ -158,7 +168,7 @@ def test_psar(self): try: expected = tal.SAR(self.high, self.low) pdt.assert_series_equal(psar, expected) - except AssertionError as ae: + except AssertionError: try: psar_corr = pandas_ta.utils.df_error_analysis(psar, expected, col=CORRELATION) self.assertGreater(psar_corr, CORRELATION_THRESHOLD) diff --git a/tests/test_indicator_volatility.py b/tests/test_indicator_volatility.py index 39b149ad..381a9d16 100644 --- a/tests/test_indicator_volatility.py +++ b/tests/test_indicator_volatility.py @@ -45,26 +45,26 @@ def test_accbands(self): self.assertEqual(result.name, "ACCBANDS_20") def test_atr(self): - result = pandas_ta.atr(self.high, self.low, self.close) + result = pandas_ta.atr(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "ATRr_14") try: expected = tal.ATR(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) - def test_bbands(self): - result = pandas_ta.bbands(self.close, ddof=0) - self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, "BBANDS_5_2.0") + result = pandas_ta.atr(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "ATRr_14") - result = pandas_ta.bbands(self.close, ddof=1) + def test_bbands(self): + result = pandas_ta.bbands(self.close, talib=False) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "BBANDS_5_2.0") @@ -72,7 +72,7 @@ def test_bbands(self): expected = tal.BBANDS(self.close) expecteddf = DataFrame({"BBU_5_2.0": expected[0], "BBM_5_2.0": expected[1], "BBL_5_2.0": expected[2]}) pdt.assert_frame_equal(result, expecteddf) - except AssertionError as ae: + except AssertionError: try: bbl_corr = pandas_ta.utils.df_error_analysis(result.iloc[:, 0], expecteddf.iloc[:,0], col=CORRELATION) self.assertGreater(bbl_corr, CORRELATION_THRESHOLD) @@ -91,6 +91,14 @@ def test_bbands(self): except Exception as ex: error_analysis(result.iloc[:, 2], CORRELATION, ex, newline=False) + result = pandas_ta.bbands(self.close, ddof=0) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "BBANDS_5_2.0") + + result = pandas_ta.bbands(self.close, ddof=1) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "BBANDS_5_2.0") + def test_donchian(self): result = pandas_ta.donchian(self.high, self.low) self.assertIsInstance(result, DataFrame) @@ -115,20 +123,24 @@ def test_massi(self): self.assertEqual(result.name, "MASSI_9_25") def test_natr(self): - result = pandas_ta.natr(self.high, self.low, self.close) + result = pandas_ta.natr(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "NATR_14") try: expected = tal.NATR(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.natr(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "NATR_14") + def test_pdist(self): result = pandas_ta.pdist(self.open, self.high, self.low, self.close) self.assertIsInstance(result, Series) @@ -153,20 +165,24 @@ def test_thermo(self): self.assertEqual(result.name, "THERMO_20_2_0.5") def test_true_range(self): - result = pandas_ta.true_range(self.high, self.low, self.close) + result = pandas_ta.true_range(self.high, self.low, self.close, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "TRUERANGE_1") try: expected = tal.TRANGE(self.high, self.low, self.close) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.true_range(self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "TRUERANGE_1") + def test_ui(self): result = pandas_ta.ui(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_volume.py b/tests/test_indicator_volume.py index 6dda44a9..dae1f54d 100644 --- a/tests/test_indicator_volume.py +++ b/tests/test_indicator_volume.py @@ -35,40 +35,48 @@ def tearDown(self): pass def test_ad(self): - result = pandas_ta.ad(self.high, self.low, self.close, self.volume_) + result = pandas_ta.ad(self.high, self.low, self.close, self.volume_, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "AD") try: expected = tal.AD(self.high, self.low, self.close, self.volume_) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.ad(self.high, self.low, self.close, self.volume_) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "AD") + def test_ad_open(self): result = pandas_ta.ad(self.high, self.low, self.close, self.volume_, self.open) self.assertIsInstance(result, Series) self.assertEqual(result.name, "ADo") def test_adosc(self): - result = pandas_ta.adosc(self.high, self.low, self.close, self.volume_) + result = pandas_ta.adosc(self.high, self.low, self.close, self.volume_, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "ADOSC_3_10") try: expected = tal.ADOSC(self.high, self.low, self.close, self.volume_) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.adosc(self.high, self.low, self.close, self.volume_) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "ADOSC_3_10") + def test_aobv(self): result = pandas_ta.aobv(self.close, self.volume_) self.assertIsInstance(result, DataFrame) @@ -95,40 +103,48 @@ def test_kvo(self): self.assertEqual(result.name, "KVO_34_55_13") def test_mfi(self): - result = pandas_ta.mfi(self.high, self.low, self.close, self.volume_) + result = pandas_ta.mfi(self.high, self.low, self.close, self.volume_, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "MFI_14") try: expected = tal.MFI(self.high, self.low, self.close, self.volume_) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.mfi(self.high, self.low, self.close, self.volume_) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "MFI_14") + def test_nvi(self): result = pandas_ta.nvi(self.close, self.volume_) self.assertIsInstance(result, Series) self.assertEqual(result.name, "NVI_1") def test_obv(self): - result = pandas_ta.obv(self.close, self.volume_) + result = pandas_ta.obv(self.close, self.volume_, talib=False) self.assertIsInstance(result, Series) self.assertEqual(result.name, "OBV") try: expected = tal.OBV(self.close, self.volume_) pdt.assert_series_equal(result, expected, check_names=False) - except AssertionError as ae: + except AssertionError: try: corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: error_analysis(result, CORRELATION, ex) + result = pandas_ta.obv(self.close, self.volume_) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "OBV") + def test_pvi(self): result = pandas_ta.pvi(self.close, self.volume_) self.assertIsInstance(result, Series) diff --git a/tests/test_utils.py b/tests/test_utils.py index 06cc553a..b1f686f9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -149,12 +149,15 @@ def test_df_dates(self): result = self.utils.df_dates(self.data, ["1999-11-01", "2020-08-15", "2020-08-24", "2020-08-25", "2020-08-26", "2020-08-27"]) self.assertEqual(5, result.shape[0]) + @skip def test_df_month_to_date(self): result = self.utils.df_month_to_date(self.data) + @skip def test_df_quarter_to_date(self): result = self.utils.df_quarter_to_date(self.data) + @skip def test_df_year_to_date(self): result = self.utils.df_year_to_date(self.data) @@ -274,6 +277,18 @@ def test_symmetric_triangle(self): npt.assert_array_equal(self.utils.symmetric_triangle(n=5), array_5) npt.assert_array_equal(self.utils.symmetric_triangle(n=5, weighted=True), array_5w) + def test_tal_ma(self): + self.assertEqual(self.utils.tal_ma("sma"), 0) + self.assertEqual(self.utils.tal_ma("Sma"), 0) + self.assertEqual(self.utils.tal_ma("ema"), 1) + self.assertEqual(self.utils.tal_ma("wma"), 2) + self.assertEqual(self.utils.tal_ma("dema"), 3) + self.assertEqual(self.utils.tal_ma("tema"), 4) + self.assertEqual(self.utils.tal_ma("trima"), 5) + self.assertEqual(self.utils.tal_ma("kama"), 6) + self.assertEqual(self.utils.tal_ma("mama"), 7) + self.assertEqual(self.utils.tal_ma("t3"), 8) + def test_zero(self): self.assertEqual(self.utils.zero(-0.0000000000000001), 0) self.assertEqual(self.utils.zero(0), 0) diff --git a/tests/test_utils_metrics.py b/tests/test_utils_metrics.py index 0f1a418e..6d45a923 100644 --- a/tests/test_utils_metrics.py +++ b/tests/test_utils_metrics.py @@ -1,10 +1,9 @@ -from .config import sample_data -from .context import pandas_ta - from unittest import skip, TestCase from pandas import DataFrame +from .config import sample_data +from .context import pandas_ta class TestUtilityMetrics(TestCase): @@ -97,7 +96,6 @@ def test_optimal_leverage(self): def test_pure_profit_score(self): result = pandas_ta.pure_profit_score(self.close) - self.assertIsInstance(result, float) self.assertGreaterEqual(result, 0) def test_sharpe_ratio(self):