MAINT pvo vwap vwma refactor DOC readme ENH extra val

This commit is contained in:
Kevin Johnson
2022-05-13 17:47:35 -07:00
parent f0eed3c240
commit 430ea742d6
24 changed files with 171 additions and 84 deletions
+41 -18
View File
@@ -145,8 +145,9 @@ Pandas TA is used by Applications and Services like
<br/>
[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
<br/>
[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).
<br/>
@@ -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)
<br/>
### **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)
<br/>
### **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)
<br/>
### **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**
<br/>
@@ -1114,7 +1115,7 @@ Back to [Contents](#contents)
<br/>
# **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())
```
<br/>
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)
<br/>
+7 -4
View File
@@ -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}
+7 -4
View File
@@ -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({
+27 -24
View File
@@ -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"))
+5 -5
View File
@@ -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"
],
}
-1
View File
@@ -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
+1 -1
View File
@@ -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:
+3 -3
View File
@@ -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)
+21 -2
View File
@@ -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(
+16 -2
View File
@@ -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(
-2
View File
@@ -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
+3
View File
@@ -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)
+4 -1
View File
@@ -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:
+1 -1
View File
@@ -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)
+4 -1
View File
@@ -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]
+3
View File
@@ -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
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
@@ -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
+4 -1
View File
@@ -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_
+12 -4
View File
@@ -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