BUG #327 ENH #336 MAINT minor refactoring TST kvo update

This commit is contained in:
Kevin Johnson
2021-07-11 15:27:21 -07:00
parent 5f6a23ced7
commit 41817b19b0
11 changed files with 69 additions and 56 deletions
+4 -2
View File
@@ -114,7 +114,7 @@ $ pip install pandas_ta
Latest Version
--------------
Best choice! Version: *0.3.05b*
Best choice! Version: *0.3.06b*
* Includes all fixes and updates between **pypi** and what is covered in this README.
```sh
$ pip install -U git+https://github.com/twopirllc/pandas-ta
@@ -987,11 +987,13 @@ of the last bars defined by the length parameter. See ```help(ta.tos_stdevall)``
* _Chande Kroll Stop_ (**cksp**): Added ```tvmode``` with default ```True```. When ```tvmode=False```, **cksp** implements “The New Technical Trader” with default values. See ```help(ta.cksp)```.
* _Decreasing_ (**decreasing**): New argument ```strict``` checks if the series is continuously decreasing over period ```length``` with a faster calculation. Default: ```False```. The ```percent``` argument has also been added with default None. See ```help(ta.decreasing)```.
* _Increasing_ (**increasing**): New argument ```strict``` checks if the series is continuously increasing over period ```length``` with a faster calculation. Default: ```False```. The ```percent``` argument has also been added with default None. See ```help(ta.increasing)```.
* _Klinger Volume Oscillator_ (**kvo**): Implements TradingView's Klinger Volume Oscillator verion. See ```help(ta.kvo)```.
* _Moving Average Convergence Divergence_ (**macd**): New argument ```asmode``` enables AS version of MACD. Default is False. See ```help(ta.macd)```.
* _Parabolic Stop and Reverse_ (**psar**): Bug fix and adjustment to match TradingView's ```sar```. New argument ```af0``` to initialize the Acceleration Factor. See ```help(ta.psar)```.
* _Percentage Price Oscillator_ (**ppo**): Included new argument ```mamode``` as an option. Default is **sma** to match TA Lib. See ```help(ta.ppo)```.
* _Volume Profile_ (**vp**): Calculation improvements. See [Pull Request #320](https://github.com/twopirllc/pandas-ta/pull/320) See ```help(ta.vp)```.
* _Volume Weighted Moving Average_ (**vwma**): Fixed bug in DataFrame Extension call. See ```help(ta.vwma)```.
* _Volume Weighted Average Price_ (**vwap**): Added a new parameter called ```anchor```. Default: "D" for "Daily". See [Timeseries Offset Aliases](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#timeseries-offset-aliases) for additional options. **Requires** the DataFrame index to be a DatetimeIndex. See ```help(ta.vwap)```.
* _Volume Weighted Moving Average_ (**vwma**): Fixed bug in DataFrame Extension call. See ```help(ta.vwma)```.
* _Z Score_ (**zscore**): Changed return column name from ```Z_length``` to ```ZS_length```. See ```help(ta.zscore)```.
<br />
+20 -4
View File
@@ -18,6 +18,8 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
if close is None: return
as_mode = kwargs.setdefault("asmode", False)
# Calculate Result
if Imports["talib"]:
from talib import MACD
@@ -30,6 +32,11 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
signalma = ema(close=macd.loc[macd.first_valid_index():,], length=signal)
histogram = macd - signalma
if as_mode:
macd = macd - signalma
signalma = ema(close=macd.loc[macd.first_valid_index():,], length=signal)
histogram = macd - signalma
# Offset
if offset != 0:
macd = macd.shift(offset)
@@ -47,16 +54,17 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_asmode = "AS" if as_mode else ""
_props = f"_{fast}_{slow}_{signal}"
macd.name = f"MACD{_props}"
histogram.name = f"MACDh{_props}"
signalma.name = f"MACDs{_props}"
macd.name = f"MACD{_asmode}{_props}"
histogram.name = f"MACD{_asmode}h{_props}"
signalma.name = f"MACD{_asmode}s{_props}"
macd.category = histogram.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {macd.name: macd, histogram.name: histogram, signalma.name: signalma}
df = DataFrame(data)
df.name = f"MACD{_props}"
df.name = f"MACD{_asmode}{_props}"
df.category = macd.category
signal_indicators = kwargs.pop("signal_indicators", False)
@@ -105,6 +113,7 @@ the difference of MACD and Signal.
Sources:
https://www.tradingview.com/wiki/MACD_(Moving_Average_Convergence/Divergence)
AS Mode: https://tr.tradingview.com/script/YFlKXHnP/
Calculation:
Default Inputs:
@@ -114,6 +123,11 @@ Calculation:
Signal = EMA(MACD, signal)
Histogram = MACD - Signal
if asmode:
MACD = MACD - Signal
Signal = EMA(MACD, signal)
Histogram = MACD - Signal
Args:
close (pd.Series): Series of 'close's
fast (int): The short period. Default: 12
@@ -122,6 +136,8 @@ Args:
offset (int): How many periods to offset the result. Default: 0
Kwargs:
asmode (value, optional): When True, enables AS version of MACD.
Default: False
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
+8 -1
View File
@@ -87,7 +87,14 @@ def yf(ticker: str, **kwargs):
# Ticker Info & Chart History
yfd = yfra.Ticker(ticker)
df = yfd.history(period=period, interval=interval, proxy=proxy, **kwargs)
try:
df = yfd.history(period=period, interval=interval, proxy=proxy, **kwargs)
except:
if yfra.__version__ == "0.1.60":
print(f"[!] If history is not downloading, see yfinance Issue #760 by user djl0.")
print(f"[!] https://github.com/ranaroussi/yfinance/issues/760#issuecomment-877355832")
return
if df.empty: return
df.name = ticker
+22 -37
View File
@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
from numpy import where as npWhere
from pandas import DataFrame
from pandas_ta.overlap import hlc3, ma
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series
from pandas_ta.utils import get_drift, get_offset, signed_series, verify_series
def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode=None, drift=None, offset=None, **kwargs):
def kvo(high, low, close, volume, fast=None, slow=None, signal=None, mamode=None, drift=None, offset=None, **kwargs):
"""Indicator: Klinger Volume Oscillator (KVO)"""
# Validate arguments
fast = int(fast) if fast and fast > 0 else 34
slow = int(slow) if slow and slow > 0 else 55
length_sig = int(length_sig) if length_sig and length_sig > 0 else 13
signal = int(signal) if signal and signal > 0 else 13
mamode = mamode.lower() if mamode and isinstance(mamode, str) else "ema"
_length = max(fast, slow, length_sig)
_length = max(fast, slow, signal)
high = verify_series(high, _length)
low = verify_series(low, _length)
close = verify_series(close, _length)
@@ -23,19 +22,10 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode=
if high is None or low is None or close is None or volume is None: return
# Calculate Result
mom = hlc3(high, low, close).diff(drift)
trend = npWhere(mom > 0, 1, 0) + npWhere(mom < 0, -1, 0)
dm = non_zero_range(high, low)
m = high.size
cm = [0] * m
for i in range(1, m):
cm[i] = (cm[i - 1] + dm[i]) if trend[i] == trend[i - 1] else (dm[i - 1] + dm[i])
vf = 100 * volume * trend * abs(2 * dm / cm - 1)
kvo = ma(mamode, vf, length=fast) - ma(mamode, vf, length=slow)
kvo_signal = ma(mamode, kvo, length=length_sig)
signed_volume = volume * signed_series(hlc3(high, low, close), 1)
sv = signed_volume.loc[signed_volume.first_valid_index():,]
kvo = ma(mamode, sv, length=fast) - ma(mamode, sv, length=slow)
kvo_signal = ma(mamode, kvo.loc[kvo.first_valid_index():,], length=signal)
# Offset
if offset != 0:
@@ -51,17 +41,18 @@ def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode=
kvo_signal.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
kvo.name = f"KVO_{fast}_{slow}"
kvo_signal.name = f"KVOSig_{length_sig}"
_props = f"_{fast}_{slow}_{signal}"
kvo.name = f"KVO{_props}"
kvo_signal.name = f"KVOs{_props}"
kvo.category = kvo_signal.category = "volume"
# Prepare DataFrame to return
data = {kvo.name: kvo, kvo_signal.name: kvo_signal}
kvoandsig = DataFrame(data)
kvoandsig.name = f"KVO_{fast}_{slow}_{length_sig}"
kvoandsig.category = kvo.category
df = DataFrame(data)
df.name = f"KVO{_props}"
df.category = kvo.category
return kvoandsig
return df
kvo.__doc__ = \
@@ -71,23 +62,17 @@ This indicator was developed by Stephen J. Klinger. It is designed to predict
price reversals in a market by comparing volume to price.
Sources:
https://www.tradingview.com/script/Qnn7ymRK-Klinger-Volume-Oscillator/
https://www.investopedia.com/terms/k/klingeroscillator.asp
https://www.daytrading.com/klinger-volume-oscillator
Calculation:
Default Inputs:
fast=34, slow=55, length_sig=13, drift=1
MOM = HLC3.diff(drift)
NEG_TREND = -1 if MOM < 0 else 0
POS_TREND = 1 if MOM > 0 else 0
TREND = POS_TREND + NEG_TREND
DM = high - low
CM = [CMt-1 + DMt if TRENDt == TRENDt-1 else DMt-1 + DMt]
vf = 100 * volume * TREND * abs(2 * dm / cm - 1)
kvo = ema(vf, fast) - ema(vf, slow)
kvo_signal = ema(kvo, length_sig)
fast=34, slow=55, signal=13, drift=1
EMA = Exponential Moving Average
SV = volume * signed_series(HLC3, 1)
KVO = EMA(SV, fast) - EMA(SV, slow)
Signal = EMA(KVO, signal)
Args:
high (pd.Series): Series of 'high's
@@ -105,5 +90,5 @@ Kwargs:
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: kvo and kvo_signal columns.
pd.DataFrame: KVO and Signal columns.
"""
+1 -1
View File
@@ -17,7 +17,7 @@ def nvi(close, volume, length=None, initial=None, offset=None, **kwargs):
# Calculate Result
roc_ = roc(close=close, length=length)
signed_volume = signed_series(volume, initial=1)
signed_volume = signed_series(volume, 1)
nvi = signed_volume[signed_volume < 0].abs() * roc_
nvi.fillna(0, inplace=True)
nvi.iloc[0] = initial
+2 -3
View File
@@ -16,9 +16,8 @@ def pvi(close, volume, length=None, initial=None, offset=None, **kwargs):
if close is None or volume is None: return
# Calculate Result
roc_ = roc(close=close, length=length)
signed_volume = signed_series(volume, initial=1)
pvi = signed_volume[signed_volume > 0].abs() * roc_
signed_volume = signed_series(volume, 1)
pvi = roc(close=close, length=length) * signed_volume[signed_volume > 0].abs()
pvi.fillna(0, inplace=True)
pvi.iloc[0] = initial
pvi = pvi.cumsum()
+2 -3
View File
@@ -11,10 +11,9 @@ def pvol(close, volume, offset=None, **kwargs):
signed = kwargs.pop("signed", False)
# Calculate Result
pvol = close * volume
if signed:
pvol = signed_series(close, 1) * close * volume
else:
pvol = close * volume
pvol *= signed_series(close, 1)
# Offset
if offset != 0:
+3 -3
View File
@@ -16,10 +16,10 @@ def vp(close, volume, width=None, **kwargs):
if close is None or volume is None: return
# Setup
signed_price = signed_series(close, initial=1)
pos_volume = signed_price[signed_price > 0] * volume
signed_price = signed_series(close, 1)
pos_volume = volume * signed_price[signed_price > 0]
pos_volume.name = volume.name
neg_volume = signed_price[signed_price < 0] * -volume
neg_volume = -volume * signed_price[signed_price < 0]
neg_volume.name = volume.name
vp = concat([close, pos_volume, neg_volume], axis=1)
+1 -1
View File
@@ -19,7 +19,7 @@ setup(
"pandas_ta.volatility",
"pandas_ta.volume"
],
version=".".join(("0", "3", "05b")),
version=".".join(("0", "3", "06b")),
description=long_description,
long_description=long_description,
author="Kevin Johnson",
+1 -1
View File
@@ -63,7 +63,7 @@ class TestVolumeExtension(TestCase):
def test_kvo_ext(self):
self.data.ta.kvo(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "KVOSig_13")
self.assertEqual(list(self.data.columns[-2:]), ["KVO_34_55_13", "KVOs_34_55_13"])
def test_mfi_ext(self):
self.data.ta.mfi(append=True)
+5
View File
@@ -243,6 +243,11 @@ class TestMomentum(TestCase):
except Exception as ex:
error_analysis(result.iloc[:, 2], CORRELATION, ex, newline=False)
def test_macdas(self):
result = pandas_ta.macd(self.close, asmode=True)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "MACDAS_12_26_9")
def test_mom(self):
result = pandas_ta.mom(self.close)
self.assertIsInstance(result, Series)