BUG dpo calc fix ENH pvo added MAINT minor

This commit is contained in:
Kevin Johnson
2020-06-02 15:32:53 -07:00
parent 5997536da6
commit bc52d67b99
12 changed files with 165 additions and 46 deletions
+4 -2
View File
@@ -6,7 +6,7 @@
# __Technical Analysis Library in Python 3.7__
![Example Chart](/images/TA_Chart.png)
__Pandas Technical Analysis__ (Pandas TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators. These indicators are 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: _Simple Moving Average_ (*SMA*) _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more.
__Pandas Technical Analysis__ (Pandas TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators. These indicators are commonly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Simple Moving Average_ (*SMA*) _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more.
This version contains both the orignal code branch as well as a newly refactored branch with the option to use [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html) mode.
All the indicators return a named Series or a DataFrame in uppercase underscore parameter format. For example, MACD(fast=12, slow=26, signal=9) will return a DataFrame with columns: ['MACD_12_26_9', 'MACDH_12_26_9', 'MACDS_12_26_9'].
@@ -38,6 +38,7 @@ All the indicators return a named Series or a DataFrame in uppercase underscore
Parabolic Stop and Reverse (psar)
Price Distance (pdist)
Psycholigical Line (psl)
Price Volume Oscillator (pvo)
Supertrend (supertrend)
Weighted Closing Price (wcp)
### __Added utilities:__
@@ -188,7 +189,7 @@ df.ta.adjusted = None
* _Heikin-Ashi_: **ha**
## _Momentum_ (25)
## _Momentum_ (26)
* _Awesome Oscillator_: **ao**
* _Absolute Price Oscillator_: **apo**
@@ -206,6 +207,7 @@ df.ta.adjusted = None
* _Momentum_: **mom**
* _Percentage Price Oscillator_: **ppo**
* _Psychological Line_: **psl**
* _Percentage Volume Oscillator_: **pvo**
* _Rate of Change_: **roc**
* _Relative Strength Index_: **rsi**
* _Relative Vigor Index_: **rvi**
+10 -3
View File
@@ -15,7 +15,7 @@ from pandas_ta.volatility import *
from pandas_ta.volume import *
from pandas_ta.utils import *
version = ".".join(("0", "1", "65b"))
version = ".".join(("0", "1", "67b"))
def finalize(method):
@wraps(method)
@@ -491,10 +491,10 @@ class AnalysisIndicators(BasePandasObject):
return result
@finalize
def ppo(self, close=None, fast=None, slow=None, percentage=True, offset=None, **kwargs):
def ppo(self, close=None, fast=None, slow=None, scalar=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = ppo(close=close, fast=fast, slow=slow, percentage=percentage, offset=offset, **kwargs)
result = ppo(close=close, fast=fast, slow=slow, scalar=scalar, offset=offset, **kwargs)
return result
@finalize
@@ -506,6 +506,13 @@ class AnalysisIndicators(BasePandasObject):
result = psl(close=close, open_=open_, length=length, scalar=scalar, drift=drift, offset=offset, **kwargs)
return result
@finalize
def pvo(self, volume=None, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs):
volume = self._get_column(volume, 'volume')
result = pvo(volume=volume, fast=fast, slow=slow, signal=signal, scalar=scalar, offset=offset, **kwargs)
return result
@finalize
def roc(self, close=None, length=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
+1
View File
@@ -15,6 +15,7 @@ from .macd import macd
from .mom import mom
from .ppo import ppo
from .psl import psl
from .pvo import pvo
from .roc import roc
from .rsi import rsi
from .rvi import rvi
+2 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from ..overlap.sma import sma
from ..utils import get_offset, verify_series
from pandas_ta.overlap import sma
from pandas_ta.utils import get_offset, verify_series
def apo(close, fast=None, slow=None, offset=None, **kwargs):
"""Indicator: Absolute Price Oscillator (APO)"""
+10 -9
View File
@@ -40,22 +40,23 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
signalma.fillna(method=kwargs['fill_method'], inplace=True)
# Name and Categorize it
macd.name = f"MACD_{fast}_{slow}_{signal}"
histogram.name = f"MACDH_{fast}_{slow}_{signal}"
signalma.name = f"MACDS_{fast}_{slow}_{signal}"
macd.category = histogram.category = signalma.category = 'momentum'
_props = f"_{fast}_{slow}_{signal}"
macd.name = f"MACD{_props}"
histogram.name = f"MACDh{_props}"
signalma.name = f"MACDs{_props}"
macd.category = histogram.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {macd.name: macd, histogram.name: histogram, signalma.name: signalma}
macddf = DataFrame(data)
macddf.name = f"MACD_{fast}_{slow}_{signal}"
macddf.category = 'momentum'
df = DataFrame(data)
df.name = f"MACD{_props}"
df.category = macd.category
signal_indicators = kwargs.pop('signal_indicators', False)
if signal_indicators:
signalsdf = concat(
[
macddf,
df,
signals(
indicator=histogram,
xa=kwargs.pop('xa', 0),
@@ -84,7 +85,7 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
return signalsdf
else:
return macddf
return df
+18 -15
View File
@@ -1,33 +1,35 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame
from ..overlap.ema import ema
from ..utils import get_offset, verify_series
from pandas_ta.overlap import ema, sma
from pandas_ta.utils import get_offset, verify_series
def ppo(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
def ppo(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs):
"""Indicator: Percentage Price Oscillator (PPO)"""
# Validate Arguments
close = verify_series(close)
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
signal = int(signal) if signal and signal > 0 else 9
scalar = float(scalar) if scalar else 100
if slow < fast:
fast, slow = slow, fast
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else fast
offset = get_offset(offset)
# Calculate Result
fastma = close.rolling(fast, min_periods=min_periods).mean()
slowma = close.rolling(slow, min_periods=min_periods).mean()
fastma = sma(close, length=fast)
slowma = sma(close, length=slow)
ppo = scalar * (fastma - slowma)
ppo /= slowma
ppo = 100 * (fastma - slowma) / slowma
signalma = ema(close=ppo, length=signal, **kwargs)
signalma = ema(ppo, length=signal)
histogram = ppo - signalma
# Offset
if offset != 0:
ppo = ppo.shift(offset)
signalma = signalma.shift(offset)
histogram = histogram.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
if 'fillna' in kwargs:
@@ -42,17 +44,17 @@ def ppo(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
# Name and Categorize it
_props = f"_{fast}_{slow}_{signal}"
ppo.name = f"PPO{_props}"
histogram.name = f"PPOH{_props}"
signalma.name = f"PPOS{_props}"
ppo.category = histogram.category = signalma.category = 'momentum'
histogram.name = f"PPOh{_props}"
signalma.name = f"PPOs{_props}"
ppo.category = histogram.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {ppo.name: ppo, histogram.name: histogram, signalma.name: signalma}
ppodf = DataFrame(data)
ppodf.name = f"PPO{_props}"
ppodf.category = 'momentum'
df = DataFrame(data)
df.name = f"PPO{_props}"
df.category = ppo.category
return ppodf
return df
@@ -80,6 +82,7 @@ Args:
fast(int): The short period. Default: 12
slow(int): The long period. Default: 26
signal(int): The signal period. Default: 9
scalar (float): How much to magnify. Default: 100
offset(int): How many periods to offset the result. Default: 0
Kwargs:
+91
View File
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series
def pvo(volume, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs):
"""Indicator: Percentage Volume Oscillator (PVO)"""
# Validate Arguments
volume = verify_series(volume)
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
signal = int(signal) if signal and signal > 0 else 9
scalar = float(scalar) if scalar else 100
if slow < fast:
fast, slow = slow, fast
offset = get_offset(offset)
# Calculate Result
fastma = ema(volume, length=fast)
slowma = ema(volume, length=slow)
pvo = scalar * (fastma - slowma)
pvo /= slowma
signalma = ema(pvo, length=signal)
histogram = pvo - signalma
# Offset
if offset != 0:
pvo = pvo.shift(offset)
histogram = histogram.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
if 'fillna' in kwargs:
pvo.fillna(kwargs['fillna'], inplace=True)
histogram.fillna(kwargs['fillna'], inplace=True)
signalma.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
pvo.fillna(method=kwargs['fill_method'], inplace=True)
histogram.fillna(method=kwargs['fill_method'], inplace=True)
signalma.fillna(method=kwargs['fill_method'], inplace=True)
# Name and Categorize it
_props = f"_{fast}_{slow}_{signal}"
pvo.name = f"PVO{_props}"
histogram.name = f"PVOh{_props}"
signalma.name = f"PVOs{_props}"
pvo.category = histogram.category = signalma.category = "momentum"
#
data = {pvo.name: pvo, histogram.name: histogram, signalma.name: signalma}
df = DataFrame(data)
df.name = pvo.name
df.category = pvo.category
return df
pvo.__doc__ = \
"""Percentage Volume Oscillator (PVO)
Percentage Volume Oscillator is a Momentum Oscillator for Volume.
Sources:
https://www.fmlabs.com/reference/default.htm?url=PVO.htm
Calculation:
Default Inputs:
fast=12, slow=26, signal=9
EMA = Exponential Moving Average
PVO = (EMA(volume, fast) - EMA(volume, slow)) / EMA(volume, slow)
Signal = EMA(PVO, signal)
Histogram = PVO - Signal
Args:
volume (pd.Series): Series of 'volume's
fast (int): The short period. Default: 12
slow (int): The long period. Default: 26
signal (int): The signal period. Default: 9
scalar (float): How much to magnify. Default: 100
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: pvo, histogram, signal columns.
"""
+14 -10
View File
@@ -1,19 +1,21 @@
# -*- coding: utf-8 -*-
from ..utils import get_offset, verify_series
from pandas_ta.overlap import sma
from pandas_ta.utils import get_offset, verify_series
def dpo(close, length=None, centered=True, offset=None, **kwargs):
"""Indicator: Detrend Price Oscillator (DPO)"""
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 1
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length
length = int(length) if length and length > 0 else 20
offset = get_offset(offset)
# Calculate Result
drift = int(0.5 * length) + 1 # int((0.5 * length) + 1)
dpo = close.shift(drift) - close.rolling(length, min_periods=min_periods).mean()
t = int(0.5 * length) + 1
ma = sma(close, length)
dpo = close - ma.shift(t)
if centered:
dpo = dpo.shift(-drift)
dpo = (close.shift(t) - ma).shift(-t)
# Offset
if offset != 0:
@@ -40,17 +42,19 @@ Is an indicator designed to remove trend from price and make it easier to
identify cycles.
Sources:
https://www.tradingview.com/scripts/detrendedpriceoscillator/
https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/dpo
http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:detrended_price_osci
Calculation:
Default Inputs:
length=1, centered=True
length=20, centered=True
SMA = Simple Moving Average
drift = int(0.5 * length) + 1
t = int(0.5 * length) + 1
DPO = close.shift(drift) - SMA(close, length)
DPO = close.shift(t) - SMA(close, length)
if centered:
DPO = DPO.shift(-drift)
DPO = DPO.shift(-t)
Args:
close (pd.Series): Series of 'close's
+6 -1
View File
@@ -166,7 +166,7 @@ class TestMomentum(TestCase):
try:
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]})
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:
try:
@@ -222,6 +222,11 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, 'PSL_12')
def test_pvo(self):
result = pandas_ta.pvo(self.volume)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, 'PVO_12_26_9')
def test_roc(self):
result = pandas_ta.roc(self.close)
self.assertIsInstance(result, Series)
+7 -2
View File
@@ -86,7 +86,7 @@ class TestMomentumExtension(TestCase):
def test_macd_ext(self):
self.data.ta.macd(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ['MACD_12_26_9', 'MACDH_12_26_9', 'MACDS_12_26_9'])
self.assertEqual(list(self.data.columns[-3:]), ['MACD_12_26_9', 'MACDh_12_26_9', 'MACDs_12_26_9'])
def test_mom_ext(self):
self.data.ta.mom(append=True)
@@ -96,13 +96,18 @@ class TestMomentumExtension(TestCase):
def test_ppo_ext(self):
self.data.ta.ppo(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ['PPO_12_26_9', 'PPOH_12_26_9', 'PPOS_12_26_9'])
self.assertEqual(list(self.data.columns[-3:]), ['PPO_12_26_9', 'PPOh_12_26_9', 'PPOs_12_26_9'])
def test_psl_ext(self):
self.data.ta.psl(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'PSL_12')
def test_pvo_ext(self):
self.data.ta.pvo(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ['PVO_12_26_9', 'PVOh_12_26_9', 'PVOs_12_26_9'])
def test_roc_ext(self):
self.data.ta.roc(append=True)
self.assertIsInstance(self.data, DataFrame)
+1 -1
View File
@@ -107,7 +107,7 @@ class TestTrend(TestCase):
def test_dpo(self):
result = pandas_ta.dpo(self.close)
self.assertIsInstance(result, Series)
self.assertEqual(result.name, 'DPO_1')
self.assertEqual(result.name, 'DPO_20')
def test_increasing(self):
result = pandas_ta.increasing(self.close)
+1 -1
View File
@@ -56,7 +56,7 @@ class TestTrendExtension(TestCase):
def test_dpo_ext(self):
self.data.ta.dpo(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], 'DPO_1')
self.assertEqual(self.data.columns[-1], 'DPO_20')
def test_increasing_ext(self):
self.data.ta.increasing(append=True)