diff --git a/README.md b/README.md index 0e90554..a4746c6 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ help(pd.DataFrame().ta.log_return) # Technical Analysis Indicators (by Category) -## _Momentum_ (20) +## _Momentum_ (21) * _Awesome Oscillator_: **ao** * _Absolute Price Oscillator_: **apo** @@ -96,6 +96,7 @@ help(pd.DataFrame().ta.log_return) * _Percentage Price Oscillator_: **ppo** * _Rate of Change_: **roc** * _Relative Strength Index_: **rsi** +* _Relative Vigor Index_: **rvi** * _Slope_: **slope** * _Stochastic Oscillator_: **stoch** * _Trix_: **trix** diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 42a0e89..4f99926 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -33,6 +33,7 @@ from .momentum.mom import mom from .momentum.ppo import ppo from .momentum.roc import roc from .momentum.rsi import rsi +from .momentum.rvi import rvi from .momentum.slope import slope from .momentum.stoch import stoch from .momentum.trix import trix diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 9c953b7..01c9199 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -335,6 +335,16 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **kwargs) return result + def rvi(self, open_=None, high=None, low=None, close=None, length=None, swma_length=None, offset=None, **kwargs): + open_ = self._get_column(open_, 'open') + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + from .momentum.rvi import rvi + result = rvi(open_=open_, high=high, low=low, close=close, length=length, swma_length=swma_length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + def slope(self, close=None, length=None, offset=None, **kwargs): close = self._get_column(close, 'close') from .momentum.slope import slope diff --git a/pandas_ta/momentum/rvi.py b/pandas_ta/momentum/rvi.py new file mode 100644 index 0000000..d632956 --- /dev/null +++ b/pandas_ta/momentum/rvi.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +from ..overlap.swma import swma +from ..utils import get_offset, verify_series + +def rvi(open_, high, low, close, length=None, swma_length=None, offset=None, **kwargs): + """Indicator: RVI""" + # Validate Arguments + open_ = verify_series(open_) + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + length = int(length) if length and length > 0 else 14 + swma_length = int(swma_length) if swma_length and swma_length > 0 else 4 + offset = get_offset(offset) + + # Calculate Result + numerator = swma(close - open_, length=swma_length).rolling(length).sum() + denominator = swma(high - low, length=swma_length).rolling(length).sum() + + rvi = numerator / denominator + + # Offset + if offset != 0: + rvi = rvi.shift(offset) + + # Name & Category + rvi.name = f"RVI_{length}_{swma_length}" + rvi.category = 'momentum' + + return rvi + + + +rvi.__doc__ = \ +"""Relative Vigor Index (RVI) + +The Relative Vigor Index attempts to measure the strength of a trend relative to +its closing price to its trading range. It is based on the belief that it tends +to close higher than they open in uptrends or close lower than they open in +downtrends. + +Sources: + https://www.investopedia.com/terms/r/relative_vigor_index.asp + +Calculation: + Default Inputs: + length=14, swma_length=4 + SWMA = Symmetrically Weighted Moving Average + numerator = SUM(SWMA(close - open, swma_length), length) + denominator = SUM(SWMA(high - low, swma_length), length) + RVI = numerator / denominator + +Args: + open_ (pd.Series): Series of 'open's + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + length (int): It's period. Default: 14 + swma_length (int): It's period. Default: 4 + 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 diff --git a/setup.py b/setup.py index 94409bc..0ecc321 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "An easy to use Python 3 Pandas Extension with 80+ 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'], - version ="0.1.32b", + version ="0.1.33b", description =long_description, long_description =long_description, author ="Kevin Johnson", diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index ef72258..e17e5e9 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -207,6 +207,11 @@ class TestMomentum(TestCase): except Exception as ex: error_analysis(result, CORRELATION, ex) + def test_rvi(self): + result = pandas_ta.rvi(self.open, self.high, self.low, self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, 'RVI_14_4') + def test_slope(self): result = pandas_ta.slope(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_momentum_ext.py b/tests/test_indicator_momentum_ext.py index 2bea314..3aa183d 100644 --- a/tests/test_indicator_momentum_ext.py +++ b/tests/test_indicator_momentum_ext.py @@ -93,6 +93,11 @@ class TestMomentumExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'RSI_14') + def test_rvi_ext(self): + self.data.ta.rvi(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'RVI_14_4') + def test_slope_ext(self): self.data.ta.slope(append=True) self.assertIsInstance(self.data, DataFrame)