mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-08-11 11:22:48 +08:00
BUG dpo calc fix ENH pvo added MAINT minor
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
# __Technical Analysis Library in Python 3.7__
|
||||

|
||||
|
||||
__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
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"""
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user