diff --git a/README.md b/README.md index 2ec0fb7..82346b2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Technical Analysis Library in Python 3.7 ![Example Chart](/images/TA_Chart.png) -Technical Analysis (TA) is an easy to use library that is built upon Python's Pandas library with more than 85 Indicators. These indicators are comminly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon Oscillator_ (*AROON*) and more. +Technical Analysis (TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators. These indicators are comminly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more. This version contains both the orignal code branch as well as a newly refactored branch with the option to use [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html) mode. All the indicators return a named Series or a DataFrame in uppercase underscore parameter format. For example, MACD(fast=12, slow=26, signal=9) will return a DataFrame with columns: ['MACD_12_26_9', 'MACDH_12_26_9', 'MACDS_12_26_9']. @@ -119,7 +119,7 @@ print(prehl2.columns) # "pre_HL2" endhl2 = df.ta.hl2(suffix="end") print(endhl2.columns) # "HL2_end" -bothhl2 = df.ta.hl2(suffix="end") +bothhl2 = df.ta.hl2(prefix="pre", suffix="end") print(bothhl2.columns) # "pre_HL2_end" ``` diff --git a/pandas_ta/core.py b/pandas_ta/core.py index d898ff6..f1e584a 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -951,6 +951,15 @@ class AnalysisIndicators(BasePandasObject): result = cross(series_a=a, series_b=b, above=above, asint=asint, offset=offset, **kwargs) return result + def cross_value(self, a=None, value=None, above=True, asint=True, offset=None, **kwargs): + if a is None and value is None: return self._df + else: + a = self._get_column(a, f"{a}") + result = cross_value(series_a=a, value=value, above=above, asint=asint, offset=offset, **kwargs) + self._add_prefix_suffix(result, **kwargs) + self._append(result, **kwargs) + return result + # Volatility Indicators diff --git a/pandas_ta/momentum/macd.py b/pandas_ta/momentum/macd.py index 2d0a834..81ae2e3 100644 --- a/pandas_ta/momentum/macd.py +++ b/pandas_ta/momentum/macd.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from pandas import DataFrame +from pandas import DataFrame, concat from ..overlap.ema import ema -from ..utils import get_offset, verify_series +from ..utils import get_offset, verify_series, generate_signal_indicators def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): """Indicator: Moving Average, Convergence/Divergence (MACD)""" @@ -51,7 +51,40 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): macddf.name = f"MACD_{fast}_{slow}_{signal}" macddf.category = 'momentum' - return macddf + signal_indicators = kwargs.pop('signal_indicators', False) + if signal_indicators: + signalsdf = concat( + [ + macddf, + generate_signal_indicators( + indicator=histogram, + xa=kwargs.pop('xa', 0), + xb=kwargs.pop('xb', None), + xserie=kwargs.pop('xserie', None), + xserie_a=kwargs.pop('xserie_a', None), + xserie_b=kwargs.pop('xserie_b', None), + cross_values=kwargs.pop('cross_values', True), + cross_series=kwargs.pop('cross_series', True), + offset=offset, + ), + generate_signal_indicators( + indicator=macd, + xa=kwargs.pop('xa', 0), + xb=kwargs.pop('xb', None), + xserie=kwargs.pop('xserie', None), + xserie_a=kwargs.pop('xserie_a', None), + xserie_b=kwargs.pop('xserie_b', None), + cross_values=kwargs.pop('cross_values', False), + cross_series=kwargs.pop('cross_series', True), + offset=offset, + ), + ], + axis=1 + ) + + return signalsdf + else: + return macddf diff --git a/pandas_ta/momentum/rsi.py b/pandas_ta/momentum/rsi.py index 95bc255..0009923 100644 --- a/pandas_ta/momentum/rsi.py +++ b/pandas_ta/momentum/rsi.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from ..utils import get_drift, get_offset, verify_series +from pandas import DataFrame, concat +from ..utils import get_drift, get_offset, verify_series, generate_signal_indicators def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Relative Strength Index (RSI)""" @@ -36,7 +37,31 @@ def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): rsi.name = f"RSI_{length}" rsi.category = 'momentum' - return rsi + signal_indicators = kwargs.pop('signal_indicators', False) + if signal_indicators: + signalsdf = concat( + [ + DataFrame( + {rsi.name: rsi} + ), + generate_signal_indicators( + indicator=rsi, + xa=kwargs.pop('xa', 80), + xb=kwargs.pop('xb', 20), + xserie=kwargs.pop('xserie', None), + xserie_a=kwargs.pop('xserie_a', None), + xserie_b=kwargs.pop('xserie_b', None), + cross_values=kwargs.pop('cross_values', False), + cross_series=kwargs.pop('cross_series', True), + offset=offset, + ), + ], + axis=1 + ) + + return signalsdf + else: + return rsi diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 8a66dd9..0ad112c 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -82,6 +82,10 @@ def combination(**kwargs): return numerator // denominator +def cross_value(series_a:pd.Series, value:float, above:bool =True, asint:bool =True, offset:int =None, **kwargs): + series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace('.','_')) + return cross(series_a, series_b, above, asint, offset, **kwargs) + def cross(series_a:pd.Series, series_b:pd.Series, above:bool =True, asint:bool =True, offset:int =None, **kwargs): series_a = verify_series(series_a) series_b = verify_series(series_b) @@ -109,7 +113,53 @@ def cross(series_a:pd.Series, series_b:pd.Series, above:bool =True, asint:bool = return cross +def signals(indicator, xa, xb, cross_values, xserie, xserie_a, xserie_b, cross_series, offset): + signalsdf = pd.DataFrame() + if xa is not None and isinstance(xa, (int, float)): + if cross_values: + crossed_above_start = cross_value(indicator, xa, above=True, offset=offset) + crossed_above_end = cross_value(indicator, xa, above=False, offset=offset) + signalsdf[crossed_above_start.name] = crossed_above_start + signalsdf[crossed_above_end.name] = crossed_above_end + else: + crossed_above = above_value(indicator, xa, offset=offset) + signalsdf[crossed_above.name] = crossed_above + if xb is not None and isinstance(xb, (int, float)): + if cross_values: + crossed_below_start = cross_value(indicator, xb, above=True, offset=offset) + crossed_below_end = cross_value(indicator, xb, above=False, offset=offset) + signalsdf[crossed_below_start.name] = crossed_below_start + signalsdf[crossed_below_end.name] = crossed_below_end + else: + crossed_below = below_value(indicator, xb, offset=offset) + signalsdf[crossed_below.name] = crossed_below + + # xseries is the default value for both xserie_a and xserie_b + if xserie_a is None: + xserie_a = xserie + if xserie_b is None: + xserie_b = xserie + + if xserie_a is not None and verify_series(xserie_a): + if cross_series: + cross_serie_above = cross(indicator, xserie_a, above=True, offset=offset) + else: + cross_serie_above = above(indicator, xserie_a, offset=offset) + + signalsdf[cross_serie_above.name] = cross_serie_above + + if xserie_b is not None and verify_series(xserie_b): + if cross_series: + cross_serie_below = cross(indicator, xserie_b, above=False, offset=offset) + else: + cross_serie_below = below(indicator, xserie_b, offset=offset) + + signalsdf[cross_serie_below.name] = cross_serie_below + + return signalsdf + + def df_error_analysis(dfA:pd.DataFrame, dfB:pd.DataFrame, **kwargs): """ """ col = kwargs.pop('col', None)