From ba95db09620b1f0a40a66883202b16a5a7a6ce49 Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 16:47:13 +0200 Subject: [PATCH 01/24] Add RSI event --- pandas_ta/__init__.py | 3 ++ pandas_ta/core.py | 18 ++++++++++ pandas_ta/event/__init__.py | 1 + pandas_ta/event/rsi_event.py | 64 ++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 pandas_ta/event/__init__.py create mode 100644 pandas_ta/event/rsi_event.py diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 8b55df2..bad9e20 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -130,6 +130,9 @@ from .volume.pvol import pvol from .volume.pvt import pvt from .volume.vp import vp +# Event +from .event.rsi_event import rsi_event + # DataFrame Extension from .core import * diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 7ddc8a6..154fa6b 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1105,4 +1105,22 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(close, 'close') volume = self._get_column(volume, 'volume') from pandas_ta.volume.vp import vp +<<<<<<< HEAD return vp(close=close, volume=volume, width=width, percent=percent, **kwargs) +======= + result = vp(close=close, volume=volume, width=width, percent=percent, **kwargs) + self._add_prefix_suffix(result, **kwargs) + self._append(result, **kwargs) + return result + + + + # Events indicators + def rsi_event(self, close=None, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + from pandas_ta.event.rsi_event import rsi_event + result = rsi_event(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) + self._add_prefix_suffix(result, **kwargs) + self._append(result, **kwargs) + return result +>>>>>>> cbba026... Add RSI event diff --git a/pandas_ta/event/__init__.py b/pandas_ta/event/__init__.py new file mode 100644 index 0000000..7c68785 --- /dev/null +++ b/pandas_ta/event/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/pandas_ta/event/rsi_event.py b/pandas_ta/event/rsi_event.py new file mode 100644 index 0000000..4debc29 --- /dev/null +++ b/pandas_ta/event/rsi_event.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from ..momentum.rsi import rsi +from ..utils import above_value, below_value + +def rsi_event(close, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): + """Indicator: Overbought or Oversold based on Relative Strength Index (RSI)""" + rsi_series = rsi(close, length=None, drift=None, offset=None, **kwargs) + above_val = int(above_val) if above_val and above_val > 0 else 80 + below_val = int(below_val) if below_val and below_val > 0 else 20 + + above = above_value(rsi_series, above_val, asint=True, **kwargs) + below = below_value(rsi_series, below_val, asint=True, **kwargs) + + # Name and Categorize it + # Not needed because above_value/below_value is already naming + # above.name = f"RSI_{length}_A_{above_val}" + # below.name = f"RSI_{length}_B_{below_val}" + above.category = below.category = 'event' + + # Prepare DataFrame to return + data = {above.name: above, below.name: below} + rsidf = DataFrame(data) + rsidf.name = f"RSI_event" + rsidf.category = 'event' + + return rsidf + + + +rsi.__doc__ = \ +"""Overbought or Oversold based on Relative Strength Index (RSI) + +The Relative Strength Index is popular momentum oscillator used to measure the +velocity as well as the magnitude of directional price movements. RSI reading +above 0.8 is considered overbought, while a reading below 0.2 is considered oversold. + +Sources: + https://www.tradingview.com/wiki/Relative_Strength_Index_(RSI) + +Calculation: + Default Inputs: + length=14, drift=1 + ABS = Absolute Value + EMA = Exponential Moving Average + positive = close if close.diff(drift) > 0 else 0 + negative = close if close.diff(drift) < 0 else 0 + pos_avg = EMA(positive, length) + neg_avg = ABS(EMA(negative, length)) + RSI = 100 * pos_avg / (pos_avg + neg_avg) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 1 + drift (int): The difference period. Default: 1 + 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. +""" \ No newline at end of file From 325cd50a09c013a7a3f8beabee8d2e64cefe7a3c Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 16:56:13 +0200 Subject: [PATCH 02/24] Resolve conflicts --- pandas_ta/core.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 154fa6b..7ff65e8 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1105,13 +1105,7 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(close, 'close') volume = self._get_column(volume, 'volume') from pandas_ta.volume.vp import vp -<<<<<<< HEAD return vp(close=close, volume=volume, width=width, percent=percent, **kwargs) -======= - result = vp(close=close, volume=volume, width=width, percent=percent, **kwargs) - self._add_prefix_suffix(result, **kwargs) - self._append(result, **kwargs) - return result @@ -1122,5 +1116,4 @@ class AnalysisIndicators(BasePandasObject): result = rsi_event(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) self._add_prefix_suffix(result, **kwargs) self._append(result, **kwargs) - return result ->>>>>>> cbba026... Add RSI event + return result \ No newline at end of file From 0f7baf8c670affc76e33250960e072441d4ead1b Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 16:50:25 +0200 Subject: [PATCH 03/24] Add setup.py with event module --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b56a0ba..cb78886 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ long_description = "An easy to use Python 3 Pandas Extension with 95+ Technical setup( name ="pandas_ta", - packages =['pandas_ta', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], + packages =['pandas_ta', 'pandas_ta.event', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], version ="0.1.47b", description =long_description, long_description =long_description, From 9b569c36c7b0f0b4159230635095afb2e604bf2e Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 17:00:13 +0200 Subject: [PATCH 04/24] Remove add_prefix_suffix function --- pandas_ta/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 7ff65e8..c599f90 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1114,6 +1114,5 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(close, 'close') from pandas_ta.event.rsi_event import rsi_event result = rsi_event(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) - self._add_prefix_suffix(result, **kwargs) self._append(result, **kwargs) return result \ No newline at end of file From dbaf3915753da247c65ec1b351aef58073ea550d Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Thu, 21 May 2020 11:28:00 -0700 Subject: [PATCH 05/24] DOC README update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28286bd..0f60d5f 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']. From da07dafd5aecb29cf87e53df791750f7d82214ae Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Thu, 21 May 2020 11:51:53 -0700 Subject: [PATCH 06/24] DOC README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f60d5f..744cb7d 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,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" ``` From a110046a0e44aaa666ce163b67c4d4ed1f9b9221 Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 23:09:01 +0200 Subject: [PATCH 07/24] Rename subpackage name by signals --- pandas_ta/__init__.py | 2 +- pandas_ta/core.py | 6 +++--- pandas_ta/{event => signals}/__init__.py | 0 pandas_ta/{event/rsi_event.py => signals/rsi_signals.py} | 0 setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename pandas_ta/{event => signals}/__init__.py (100%) rename pandas_ta/{event/rsi_event.py => signals/rsi_signals.py} (100%) diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index bad9e20..5cfefd6 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -131,7 +131,7 @@ from .volume.pvt import pvt from .volume.vp import vp # Event -from .event.rsi_event import rsi_event +from .signals.rsi_signals import rsi_signals # DataFrame Extension from .core import * diff --git a/pandas_ta/core.py b/pandas_ta/core.py index a822172..8feda41 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1225,10 +1225,10 @@ class AnalysisIndicators(BasePandasObject): # Events indicators - def rsi_event(self, close=None, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): + def rsi_signals(self, close=None, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): close = self._get_column(close, 'close') - from pandas_ta.event.rsi_event import rsi_event - result = rsi_event(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) + from pandas_ta.signals.rsi_signals import rsi_signals + result = rsi_signals(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) self._add_prefix_suffix(result, **kwargs) self._append(result, **kwargs) return result diff --git a/pandas_ta/event/__init__.py b/pandas_ta/signals/__init__.py similarity index 100% rename from pandas_ta/event/__init__.py rename to pandas_ta/signals/__init__.py diff --git a/pandas_ta/event/rsi_event.py b/pandas_ta/signals/rsi_signals.py similarity index 100% rename from pandas_ta/event/rsi_event.py rename to pandas_ta/signals/rsi_signals.py diff --git a/setup.py b/setup.py index 61027a1..03969aa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ long_description = "An easy to use Python 3 Pandas Extension with 100+ Technical setup( name ="pandas_ta", - packages =['pandas_ta', 'pandas_ta.event', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], + packages =['pandas_ta', 'pandas_ta.signals', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], version ="0.1.52b", description =long_description, long_description =long_description, From 6b3c419cec12d389d9f2af1a926c11c1cbcc4516 Mon Sep 17 00:00:00 2001 From: Lluis Date: Thu, 21 May 2020 23:12:15 +0200 Subject: [PATCH 08/24] Rename subpackage name by signals --- pandas_ta/signals/rsi_signals.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas_ta/signals/rsi_signals.py b/pandas_ta/signals/rsi_signals.py index 4debc29..ab67417 100644 --- a/pandas_ta/signals/rsi_signals.py +++ b/pandas_ta/signals/rsi_signals.py @@ -3,7 +3,7 @@ from pandas import DataFrame from ..momentum.rsi import rsi from ..utils import above_value, below_value -def rsi_event(close, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): +def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): """Indicator: Overbought or Oversold based on Relative Strength Index (RSI)""" rsi_series = rsi(close, length=None, drift=None, offset=None, **kwargs) above_val = int(above_val) if above_val and above_val > 0 else 80 @@ -16,13 +16,13 @@ def rsi_event(close, above_val=None, below_val=None, length=None, drift=None, of # Not needed because above_value/below_value is already naming # above.name = f"RSI_{length}_A_{above_val}" # below.name = f"RSI_{length}_B_{below_val}" - above.category = below.category = 'event' + above.category = below.category = 'signals' # Prepare DataFrame to return data = {above.name: above, below.name: below} rsidf = DataFrame(data) - rsidf.name = f"RSI_event" - rsidf.category = 'event' + rsidf.name = f"RSI_signals" + rsidf.category = 'signals' return rsidf From 91d63afe8346014848762d77574ca1176e4ee5be Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 00:03:58 +0200 Subject: [PATCH 09/24] Add README.md to follow steps --- pandas_ta/signals/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pandas_ta/signals/README.md diff --git a/pandas_ta/signals/README.md b/pandas_ta/signals/README.md new file mode 100644 index 0000000..a5edf52 --- /dev/null +++ b/pandas_ta/signals/README.md @@ -0,0 +1,19 @@ +# Signals subpackage + +## Decisions to take (@twopirllc I let this section decide to you) + +- [ ] Split subpackage between signals/events and periods/areas or to have only one subpackage +- [ ] Name of subpackage(s) +- [ ] For each signal can we have two kind of outputs? (crossing signals and periods/areas). If so, do we want to split the indicator in two functions, have a parameter to change from one to another or always output both of them? +- [ ] General naming of indicators created in this subpackage (for example mark 'XA'/'XB' for crossing above/below and 'A'/'B' for periods above/below or more splicit naming that describe bearish/bullish signals) + +## Signals to be added + +- [x] RSI (Overbought/oversold 80/20) +- [ ] Extended RSI (Overbought/oversold 80/20 and positive trend when $RSI_t$ > $RSI_{t-1}$) +- [ ] Crossing EMA(50) and EMA(200) (long term bearish/bullish signal) +- [ ] Crossing EMA(12) and EMA(26) (short term bearish/bullish signal) +- [ ] Crossing MACD(26,12, 9) with 0 line (bearish/bullish signal) +- [ ] Stochastic oscillator (Overbough/oversold 80/20) +- [ ] Crossing Bollinger bands with close price +- [ ] Average directional index (ADX) cross with 25 (strong trend/drift) From b382c6899e36610161fb3189c866d5d60aa1f778 Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 00:24:47 +0200 Subject: [PATCH 10/24] Add more signals to README.md --- pandas_ta/signals/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas_ta/signals/README.md b/pandas_ta/signals/README.md index a5edf52..78f630e 100644 --- a/pandas_ta/signals/README.md +++ b/pandas_ta/signals/README.md @@ -16,4 +16,6 @@ - [ ] Crossing MACD(26,12, 9) with 0 line (bearish/bullish signal) - [ ] Stochastic oscillator (Overbough/oversold 80/20) - [ ] Crossing Bollinger bands with close price -- [ ] Average directional index (ADX) cross with 25 (strong trend/drift) +- [ ] Average directional index (ADX) cross with 20 or 25 (trend/drift and 40 for strong trend) +- [ ] Extend ADX with DI+ and DI- (strong trend/drift confirming uptrend or downtrend) +- [ ] Aroon osicillator around 100 From 91618f1d1451013cfa395b1369fb80db5627ec16 Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 10:25:44 +0200 Subject: [PATCH 11/24] Add cross_value function --- pandas_ta/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 7503dfa..9035018 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -77,6 +77,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) From 42e45b5e1ff7c591db7fb96bd12a0150da396e08 Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 10:27:43 +0200 Subject: [PATCH 12/24] Add cross_value function --- pandas_ta/core.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 8feda41..aa70648 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -988,6 +988,15 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **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(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 From a1d83358e6d2b853c9b48f6aa8b72fcf7f7ccf2b Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 10:32:45 +0200 Subject: [PATCH 13/24] Fix cross_value function on core.py --- pandas_ta/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index aa70648..980737f 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -992,7 +992,7 @@ class AnalysisIndicators(BasePandasObject): if a is None and value is None: return self._df else: a = self._get_column(a, f"{a}") - result = cross(series_a=a, value=value, above=above, asint=asint, offset=offset, **kwargs) + 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 From e8c9509d4d2da0b36d3b63e745926d667810bebe Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 10:41:41 +0200 Subject: [PATCH 14/24] Add crossing signals and change naming convention --- pandas_ta/signals/rsi_signals.py | 43 ++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/pandas_ta/signals/rsi_signals.py b/pandas_ta/signals/rsi_signals.py index ab67417..be4c51d 100644 --- a/pandas_ta/signals/rsi_signals.py +++ b/pandas_ta/signals/rsi_signals.py @@ -1,25 +1,54 @@ # -*- coding: utf-8 -*- from pandas import DataFrame from ..momentum.rsi import rsi -from ..utils import above_value, below_value +from ..utils import above_value, below_value, cross_value -def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): - """Indicator: Overbought or Oversold based on Relative Strength Index (RSI)""" +def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, offset=None, crossing=False, **kwargs): + """Indicator: Signals based on Relative Strength Index (RSI)""" rsi_series = rsi(close, length=None, drift=None, offset=None, **kwargs) above_val = int(above_val) if above_val and above_val > 0 else 80 below_val = int(below_val) if below_val and below_val > 0 else 20 + # Mark the all the ticks when the security is overbought/oversold above = above_value(rsi_series, above_val, asint=True, **kwargs) below = below_value(rsi_series, below_val, asint=True, **kwargs) + if crossing: + # Mark only the crossing ticks when the security starts to be overbought/oversold + cross_start_above = cross_value(rsi_series, above_val, above=True, asint=True, **kwargs) + cross_start_below = cross_value(rsi_series, below_val, above=False, asint=True, **kwargs) + + # Mark only the crossing ticks when the security ends to be overbought/oversold + cross_end_above = cross_value(rsi_series, above_val, above=False, asint=True, **kwargs) + cross_end_below = cross_value(rsi_series, below_val, above=True, asint=True, **kwargs) + # Name and Categorize it # Not needed because above_value/below_value is already naming - # above.name = f"RSI_{length}_A_{above_val}" - # below.name = f"RSI_{length}_B_{below_val}" + above.name = f"RSI_{length}_OB_{above_val}" + below.name = f"RSI_{length}_OS_{below_val}" above.category = below.category = 'signals' + if crossing: + cross_start_above.name = f"RSI_{length}_XS_OB_{above_val}" + cross_start_below.name = f"RSI_{length}_XS_OS_{below_val}" + cross_end_above.name = f"RSI_{length}_XE_OB_{above_val}" + cross_end_below.name = f"RSI_{length}_XE_OS_{below_val}" + cross_start_above.category = cross_start_below.category = cross_end_above.category = cross_end_below.category = 'signals' # Prepare DataFrame to return - data = {above.name: above, below.name: below} + data = { + above.name: above, + below.name: below, + } + if crossing: + data.update( + { + cross_start_above.name: cross_start_above, + cross_start_below.name: cross_start_below, + cross_end_above.name: cross_end_above, + cross_end_below.name: cross_end_below + } + ) + rsidf = DataFrame(data) rsidf.name = f"RSI_signals" rsidf.category = 'signals' @@ -29,7 +58,7 @@ def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, rsi.__doc__ = \ -"""Overbought or Oversold based on Relative Strength Index (RSI) +"""Signals based on Relative Strength Index (RSI) The Relative Strength Index is popular momentum oscillator used to measure the velocity as well as the magnitude of directional price movements. RSI reading From 8ee43b653b8429042f7d1aad52433f9f46f61386 Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 10:47:06 +0200 Subject: [PATCH 15/24] Fix RSI name to get length correctly --- pandas_ta/signals/rsi_signals.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas_ta/signals/rsi_signals.py b/pandas_ta/signals/rsi_signals.py index be4c51d..6db5ebc 100644 --- a/pandas_ta/signals/rsi_signals.py +++ b/pandas_ta/signals/rsi_signals.py @@ -24,14 +24,14 @@ def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, # Name and Categorize it # Not needed because above_value/below_value is already naming - above.name = f"RSI_{length}_OB_{above_val}" - below.name = f"RSI_{length}_OS_{below_val}" + above.name = f"{rsi_series.name}_OB_{above_val}" + below.name = f"{rsi_series.name}_OS_{below_val}" above.category = below.category = 'signals' if crossing: - cross_start_above.name = f"RSI_{length}_XS_OB_{above_val}" - cross_start_below.name = f"RSI_{length}_XS_OS_{below_val}" - cross_end_above.name = f"RSI_{length}_XE_OB_{above_val}" - cross_end_below.name = f"RSI_{length}_XE_OS_{below_val}" + cross_start_above.name = f"{rsi_series.name}_XS_OB_{above_val}" + cross_start_below.name = f"{rsi_series.name}_XS_OS_{below_val}" + cross_end_above.name = f"{rsi_series.name}_XE_OB_{above_val}" + cross_end_below.name = f"{rsi_series.name}_XE_OS_{below_val}" cross_start_above.category = cross_start_below.category = cross_end_above.category = cross_end_below.category = 'signals' # Prepare DataFrame to return @@ -50,7 +50,7 @@ def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, ) rsidf = DataFrame(data) - rsidf.name = f"RSI_signals" + rsidf.name = f"{rsi_series.name}_signals" rsidf.category = 'signals' return rsidf From 9c60eaff7a5b5d6f551539d5dcddec8e402aca90 Mon Sep 17 00:00:00 2001 From: Lluis Date: Fri, 22 May 2020 14:29:43 +0200 Subject: [PATCH 16/24] Add macd signals --- pandas_ta/__init__.py | 3 +- pandas_ta/core.py | 10 ++++- pandas_ta/signals/README.md | 4 +- pandas_ta/signals/macd_signals.py | 75 +++++++++++++++++++++++++++++++ pandas_ta/signals/rsi_signals.py | 2 +- 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 pandas_ta/signals/macd_signals.py diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 5cfefd6..2b5bc4a 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -130,8 +130,9 @@ from .volume.pvol import pvol from .volume.pvt import pvt from .volume.vp import vp -# Event +# Signals from .signals.rsi_signals import rsi_signals +from .signals.macd_signals import macd_signals # DataFrame Extension from .core import * diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 980737f..6defdf7 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1233,7 +1233,7 @@ class AnalysisIndicators(BasePandasObject): - # Events indicators + # Signals indicators def rsi_signals(self, close=None, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): close = self._get_column(close, 'close') from pandas_ta.signals.rsi_signals import rsi_signals @@ -1241,3 +1241,11 @@ class AnalysisIndicators(BasePandasObject): self._add_prefix_suffix(result, **kwargs) self._append(result, **kwargs) return result + + def macd_signals(self, close=None, fast=None, slow=None, signal=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + from pandas_ta.signals.macd_signals import macd_signals + result = macd_signals(close=close, fast=fast, slow=slow, signal=signal, offset=offset, **kwargs) + self._add_prefix_suffix(result, **kwargs) + self._append(result, **kwargs) + return result diff --git a/pandas_ta/signals/README.md b/pandas_ta/signals/README.md index 78f630e..78bed0c 100644 --- a/pandas_ta/signals/README.md +++ b/pandas_ta/signals/README.md @@ -13,9 +13,9 @@ - [ ] Extended RSI (Overbought/oversold 80/20 and positive trend when $RSI_t$ > $RSI_{t-1}$) - [ ] Crossing EMA(50) and EMA(200) (long term bearish/bullish signal) - [ ] Crossing EMA(12) and EMA(26) (short term bearish/bullish signal) -- [ ] Crossing MACD(26,12, 9) with 0 line (bearish/bullish signal) +- [x] Crossing MACD(26,12, 9) with 0 line (bearish/bullish signal) - [ ] Stochastic oscillator (Overbough/oversold 80/20) - [ ] Crossing Bollinger bands with close price - [ ] Average directional index (ADX) cross with 20 or 25 (trend/drift and 40 for strong trend) - [ ] Extend ADX with DI+ and DI- (strong trend/drift confirming uptrend or downtrend) -- [ ] Aroon osicillator around 100 +- [ ] Aroon osicillator around 100 diff --git a/pandas_ta/signals/macd_signals.py b/pandas_ta/signals/macd_signals.py new file mode 100644 index 0000000..6c51341 --- /dev/null +++ b/pandas_ta/signals/macd_signals.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from ..momentum.macd import macd +from ..utils import above_value, below_value, cross_value + +def macd_signals(close, fast=None, slow=None, signal=None, offset=None, **kwargs): + """Indicator: Signals based on Moving Average, Convergence/Divergence (MACD)""" + macd_df = macd(close, fast=fast, slow=slow, signal=signal, offset=offset, **kwargs) + histogram_col = macd_df.columns.values[macd_df.columns.str.startswith('MACDH')][0] + histogram_serie = macd_df[histogram_col] + macd_col = macd_df.columns.values[macd_df.columns.str.startswith('MACD_')][0] + macd_serie = macd_df[macd_col] + + signals_above = cross_value(histogram_serie, 0, above=True, asint=True, **kwargs) + signals_below = cross_value(histogram_serie, 0, above=False, asint=True, **kwargs) + bull = above_value(histogram_serie, 0, asint=True, **kwargs) + bear = below_value(histogram_serie, 0, asint=True, **kwargs) + + # Name and Categorize it + # Not needed because above_value/below_value is already naming + signals_above.name = f"{macd_serie.name}_X_BULL" + signals_below.name = f"{macd_serie.name}_X_BEAR" + bull.name = f"{macd_serie.name}_BULL" + bear.name = f"{macd_serie.name}_BEAR" + signals_above.category = signals_below.category = bull.category = bear.category = 'signals' + + # Prepare DataFrame to return + data = { + signals_above.name: signals_above, + signals_below.name: signals_below, + bull.name: bull, + bear.name: bear + } + + macddf = DataFrame(data) + macddf.name = f"{macd_serie.name}_signals" + macddf.category = 'signals' + + return macddf + + + +macd.__doc__ = \ +"""Moving Average Convergence Divergence (MACD) + +The MACD is a popular indicator to that is used to identify a security's trend. +While APO and MACD are the same calculation, MACD also returns two more series +called Signal and Histogram. The Signal is an EMA of MACD and the Histogram is +the difference of MACD and Signal. + +Sources: + https://www.tradingview.com/wiki/MACD_(Moving_Average_Convergence/Divergence) + +Calculation: + Default Inputs: + fast=12, slow=26, signal=9 + EMA = Exponential Moving Average + MACD = EMA(close, fast) - EMA(close, slow) + 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 + 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.DataFrame: macd, histogram, signal columns. +""" \ No newline at end of file diff --git a/pandas_ta/signals/rsi_signals.py b/pandas_ta/signals/rsi_signals.py index 6db5ebc..d8af594 100644 --- a/pandas_ta/signals/rsi_signals.py +++ b/pandas_ta/signals/rsi_signals.py @@ -5,7 +5,7 @@ from ..utils import above_value, below_value, cross_value def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, offset=None, crossing=False, **kwargs): """Indicator: Signals based on Relative Strength Index (RSI)""" - rsi_series = rsi(close, length=None, drift=None, offset=None, **kwargs) + rsi_series = rsi(close, length=length, drift=drift, offset=offset, **kwargs) above_val = int(above_val) if above_val and above_val > 0 else 80 below_val = int(below_val) if below_val and below_val > 0 else 20 From 083bc2cf5feba954e413809d9c7dfa144d1159ec Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:06:25 +0200 Subject: [PATCH 17/24] Add generate_signal_indicators function --- pandas_ta/utils.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 9035018..485abf7 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -108,6 +108,46 @@ def cross(series_a:pd.Series, series_b:pd.Series, above:bool =True, asint:bool = return cross +def generate_signal_indicators(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 = cross_value(indicator, xa, above=True, offset=offset) + 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 = cross_value(indicator, xb, above=True, offset=offset) + 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) From 3baaf52c5df9a4509710f9c4e6e1d719641f6619 Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:13:37 +0200 Subject: [PATCH 18/24] Fix cross_value on generate_signal_indicators function --- pandas_ta/utils.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 485abf7..6125791 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -112,17 +112,23 @@ def generate_signal_indicators(indicator, xa, xb, cross_values, xserie, xserie_a signalsdf = pd.DataFrame() if xa is not None and isinstance(xa, (int, float)): if cross_values: - crossed_above = cross_value(indicator, xa, above=True, offset=offset) + 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 + signalsdf[crossed_above.name] = crossed_above if xb is not None and isinstance(xb, (int, float)): if cross_values: - crossed_below = cross_value(indicator, xb, above=True, offset=offset) + 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 + signalsdf[crossed_below.name] = crossed_below # xseries is the default value for both xserie_a and xserie_b if xserie_a is None: From 4d5beb1e6077ecf87dd174ad6a5199cc2150eda9 Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:14:52 +0200 Subject: [PATCH 19/24] Add signal on rsi --- pandas_ta/momentum/rsi.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) 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 From 71c84405580cb64f87070ccc52f614f7b61ba5cd Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:31:06 +0200 Subject: [PATCH 20/24] Add signal on macd --- pandas_ta/momentum/macd.py | 39 +++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) 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 From 8e4180fd970cc11457f7e7a661637662db86e7e4 Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:36:47 +0200 Subject: [PATCH 21/24] Remove signals --- pandas_ta/core.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 1fa2f33..4c5fb73 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1238,22 +1238,3 @@ class AnalysisIndicators(BasePandasObject): self._add_prefix_suffix(result, **kwargs) self._append(result, **kwargs) return result - - - - # Signals indicators - def rsi_signals(self, close=None, above_val=None, below_val=None, length=None, drift=None, offset=None, **kwargs): - close = self._get_column(close, 'close') - from pandas_ta.signals.rsi_signals import rsi_signals - result = rsi_signals(close=close, above_val=above_val, below_val=below_val, length=length, drift=drift, offset=offset, **kwargs) - self._add_prefix_suffix(result, **kwargs) - self._append(result, **kwargs) - return result - - def macd_signals(self, close=None, fast=None, slow=None, signal=None, offset=None, **kwargs): - close = self._get_column(close, 'close') - from pandas_ta.signals.macd_signals import macd_signals - result = macd_signals(close=close, fast=fast, slow=slow, signal=signal, offset=offset, **kwargs) - self._add_prefix_suffix(result, **kwargs) - self._append(result, **kwargs) - return result From 3bee712e347f3806b552db639467156298e8298d Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:38:48 +0200 Subject: [PATCH 22/24] Remove extra line --- pandas_ta/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index e0a43ef..42fc3c5 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -18,5 +18,4 @@ except DistributionNotFound: else: __version__ = _dist.version - from pandas_ta.core import * From 83d004140d6081a9354561e762c17bb44b459da4 Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:39:14 +0200 Subject: [PATCH 23/24] Remove extra line --- pandas_ta/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 42fc3c5..19aa92e 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -18,4 +18,4 @@ except DistributionNotFound: else: __version__ = _dist.version -from pandas_ta.core import * +from pandas_ta.core import * \ No newline at end of file From 39a1070bb4475a5a943dd3ff54e6829692f8471b Mon Sep 17 00:00:00 2001 From: Lluis Date: Tue, 26 May 2020 16:40:10 +0200 Subject: [PATCH 24/24] Remove unnecessary files --- pandas_ta/signals/README.md | 21 ------- pandas_ta/signals/__init__.py | 1 - pandas_ta/signals/macd_signals.py | 75 ------------------------- pandas_ta/signals/rsi_signals.py | 93 ------------------------------- 4 files changed, 190 deletions(-) delete mode 100644 pandas_ta/signals/README.md delete mode 100644 pandas_ta/signals/__init__.py delete mode 100644 pandas_ta/signals/macd_signals.py delete mode 100644 pandas_ta/signals/rsi_signals.py diff --git a/pandas_ta/signals/README.md b/pandas_ta/signals/README.md deleted file mode 100644 index 78bed0c..0000000 --- a/pandas_ta/signals/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Signals subpackage - -## Decisions to take (@twopirllc I let this section decide to you) - -- [ ] Split subpackage between signals/events and periods/areas or to have only one subpackage -- [ ] Name of subpackage(s) -- [ ] For each signal can we have two kind of outputs? (crossing signals and periods/areas). If so, do we want to split the indicator in two functions, have a parameter to change from one to another or always output both of them? -- [ ] General naming of indicators created in this subpackage (for example mark 'XA'/'XB' for crossing above/below and 'A'/'B' for periods above/below or more splicit naming that describe bearish/bullish signals) - -## Signals to be added - -- [x] RSI (Overbought/oversold 80/20) -- [ ] Extended RSI (Overbought/oversold 80/20 and positive trend when $RSI_t$ > $RSI_{t-1}$) -- [ ] Crossing EMA(50) and EMA(200) (long term bearish/bullish signal) -- [ ] Crossing EMA(12) and EMA(26) (short term bearish/bullish signal) -- [x] Crossing MACD(26,12, 9) with 0 line (bearish/bullish signal) -- [ ] Stochastic oscillator (Overbough/oversold 80/20) -- [ ] Crossing Bollinger bands with close price -- [ ] Average directional index (ADX) cross with 20 or 25 (trend/drift and 40 for strong trend) -- [ ] Extend ADX with DI+ and DI- (strong trend/drift confirming uptrend or downtrend) -- [ ] Aroon osicillator around 100 diff --git a/pandas_ta/signals/__init__.py b/pandas_ta/signals/__init__.py deleted file mode 100644 index 7c68785..0000000 --- a/pandas_ta/signals/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/pandas_ta/signals/macd_signals.py b/pandas_ta/signals/macd_signals.py deleted file mode 100644 index 6c51341..0000000 --- a/pandas_ta/signals/macd_signals.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -from pandas import DataFrame -from ..momentum.macd import macd -from ..utils import above_value, below_value, cross_value - -def macd_signals(close, fast=None, slow=None, signal=None, offset=None, **kwargs): - """Indicator: Signals based on Moving Average, Convergence/Divergence (MACD)""" - macd_df = macd(close, fast=fast, slow=slow, signal=signal, offset=offset, **kwargs) - histogram_col = macd_df.columns.values[macd_df.columns.str.startswith('MACDH')][0] - histogram_serie = macd_df[histogram_col] - macd_col = macd_df.columns.values[macd_df.columns.str.startswith('MACD_')][0] - macd_serie = macd_df[macd_col] - - signals_above = cross_value(histogram_serie, 0, above=True, asint=True, **kwargs) - signals_below = cross_value(histogram_serie, 0, above=False, asint=True, **kwargs) - bull = above_value(histogram_serie, 0, asint=True, **kwargs) - bear = below_value(histogram_serie, 0, asint=True, **kwargs) - - # Name and Categorize it - # Not needed because above_value/below_value is already naming - signals_above.name = f"{macd_serie.name}_X_BULL" - signals_below.name = f"{macd_serie.name}_X_BEAR" - bull.name = f"{macd_serie.name}_BULL" - bear.name = f"{macd_serie.name}_BEAR" - signals_above.category = signals_below.category = bull.category = bear.category = 'signals' - - # Prepare DataFrame to return - data = { - signals_above.name: signals_above, - signals_below.name: signals_below, - bull.name: bull, - bear.name: bear - } - - macddf = DataFrame(data) - macddf.name = f"{macd_serie.name}_signals" - macddf.category = 'signals' - - return macddf - - - -macd.__doc__ = \ -"""Moving Average Convergence Divergence (MACD) - -The MACD is a popular indicator to that is used to identify a security's trend. -While APO and MACD are the same calculation, MACD also returns two more series -called Signal and Histogram. The Signal is an EMA of MACD and the Histogram is -the difference of MACD and Signal. - -Sources: - https://www.tradingview.com/wiki/MACD_(Moving_Average_Convergence/Divergence) - -Calculation: - Default Inputs: - fast=12, slow=26, signal=9 - EMA = Exponential Moving Average - MACD = EMA(close, fast) - EMA(close, slow) - 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 - 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.DataFrame: macd, histogram, signal columns. -""" \ No newline at end of file diff --git a/pandas_ta/signals/rsi_signals.py b/pandas_ta/signals/rsi_signals.py deleted file mode 100644 index d8af594..0000000 --- a/pandas_ta/signals/rsi_signals.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -from pandas import DataFrame -from ..momentum.rsi import rsi -from ..utils import above_value, below_value, cross_value - -def rsi_signals(close, above_val=None, below_val=None, length=None, drift=None, offset=None, crossing=False, **kwargs): - """Indicator: Signals based on Relative Strength Index (RSI)""" - rsi_series = rsi(close, length=length, drift=drift, offset=offset, **kwargs) - above_val = int(above_val) if above_val and above_val > 0 else 80 - below_val = int(below_val) if below_val and below_val > 0 else 20 - - # Mark the all the ticks when the security is overbought/oversold - above = above_value(rsi_series, above_val, asint=True, **kwargs) - below = below_value(rsi_series, below_val, asint=True, **kwargs) - - if crossing: - # Mark only the crossing ticks when the security starts to be overbought/oversold - cross_start_above = cross_value(rsi_series, above_val, above=True, asint=True, **kwargs) - cross_start_below = cross_value(rsi_series, below_val, above=False, asint=True, **kwargs) - - # Mark only the crossing ticks when the security ends to be overbought/oversold - cross_end_above = cross_value(rsi_series, above_val, above=False, asint=True, **kwargs) - cross_end_below = cross_value(rsi_series, below_val, above=True, asint=True, **kwargs) - - # Name and Categorize it - # Not needed because above_value/below_value is already naming - above.name = f"{rsi_series.name}_OB_{above_val}" - below.name = f"{rsi_series.name}_OS_{below_val}" - above.category = below.category = 'signals' - if crossing: - cross_start_above.name = f"{rsi_series.name}_XS_OB_{above_val}" - cross_start_below.name = f"{rsi_series.name}_XS_OS_{below_val}" - cross_end_above.name = f"{rsi_series.name}_XE_OB_{above_val}" - cross_end_below.name = f"{rsi_series.name}_XE_OS_{below_val}" - cross_start_above.category = cross_start_below.category = cross_end_above.category = cross_end_below.category = 'signals' - - # Prepare DataFrame to return - data = { - above.name: above, - below.name: below, - } - if crossing: - data.update( - { - cross_start_above.name: cross_start_above, - cross_start_below.name: cross_start_below, - cross_end_above.name: cross_end_above, - cross_end_below.name: cross_end_below - } - ) - - rsidf = DataFrame(data) - rsidf.name = f"{rsi_series.name}_signals" - rsidf.category = 'signals' - - return rsidf - - - -rsi.__doc__ = \ -"""Signals based on Relative Strength Index (RSI) - -The Relative Strength Index is popular momentum oscillator used to measure the -velocity as well as the magnitude of directional price movements. RSI reading -above 0.8 is considered overbought, while a reading below 0.2 is considered oversold. - -Sources: - https://www.tradingview.com/wiki/Relative_Strength_Index_(RSI) - -Calculation: - Default Inputs: - length=14, drift=1 - ABS = Absolute Value - EMA = Exponential Moving Average - positive = close if close.diff(drift) > 0 else 0 - negative = close if close.diff(drift) < 0 else 0 - pos_avg = EMA(positive, length) - neg_avg = ABS(EMA(negative, length)) - RSI = 100 * pos_avg / (pos_avg + neg_avg) - -Args: - close (pd.Series): Series of 'close's - length (int): It's period. Default: 1 - drift (int): The difference period. Default: 1 - 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. -""" \ No newline at end of file