From 430ea742d674540133e0a47f6bf11a52cb3f31e1 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Fri, 13 May 2022 17:47:35 -0700 Subject: [PATCH] MAINT pvo vwap vwma refactor DOC readme ENH extra val --- README.md | 59 +++++++++++++++++++-------- pandas_ta/candles/cdl_pattern.py | 11 +++-- pandas_ta/candles/ha.py | 11 +++-- pandas_ta/core.py | 51 ++++++++++++----------- pandas_ta/maps.py | 10 ++--- pandas_ta/momentum/__init__.py | 1 - pandas_ta/momentum/bop.py | 2 +- pandas_ta/momentum/dm.py | 6 +-- pandas_ta/momentum/mom.py | 23 ++++++++++- pandas_ta/momentum/roc.py | 18 +++++++- pandas_ta/overlap/__init__.py | 2 - pandas_ta/overlap/hl2.py | 3 ++ pandas_ta/overlap/hlc3.py | 5 ++- pandas_ta/utils/_numba.py | 2 +- pandas_ta/volatility/hwc.py | 5 ++- pandas_ta/volume/__init__.py | 3 ++ pandas_ta/volume/ad.py | 2 +- pandas_ta/volume/nvi.py | 4 +- pandas_ta/volume/pvi.py | 4 +- pandas_ta/{momentum => volume}/pvo.py | 0 pandas_ta/{overlap => volume}/vwap.py | 12 +++--- pandas_ta/{overlap => volume}/vwma.py | 0 pandas_ta/volume/wb_tsv.py | 5 ++- tests/test_study.py | 16 ++++++-- 24 files changed, 171 insertions(+), 84 deletions(-) rename pandas_ta/{momentum => volume}/pvo.py (100%) rename pandas_ta/{overlap => volume}/vwap.py (94%) rename pandas_ta/{overlap => volume}/vwma.py (100%) diff --git a/README.md b/README.md index b76778d..f00f915 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,9 @@ Pandas TA is used by Applications and Services like
-[Open BB](https://openbb.co/) (previously Gamestonk Terminal) +[Open BB](https://openbb.co/) ------------------- +#### Previously **Gamestonk Terminal** > OpenBB is a leading open source investment analysis company. We represent millions of investors who want to leverage state-of-the-art data science and machine learning technologies to make sense of raw unrefined data. Our mission is to make investment research effective, powerful and accessible to everyone. @@ -158,7 +159,7 @@ We represent millions of investors who want to leverage state-of-the-art data sc
-[VectorBT Pro & Open Source](https://vectorbt.pro/) +[VectorBT Pro](https://vectorbt.pro/) ------------------- > vectorbt PRO is the next-generation engine for backtesting, algorithmic trading, and research. It's a high-performance, actively-developed, commercial successor to the vectorbt library, one of the world's most innovative open-source backtesting engines. The PRO version extends the standard library with new impressive features and useful enhancements for professionals. @@ -280,7 +281,7 @@ Back to [Contents](#contents) # **Issues and Contributions** -Contributions, feedback, and bug squashing are integral to the success of this library. If something you can fix, _please_ do. Your contributon helps everyone! +Contributions, feedback, and bug squashing are integral to the success of this library. If you see something you can fix, _please_ do. Your contributon helps us all! * :stop_sign: _Please_ **DO NOT** email me personally with Pandas TA Bugs, Issues or Feature Requests that are best handled with Github [Issues](https://github.com/twopirllc/pandas-ta/issues).
@@ -289,7 +290,7 @@ Contributions, feedback, and bug squashing are integral to the success of this l -------------------------------------- 1. Some bugs and features may already be be fixed or implemented in either the [Latest Version](#latest-version) or the the [Development Version](#development-version). _Please_ try them first. 1. If the _Latest_ or _Development_ Versions do not resolve the bug or address the Issue, try searching both _Open_ and _Closed_ Issues **before** opening a new Issue. -1. When you creating a new Issue, please be as **detailed** as possible **with** reproducible code, links if any, applicable screenshots, errors, logs, and data samples. +1. When creating a new Issue, please be as **detailed** as possible **with** reproducible code, links if any, applicable screenshots, errors, logs, and data samples. * You **will** be asked again for skipping form questions. * Do you have correlation analysis to back your claim? @@ -853,7 +854,7 @@ Back to [Contents](#contents)
-### **Momentum** (42) +### **Momentum** (41) * _Awesome Oscillator_: **ao** * _Absolute Price Oscillator_: **apo** * _Bias_: **bias** @@ -878,7 +879,6 @@ Back to [Contents](#contents) * _Pretty Good Oscillator_: **pgo** * _Percentage Price Oscillator_: **ppo** * _Psychological Line_: **psl** -* _Percentage Volume Oscillator_: **pvo** * _Quantitative Qualitative Estimation_: **qqe** * _Rate of Change_: **roc** * _Relative Strength Index_: **rsi** @@ -910,7 +910,7 @@ Back to [Contents](#contents)
-### **Overlap** (37) +### **Overlap** (35) * _Bill Williams Alligator_: **alligator** * _Arnaud Legoux Moving Average_: **alma** @@ -948,9 +948,6 @@ Back to [Contents](#contents) * _Triple Exponential Moving Average_: **tema** * _Triangular Moving Average_: **trima** * _Variable Index Dynamic Average_: **vidya** -* _Volume Weighted Average Price_: **vwap** - * **Requires** the DataFrame index to be a DatetimeIndex -* _Volume Weighted Moving Average_: **vwma** * _Weighted Closing Price_: **wcp** * _Weighted Moving Average_: **wma** * _Zero Lag Moving Average_: **zlma** @@ -1084,7 +1081,7 @@ Back to [Contents](#contents)
-### **Volume** (16) +### **Volume** (19) * _Accumulation/Distribution Index_: **ad** * _Accumulation/Distribution Oscillator_: **adosc** @@ -1097,10 +1094,14 @@ Back to [Contents](#contents) * _Negative Volume Index_: **nvi** * _On-Balance Volume_: **obv** * _Positive Volume Index_: **pvi** +* _Percentage Volume Oscillator_: **pvo** * _Price-Volume_: **pvol** * _Price Volume Rank_: **pvr** * _Price Volume Trend_: **pvt** * _Volume Profile_: **vp** +* _Volume Weighted Average Price_: **vwap** + * **Requires** the DataFrame index to be a DatetimeIndex +* _Volume Weighted Moving Average_: **vwma** * _Worden Brothers Time Segmented Value_: **wb_tsv**
@@ -1114,7 +1115,7 @@ Back to [Contents](#contents)
# **Backtesting** -While Pandas TA is not a backtesting application, Pandas TA does provide _two_ methods to help generate trading signals for backtesting purposes: **Trend Signals** (```ta.tsignals()```) and **Cross Signals** (```ta.xsignals()```). Both Signal methods return a DataFrame with columns for the Trend, Trades, Entries and Exits. +While Pandas TA is not a backtesting application, it does provide _two_ trend methods that generate trading signals for backtesting purposes: **Trend Signals** (```ta.tsignals()```) and **Cross Signals** (```ta.xsignals()```). Both Signal methods return a DataFrame with columns for the signal's Trend, Trades, Entries and Exits. A simple manual backtest using **Trend Signals** can be found in the [TA Analysis Notebook](https://github.com/twopirllc/pandas-ta/blob/main/examples/TA_Analysis.ipynb) starting at _Trend Creation_ cell. @@ -1123,14 +1124,14 @@ A simple manual backtest using **Trend Signals** can be found in the [TA Analysi Trend Signals ------------- * Useful for signals based on trends or **states**. -* Examples +* _Examples_ * **Golden Cross**: ```df.ta.sma(length=50) > df.ta.sma(length=200)``` * **Positive MACD Histogram**: ```df.ta.macd().iloc[:,1] > 0``` Cross Signals ------------- * Useful for Signal Crossings or **events**. -* Examples +* _Examples_ * RSI crosses above 30 and then below 70 * ZSCORE crosses above -2 and then below 2. @@ -1144,14 +1145,11 @@ _Ideally_ a backtesting application like [**vectorbt**](https://polakowo.io/vect Trend Signal Example -------------------- ```python -import pandas as pd import pandas_ta as ta import vectorbt as vbt -df = pd.DataFrame() - # requires 'yfinance' installed -df = df.ta.ticker("AAPL", timed=True) +df = ta.df.ta.ticker("AAPL", timed=True) # Create the "Golden Cross" df["GC"] = df.ta.sma(50, append=True) > df.ta.sma(200, append=True) @@ -1167,6 +1165,31 @@ print(pf.stats()) print(pf.returns_stats()) ``` +
+ +Cross Signal Example +-------------------- +```python +import pandas_ta as ta +import vectorbt as vbt + +# requires 'yfinance' installed +df = ta.df.ta.ticker("AAPL", timed=True) + +# Signal when RSI crosses above 30 and later below 70 +rsi = df.ta.rsi(append=True) + +# Create Cross Signals +rsi_long = ta.xsignals(rsi, 20, 80, above=True) + +# Create the Signals Portfolio +pf = vbt.Portfolio.from_signals(df.Close, entries=rsi_long.TS_Entries, exits=rsi_long.TS_Exits, freq="D", init_cash=100_000, fees=0.0025, slippage=0.0025) + +# Print Portfolio Stats and Return Stats +print(pf.stats()) +print(pf.returns_stats()) +``` + Back to [Contents](#contents)
diff --git a/pandas_ta/candles/cdl_pattern.py b/pandas_ta/candles/cdl_pattern.py index 2c8e374..2d37ab3 100644 --- a/pandas_ta/candles/cdl_pattern.py +++ b/pandas_ta/candles/cdl_pattern.py @@ -64,13 +64,16 @@ def cdl_pattern( pd.DataFrame: one column for each pattern. """ # Validate Arguments - open_ = v_series(open_) - high = v_series(high) - low = v_series(low) - close = v_series(close) + open_ = v_series(open_, 1) + high = v_series(high, 1) + low = v_series(low, 1) + close = v_series(close, 1) offset = v_offset(offset) scalar = v_scalar(scalar, 100) + if open_ is None or high is None or low is None or close is None: + return + # Patterns that implemented in pandas-ta pta_patterns = {"doji": cdl_doji, "inside": cdl_inside} diff --git a/pandas_ta/candles/ha.py b/pandas_ta/candles/ha.py index e3bbd12..9bd436c 100644 --- a/pandas_ta/candles/ha.py +++ b/pandas_ta/candles/ha.py @@ -37,12 +37,15 @@ def ha( pd.DataFrame: ha_open, ha_high,ha_low, ha_close columns. """ # Validate - open_ = v_series(open_) - high = v_series(high) - low = v_series(low) - close = v_series(close) + open_ = v_series(open_, 1) + high = v_series(high, 1) + low = v_series(low, 1) + close = v_series(close, 1) offset = v_offset(offset) + if open_ is None or high is None or low is None or close is None: + return + # Calculate m = close.size df = DataFrame({ diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 9861e4f..b4330ea 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -607,6 +607,8 @@ class AnalysisIndicators(object): # If All or a Category, exclude user list if any user_excluded = kwargs.pop("exclude", []) + if isinstance(user_excluded, str) and len(user_excluded) > 1: + user_excluded = [user_excluded] if mode["all"] or mode["category"]: excluded += user_excluded @@ -634,7 +636,8 @@ class AnalysisIndicators(object): if "length" in kwds and kwds["length"] > self._df.shape[0]: _ = True if _: removal.append(kwds) - if len(removal) > 0: [ta.remove(x) for x in removal] + if len(removal) > 0: + [ta.remove(x) for x in removal] verbose = kwargs.pop("verbose", False) if verbose: @@ -1028,11 +1031,6 @@ class AnalysisIndicators(object): result = psl(close=close, open_=open_, length=length, scalar=scalar, drift=drift, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def pvo(self, fast=None, slow=None, signal=None, scalar=None, offset: Int = None, **kwargs: DictLike): - volume = self._get_column(kwargs.pop("volume", "volume")) - result = pvo(volume=volume, fast=fast, slow=slow, signal=signal, scalar=scalar, offset=offset, **kwargs) - return self._post_process(result, **kwargs) - def qqe(self, length=None, smooth=None, factor=None, mamode=None, offset: Int = None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) result = qqe(close=close, length=length, smooth=smooth, factor=factor, mamode=mamode, offset=offset, **kwargs) @@ -1330,24 +1328,6 @@ class AnalysisIndicators(object): result = vidya(close=close, length=length, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def vwap(self, anchor=None, offset: Int = None, **kwargs: DictLike): - high = self._get_column(kwargs.pop("high", "high")) - low = self._get_column(kwargs.pop("low", "low")) - close = self._get_column(kwargs.pop("close", "close")) - volume = self._get_column(kwargs.pop("volume", "volume")) - - if not self.datetime_ordered(): - volume.index = self._df.index - - result = vwap(high=high, low=low, close=close, volume=volume, anchor=anchor, offset=offset, **kwargs) - return self._post_process(result, **kwargs) - - def vwma(self, volume=None, length=None, offset: Int = None, **kwargs: DictLike): - close = self._get_column(kwargs.pop("close", "close")) - volume = self._get_column(kwargs.pop("volume", "volume")) - result = vwma(close=close, volume=volume, length=length, offset=offset, **kwargs) - return self._post_process(result, **kwargs) - def wcp(self, offset: Int = None, **kwargs: DictLike): high = self._get_column(kwargs.pop("high", "high")) low = self._get_column(kwargs.pop("low", "low")) @@ -1768,6 +1748,11 @@ class AnalysisIndicators(object): result = pvi(close=close, volume=volume, length=length, initial=initial, signed=signed, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def pvo(self, fast=None, slow=None, signal=None, scalar=None, offset: Int = None, **kwargs: DictLike): + volume = self._get_column(kwargs.pop("volume", "volume")) + result = pvo(volume=volume, fast=fast, slow=slow, signal=signal, scalar=scalar, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def pvol(self, volume=None, offset: Int = None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) volume = self._get_column(kwargs.pop("volume", "volume")) @@ -1786,6 +1771,24 @@ class AnalysisIndicators(object): result = pvt(close=close, volume=volume, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def vwap(self, anchor=None, offset: Int = None, **kwargs: DictLike): + high = self._get_column(kwargs.pop("high", "high")) + low = self._get_column(kwargs.pop("low", "low")) + close = self._get_column(kwargs.pop("close", "close")) + volume = self._get_column(kwargs.pop("volume", "volume")) + + if not self.datetime_ordered(): + volume.index = self._df.index + + result = vwap(high=high, low=low, close=close, volume=volume, anchor=anchor, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + + def vwma(self, volume=None, length=None, offset: Int = None, **kwargs: DictLike): + close = self._get_column(kwargs.pop("close", "close")) + volume = self._get_column(kwargs.pop("volume", "volume")) + result = vwma(close=close, volume=volume, length=length, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def wb_tsv(self, length=None, signal=None, offset: Int = None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) volume = self._get_column(kwargs.pop("volume", "volume")) diff --git a/pandas_ta/maps.py b/pandas_ta/maps.py index f04d49e..4707284 100644 --- a/pandas_ta/maps.py +++ b/pandas_ta/maps.py @@ -49,9 +49,9 @@ Category: Dict[str, ListStr] = { "momentum": [ "ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo", "coppock", "cti", "er", "eri", "fisher", "inertia", "kdj", "kst", - "macd", "mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", - "rsx", "rvgi", "slope", "smi", "squeeze", "squeeze_pro", "stc", - "stoch", "stochf", "stochrsi", "td_seq", "trix", "tsi", "uo", "willr" + "macd", "mom", "pgo", "ppo", "psl", "qqe", "roc", "rsi", "rsx", + "rvgi", "slope", "smi", "squeeze", "squeeze_pro", "stc", "stoch", + "stochf", "stochrsi", "td_seq", "trix", "tsi", "uo", "willr" ], # Overlap "overlap": [ @@ -59,7 +59,7 @@ Category: Dict[str, ListStr] = { "hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mama", "mcgd", "midpoint", "midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "smma", "ssf", "ssf3", "supertrend", "swma", "t3", "tema", - "trima", "vidya", "vwap", "vwma", "wcp", "wma", "zlma" + "trima", "vidya", "wcp", "wma", "zlma" ], # Performance "performance": ["log_return", "percent_return"], @@ -87,7 +87,7 @@ Category: Dict[str, ListStr] = { # Note: "vp" or "Volume Profile" is excluded since it does not return a Time Series "volume": [ "ad", "adosc", "aobv", "cmf", "efi", "eom", "kvo", "mfi", "nvi", - "obv", "pvi", "pvol", "pvr", "pvt", "wb_tsv" + "obv", "pvi", "pvo", "pvol", "pvr", "pvt", "vwap", "vwma", "wb_tsv" ], } diff --git a/pandas_ta/momentum/__init__.py b/pandas_ta/momentum/__init__.py index c130803..ad8bf25 100644 --- a/pandas_ta/momentum/__init__.py +++ b/pandas_ta/momentum/__init__.py @@ -22,7 +22,6 @@ from .mom import mom from .pgo import pgo from .ppo import ppo from .psl import psl -from .pvo import pvo from .qqe import qqe from .roc import roc from .rsi import rsi diff --git a/pandas_ta/momentum/bop.py b/pandas_ta/momentum/bop.py index 82d1692..1510da1 100644 --- a/pandas_ta/momentum/bop.py +++ b/pandas_ta/momentum/bop.py @@ -50,7 +50,7 @@ def bop( offset = v_offset(offset) # Calculate - if Imports["talib"] and mode_tal: + if Imports["talib"] and mode_tal and close.size: from talib import BOP bop = BOP(open_, high, low, close) else: diff --git a/pandas_ta/momentum/dm.py b/pandas_ta/momentum/dm.py index 17848aa..dcbefa8 100644 --- a/pandas_ta/momentum/dm.py +++ b/pandas_ta/momentum/dm.py @@ -47,8 +47,8 @@ def dm( """ # Validate length = v_pos_default(length, 14) - high = v_series(high) - low = v_series(low) + high = v_series(high, length) + low = v_series(low, length) if high is None or low is None: return @@ -58,7 +58,7 @@ def dm( drift = v_drift(drift) offset = v_offset(offset) - if Imports["talib"] and mode_tal: + if Imports["talib"] and mode_tal and high.size and low.size: from talib import MINUS_DM, PLUS_DM pos = PLUS_DM(high, low, length) neg = MINUS_DM(high, low, length) diff --git a/pandas_ta/momentum/mom.py b/pandas_ta/momentum/mom.py index 52d91a3..d908411 100644 --- a/pandas_ta/momentum/mom.py +++ b/pandas_ta/momentum/mom.py @@ -1,8 +1,27 @@ # -*- coding: utf-8 -*- +# from numpy.ma import diff as np_ma_diff from pandas import Series -from pandas_ta._typing import DictLike, Int +from pandas_ta._typing import Array, DictLike, Int from pandas_ta.maps import Imports -from pandas_ta.utils import v_offset, v_pos_default, v_series, v_talib +from pandas_ta.utils import ( + # np_prepend, + v_offset, + v_pos_default, + v_series, + v_talib +) + + +try: + from numba import njit +except ImportError: + def njit(_): return _ + + +# Mockup +# @njit +# def np_mom(x: Array, n: Int): +# return np_prepend(np_ma_diff(x, n), n) def mom( diff --git a/pandas_ta/momentum/roc.py b/pandas_ta/momentum/roc.py index b2810b8..ba19255 100644 --- a/pandas_ta/momentum/roc.py +++ b/pandas_ta/momentum/roc.py @@ -1,15 +1,29 @@ # -*- coding: utf-8 -*- from pandas import Series -from pandas_ta._typing import DictLike, Int, IntFloat +from pandas_ta._typing import Array, DictLike, Int, IntFloat from pandas_ta.maps import Imports from pandas_ta.utils import ( + np_shift, v_offset, v_pos_default, v_scalar, v_series, v_talib ) -from .mom import mom +from .mom import mom#, np_mom + + +# try: +# from numba import njit +# except ImportError: +# def njit(_): return _ + + +# Mockup +# @njit +# def np_roc(x: Array, n: Int, k: IntFloat): +# result = k * np_mom(x, n) / np_shift(x, n) +# return result def roc( diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 2adc09f..d61edee 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -31,8 +31,6 @@ from .t3 import t3 from .tema import tema from .trima import trima from .vidya import vidya -from .vwap import vwap -from .vwma import vwma from .wcp import wcp from .wma import wma from .zlma import zlma diff --git a/pandas_ta/overlap/hl2.py b/pandas_ta/overlap/hl2.py index 4a9432d..c0ef833 100644 --- a/pandas_ta/overlap/hl2.py +++ b/pandas_ta/overlap/hl2.py @@ -31,6 +31,9 @@ def hl2( low = v_series(low) offset = v_offset(offset) + if high is None or low is None: + return + # Calculate avg = 0.5 * (high.values + low.values) hl2 = Series(avg, index=high.index) diff --git a/pandas_ta/overlap/hlc3.py b/pandas_ta/overlap/hlc3.py index 18274c0..185cc73 100644 --- a/pandas_ta/overlap/hlc3.py +++ b/pandas_ta/overlap/hlc3.py @@ -35,8 +35,11 @@ def hlc3( mode_tal = v_talib(talib) offset = v_offset(offset) + if high is None or low is None or close is None: + return + # Calculate - if Imports["talib"] and mode_tal: + if Imports["talib"] and mode_tal and close.size: from talib import TYPPRICE hlc3 = TYPPRICE(high, low, close) else: diff --git a/pandas_ta/utils/_numba.py b/pandas_ta/utils/_numba.py index 34800c4..ccbd9fb 100644 --- a/pandas_ta/utils/_numba.py +++ b/pandas_ta/utils/_numba.py @@ -17,7 +17,7 @@ def np_prepend(x: Array, n: Int, value: IntFloat = nan) -> Array: @njit -def np_roll(x: Array, n: Int, fn = None) -> Array: +def np_rolling(x: Array, n: Int, fn = None) -> Array: """Like Pandas Rolling Window. x.rolling(n).fn()""" m = x.size result = zeros_like(x, dtype=float) diff --git a/pandas_ta/volatility/hwc.py b/pandas_ta/volatility/hwc.py index fa4cad9..dc19acf 100644 --- a/pandas_ta/volatility/hwc.py +++ b/pandas_ta/volatility/hwc.py @@ -41,7 +41,7 @@ def hwc( pd.DataFrame: HWM (Mid), HWU (Upper), HWL (Lower) columns. """ # Validate - close = v_series(close) + close = v_series(close, 1) scalar = v_pos_default(scalar, 1) channels = v_bool(channels, False) na = v_pos_default(na, 0.2) @@ -50,6 +50,9 @@ def hwc( nd = v_pos_default(nd, 0.1) offset = v_offset(offset) + if close is None: + return + # Calculate Result last_a = last_v = last_var = 0 last_f = last_price = last_result = close[0] diff --git a/pandas_ta/volume/__init__.py b/pandas_ta/volume/__init__.py index 70a5030..259cf54 100644 --- a/pandas_ta/volume/__init__.py +++ b/pandas_ta/volume/__init__.py @@ -10,8 +10,11 @@ from .mfi import mfi from .nvi import nvi from .obv import obv from .pvi import pvi +from .pvo import pvo from .pvol import pvol from .pvr import pvr from .pvt import pvt from .vp import vp +from .vwap import vwap +from .vwma import vwma from .wb_tsv import wb_tsv diff --git a/pandas_ta/volume/ad.py b/pandas_ta/volume/ad.py index ade2b36..712697f 100644 --- a/pandas_ta/volume/ad.py +++ b/pandas_ta/volume/ad.py @@ -44,7 +44,7 @@ def ad( offset = v_offset(offset) # Calculate - if Imports["talib"] and mode_tal: + if Imports["talib"] and mode_tal and volume.size: from talib import AD ad = AD(high, low, close, volume) else: diff --git a/pandas_ta/volume/nvi.py b/pandas_ta/volume/nvi.py index a2f5a80..c682f1e 100644 --- a/pandas_ta/volume/nvi.py +++ b/pandas_ta/volume/nvi.py @@ -35,8 +35,8 @@ def nvi( """ # Validate length = v_pos_default(length, 1) - close = v_series(close, length) - volume = v_series(volume, length) + close = v_series(close, length + 1) + volume = v_series(volume, length + 1) if close is None or volume is None: return diff --git a/pandas_ta/volume/pvi.py b/pandas_ta/volume/pvi.py index bdec731..bf0ec3b 100644 --- a/pandas_ta/volume/pvi.py +++ b/pandas_ta/volume/pvi.py @@ -34,8 +34,8 @@ def pvi( """ # Validate length = v_pos_default(length, 1) - close = v_series(close, length) - volume = v_series(volume, length) + close = v_series(close, length + 1) + volume = v_series(volume, length + 1) if close is None or volume is None: return diff --git a/pandas_ta/momentum/pvo.py b/pandas_ta/volume/pvo.py similarity index 100% rename from pandas_ta/momentum/pvo.py rename to pandas_ta/volume/pvo.py diff --git a/pandas_ta/overlap/vwap.py b/pandas_ta/volume/vwap.py similarity index 94% rename from pandas_ta/overlap/vwap.py rename to pandas_ta/volume/vwap.py index 82bd531..a71e142 100644 --- a/pandas_ta/overlap/vwap.py +++ b/pandas_ta/volume/vwap.py @@ -47,10 +47,11 @@ def vwap( pd.DataFrame: New feature generated. """ # Validate - high = v_series(high) - low = v_series(low) - close = v_series(close) - volume = v_series(volume) + _length = 1 + high = v_series(high, _length) + low = v_series(low, _length) + close = v_series(close, _length) + volume = v_series(volume, _length) bands = v_list(bands) offset = v_offset(offset) @@ -60,7 +61,8 @@ def vwap( anchor = "D" typical_price = hlc3(high=high, low=low, close=close) - if not v_datetime_ordered(volume) or not v_datetime_ordered(typical_price): + if not v_datetime_ordered(volume) or \ + not v_datetime_ordered(typical_price): print("[!] VWAP requires a datetime ordered index.") return diff --git a/pandas_ta/overlap/vwma.py b/pandas_ta/volume/vwma.py similarity index 100% rename from pandas_ta/overlap/vwma.py rename to pandas_ta/volume/vwma.py diff --git a/pandas_ta/volume/wb_tsv.py b/pandas_ta/volume/wb_tsv.py index 9d26680..6a77b6b 100644 --- a/pandas_ta/volume/wb_tsv.py +++ b/pandas_ta/volume/wb_tsv.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from numpy import isnan from pandas import DataFrame, Series from pandas_ta._typing import DictLike, Int from pandas_ta.ma import ma @@ -52,7 +53,7 @@ def wb_tsv( # Validate length = v_pos_default(length, 18) signal = v_pos_default(signal, 10) - _length = max(length, signal) - 2 + _length = max(length, signal) + 1 close = v_series(close, _length) if close is None: @@ -69,6 +70,8 @@ def wb_tsv( cvd = signed_volume * close.diff(drift) tsv = cvd.rolling(length).sum() + if all(isnan(tsv)): + return # Emergency Break signal_ = ma(mamode, tsv, length=signal) ratio = tsv / signal_ diff --git a/tests/test_study.py b/tests/test_study.py index 9d29f19..4882622 100644 --- a/tests/test_study.py +++ b/tests/test_study.py @@ -112,7 +112,7 @@ class TestStudyMethods(TestCase): self.category = "Candles" self.data.ta.study(pandas_ta.AllStudy, verbose=verbose, timed=timed_test) - # @skipUnless(verbose, "verbose mode only") + @skipUnless(verbose, "verbose mode only") def test_all_without_append(self): """Study: All sans append""" self.category = "All: Sans append" @@ -230,10 +230,18 @@ class TestStudyMethods(TestCase): ) self.data.ta.study(custom, verbose=verbose, timed=timed_test) + def test_custom_ohlc(self): + """Custom E: OHLC""" + self.category = "Custom E: OHLC" + + self.data.rename(columns={"volume": "_v"}, inplace=True) + self.data.ta.study(exclude=pandas_ta.Category["volume"], verbose=verbose, timed=timed_test) + self.data.rename(columns={"_v": "volume"}, inplace=True) + # @skip - def test_custom_e(self): - """Custom E""" - self.category = "Custom E" + def test_custom_study_with_signals(self): + """Custom F: Custom Study with Signals""" + self.category = "Custom F: Custom Study with Signals" amat_logret_ta = [ {"kind": "amat", "fast": 20, "slow": 50 }, # 2