diff --git a/README.md b/README.md index b098376..27a85a8 100644 --- a/README.md +++ b/README.md @@ -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)```.
diff --git a/pandas_ta/momentum/macd.py b/pandas_ta/momentum/macd.py index 1b6cf41..20e7f82 100644 --- a/pandas_ta/momentum/macd.py +++ b/pandas_ta/momentum/macd.py @@ -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 diff --git a/pandas_ta/utils/data/yahoofinance.py b/pandas_ta/utils/data/yahoofinance.py index 10c3767..3fa464e 100644 --- a/pandas_ta/utils/data/yahoofinance.py +++ b/pandas_ta/utils/data/yahoofinance.py @@ -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 diff --git a/pandas_ta/volume/kvo.py b/pandas_ta/volume/kvo.py index a497a02..ea20609 100644 --- a/pandas_ta/volume/kvo.py +++ b/pandas_ta/volume/kvo.py @@ -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. """ diff --git a/pandas_ta/volume/nvi.py b/pandas_ta/volume/nvi.py index fb08b31..1c1cead 100644 --- a/pandas_ta/volume/nvi.py +++ b/pandas_ta/volume/nvi.py @@ -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 diff --git a/pandas_ta/volume/pvi.py b/pandas_ta/volume/pvi.py index 8ab2da9..302d378 100644 --- a/pandas_ta/volume/pvi.py +++ b/pandas_ta/volume/pvi.py @@ -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() diff --git a/pandas_ta/volume/pvol.py b/pandas_ta/volume/pvol.py index 8168943..aba6832 100644 --- a/pandas_ta/volume/pvol.py +++ b/pandas_ta/volume/pvol.py @@ -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: diff --git a/pandas_ta/volume/vp.py b/pandas_ta/volume/vp.py index b6f5273..7a5d7ce 100644 --- a/pandas_ta/volume/vp.py +++ b/pandas_ta/volume/vp.py @@ -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) diff --git a/setup.py b/setup.py index 43ec343..fdc7b35 100644 --- a/setup.py +++ b/setup.py @@ -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", diff --git a/tests/test_ext_indicator_volume.py b/tests/test_ext_indicator_volume.py index a95292c..1ca417e 100644 --- a/tests/test_ext_indicator_volume.py +++ b/tests/test_ext_indicator_volume.py @@ -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) diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 84e4b68..468f4c0 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -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)