Skip to content

Commit

Permalink
PVI update to match Investopedia definition
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Garbutt committed Mar 17, 2024
1 parent 947cbde commit bc1faa8
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 30 deletions.
4 changes: 2 additions & 2 deletions pandas_ta/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1806,10 +1806,10 @@ def obv(self, offset=None, **kwargs: DictLike):
result = obv(close=close, volume=volume, offset=offset, **kwargs)
return self._post_process(result, **kwargs)

def pvi(self, length=None, initial=None, signed=True, offset=None, **kwargs: DictLike):
def pvi(self, length=None, initial=None, mamode=None, offset=None, **kwargs: DictLike):
close = self._get_column(kwargs.pop("close", "close"))
volume = self._get_column(kwargs.pop("volume", "volume"))
result = pvi(close=close, volume=volume, length=length, initial=initial, signed=signed, offset=offset, **kwargs)
result = pvi(close=close, volume=volume, length=length, initial=initial, mamode=mamode, offset=offset, **kwargs)
return self._post_process(result, **kwargs)

def pvo(self, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs: DictLike):
Expand Down
74 changes: 48 additions & 26 deletions pandas_ta/volume/pvi.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from pandas import Series
from pandas_ta._typing import DictLike, Int
from pandas_ta.ma import ma
from pandas_ta.momentum import roc
from pandas_ta.utils import signed_series, v_offset, v_pos_default, v_series

from pandas_ta.utils import signed_series, v_offset, v_mamode, v_pos_default, v_series

def pvi(
close: Series, volume: Series, length: Int = None, initial: Int = None,
offset: Int = None, **kwargs: DictLike
) -> Series:
mamode: str = None, offset: Int = None, **kwargs: DictLike
) -> pd.DataFrame:
"""Positive Volume Index (PVI)
The Positive Volume Index is a cumulative indicator that uses volume
Expand All @@ -21,48 +23,68 @@ def pvi(
Args:
close (pd.Series): Series of 'close's
volume (pd.Series): Series of 'volume's
length (int): The short period. Default: 13
initial (int): The short period. Default: 1000
length (int): The short period. Default: 255
initial (int): The short period. Default: 100
mamode (str): See ``help(ta.ma)``. Default: 'ema'
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.Series: New feature generated.
pd.DataFrame: New DataFrame with ['close', 'volume', 'PVI_1', 'PVIs_<length>']
"""
# Validate
length = v_pos_default(length, 1)
mamode = v_mamode(mamode, "ema")
length = v_pos_default(length, 255)
close = v_series(close, length + 1)
volume = v_series(volume, length + 1)
initial = v_pos_default(initial, 100)
offset = v_offset(offset)

if close is None or volume is None:
return

initial = v_pos_default(initial, 1000)
offset = v_offset(offset)
# Create a dataframe from the close and volume series
# retain index of the close series
df = close.to_frame('close')
df['volume'] = volume

df['PVI_1'] = pd.Series(dtype=float)
df.iloc[0, df.columns.get_loc('PVI_1')] = initial

# Get numpy arrays of the data
close_prices = df['close'].values
volumes = df['volume'].values
pvis = np.empty(len(df))

# Set the first value from from initial
pvis[0] = df.iloc[0]['PVI_1']

# Calculate
signed_volume = signed_series(volume, 1)
_roc = roc(close=close, length=length)
pvi = _roc * signed_volume[signed_volume > 0].abs()
pvi.fillna(0, inplace=True)
pvi.iloc[0] = initial
pvi = pvi.cumsum()

# Offset
for i in range(1, len(df)):
if volumes[i] > volumes[i-1]:
# PVI = Yesterday’s PVI + [[(Close – Yesterday’s Close) / Yesterday’s Close] * Yesterday’s PVI
pvis[i] = pvis[i-1] + (((close_prices[i] - close_prices[i-1]) / close_prices[i-1]) * pvis[i-1])
else:
# PVI = Yesterday’s PVI
pvis[i] = pvis[i-1]

# Update the df
df['PVI_1'] = pvis
df.name = "PVI_1"

if offset != 0:
pvi = pvi.shift(offset)
df['PVI_1'] = df['PVI_1'].shift(offset)

sig_series = ma(mamode, df['PVI_1'], length=length)
df[f'PVIs_{length}'] = sig_series

# Fill
if "fillna" in kwargs:
pvi.fillna(kwargs["fillna"], inplace=True)
df.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
pvi.fillna(method=kwargs["fill_method"], inplace=True)

# Name and Category
pvi.name = f"PVI_{length}"
pvi.category = "volume"
df.fillna(method=kwargs["fill_method"], inplace=True)

return pvi
return df
4 changes: 2 additions & 2 deletions tests/test_indicator_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_obv(df):

def test_pvi(df):
result = ta.pvi(df.close, df.volume)
assert isinstance(result, Series)
assert isinstance(result, DataFrame)
assert result.name == "PVI_1"


Expand Down Expand Up @@ -253,7 +253,7 @@ def test_ext_nvi(df):

def test_ext_pvi(df):
df.ta.pvi(append=True)
assert df.columns[-1] == "PVI_1"
assert list(df.columns) == ["close", "volume", "PVI_1", "PVIs_255"]


def test_ext_pvol(df):
Expand Down

0 comments on commit bc1faa8

Please sign in to comment.