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