Skip to content

Commit

Permalink
made changes to vpt and added unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Groni3000 committed Oct 31, 2023
1 parent 17b8dea commit e679c30
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 18 deletions.
28 changes: 10 additions & 18 deletions ta/volume.py
Expand Up @@ -264,31 +264,21 @@ class VolumePriceTrendIndicator(IndicatorMixin):
Args:
close(pandas.Series): dataset 'Close' column.
volume(pandas.Series): dataset 'Volume' column.
fillna(bool): if True, fill nan values.
fillna(bool)=False: if True, fill nan values. DO NOT RECCOMEND to set it True.
smoothing_factor(int)=None: will smooth default VPT implementation with SMA.
dropnans(bool)=False: drop nans after indicator calculated.
"""

def __init__(self, close: pd.Series, volume: pd.Series, fillna: bool = False, smoothing_factor: int = None, dropnans:bool = False):
self._close = close
self._volume = volume
self._fillna = fillna #This should never be used here like it was before `self._close.shift(1, fill_value=self._close.mean()`. That thing ruins indicator until it's influence will be miserable.

self._fillna = fillna
self._smoothing_factor = smoothing_factor
if smoothing_factor is not None:
if not isinstance(self._smoothing_factor, (int, float)): raise TypeError("Smoothing factor must be an integer.")# Float in case of 10. or something like this
else:
if self._smoothing_factor != int(self._smoothing_factor):
print("You have provided smoothing factor as float such that not equal to integer! We will force it to be an integer.")
self._smoothing_factor = int(self._smoothing_factor)

if self._smoothing_factor <= 1: raise ValueError("Smoothing factor must be bigger than 1.")

self._dropnans = dropnans
if not isinstance(self._dropnans, bool): raise TypeError("dropnans must be boolean.")

self._run()

def _run(self):
self._vpt = (self._close.pct_change().fillna(self._close.mean()) * self._volume).cumsum() #.fillna(self._close.mean()) is BAD
self._vpt = (self._close.pct_change() * self._volume).cumsum()
if self._smoothing_factor:
min_periods = 0 if self._fillna else self._window
self._vpt = self._vpt.rolling(self._smoothing_factor, min_periods=min_periods).mean()
Expand Down Expand Up @@ -622,7 +612,7 @@ def sma_ease_of_movement(high, low, volume, window=14, fillna=False):
).sma_ease_of_movement()


def volume_price_trend(close, volume, fillna=False):
def volume_price_trend(close, volume, fillna=False, smoothing_factor: int=None, dropnans: bool=False):
"""Volume-price trend (VPT)
Is based on a running cumulative volume that adds or substracts a multiple
Expand All @@ -634,13 +624,15 @@ def volume_price_trend(close, volume, fillna=False):
Args:
close(pandas.Series): dataset 'Close' column.
volume(pandas.Series): dataset 'Volume' column.
fillna(bool): if True, fill nan values.
fillna(bool)=False: if True, fill nan values. DO NOT RECCOMEND to set it True.
smoothing_factor(int)=None: will smooth default VPT implementation with SMA.
dropnans(bool)=False: drop nans after indicator calculated.
Returns:
pandas.Series: New feature generated.
"""
return VolumePriceTrendIndicator(
close=close, volume=volume, fillna=fillna
close=close, volume=volume, fillna=fillna, smoothing_factor=smoothing_factor, dropnans=dropnans
).volume_price_trend()


Expand Down
31 changes: 31 additions & 0 deletions test/data/cs-vpt.csv
@@ -0,0 +1,31 @@
,Close,Volume,pct_change,pct_change x Volume,unsmoothed vpt,14-smoothed vpt
3-Dec-10,24.7486,18730.144,,,,
6-Dec-10,24.7088,12271.74,-0.0016081717753730906,-19.735065902716972,-19.735065902716972,
7-Dec-10,25.0373,24691.414,0.013294858511947005,328.26885558990745,308.53378968719045,
8-Dec-10,25.545,18357.606,0.02027774560355966,372.25086435838045,680.784654045571,
9-Dec-10,25.0672,22964.08,-0.018704247406537533,-429.52583378352045,251.2588202620505,
10-Dec-10,25.107,15918.948,0.0015877321759112384,25.275025946257855,276.5338462083084,
13-Dec-10,24.888,16067.044,-0.008722666985302774,-140.147474250207,136.38637195810136,
14-Dec-10,24.9975,16568.487,0.004399710703953508,72.89654960221455,209.2829215603159,
15-Dec-10,25.0473,16018.729,0.0019921992199221084,31.912499417943653,241.19542097825956,
16-Dec-10,25.336,9773.569,0.011526192443896077,112.65203715769694,353.8474581359565,
17-Dec-10,25.0572,22572.712000000003,-0.011004104831070283,-248.3924891695582,105.45496896639833,
20-Dec-10,25.4455,12986.669,0.015496543907539406,201.24848637118086,306.70345533757916,
21-Dec-10,25.5649,10906.659,0.004692381757088748,51.17820772238781,357.88166305996697,
22-Dec-10,25.555,5799.259,-0.00038724970565118255,-2.2457613407449712,355.635901719222,
23-Dec-10,25.4057,7395.274,-0.005842300919585264,-43.20541609078499,312.430485628437,276.8710494031886
27-Dec-10,25.3658,5818.161999999999,-0.0015705137036177153,-9.137503150867852,303.2929824775691,299.94448143035186
28-Dec-10,25.0373,7164.726,-0.012950508164536578,-92.78684255966749,210.50613991790163,292.94250644683126
29-Dec-10,24.9178,5672.914000000001,-0.004772878864733765,-27.076131332052285,183.43000858584935,257.41717462827967
30-Dec-10,24.878,5624.741999999999,-0.0015972517637993233,-8.984129080416132,174.44587950543323,251.93053600280703
31-Dec-10,24.9676,5023.469,0.003601575689364145,18.09240382667441,192.53828333210762,245.93085294022126
3-Jan-11,25.0473,7457.090999999999,0.003192137009564444,23.804056164789927,216.34233949689755,251.64199347870672
4-Jan-11,24.45,11798.008999999998,-0.023846881699823963,-281.3457249164584,-65.00338541956083,232.05011440871553
5-Jan-11,24.5694,12366.132,0.0048834355828222265,60.38920903067658,-4.614176388884246,214.49228602534808
6-Jan-11,24.0219,13294.865,-0.022283816454614414,-296.26033144887725,-300.8745078377615,167.72643131293967
7-Jan-11,23.8825,9256.87,-0.0058030380611024945,-53.717968936677856,-354.5924767744393,134.8658994743084
10-Jan-11,24.2011,9690.604,0.013340311943891958,129.27568028472717,-225.31679648971215,96.86445291521618
11-Jan-11,24.2807,8870.318000000001,0.0032891066934974678,29.175422307251075,-196.14137418246108,57.291378826471316
12-Jan-11,24.3305,7168.965,0.0020510117088881064,14.703631155609024,-181.43774302685205,18.928975630323173
13-Jan-11,24.44,11356.18,0.0045005240336204455,51.10876102011983,-130.32898200673222,-12.696700629331774
14-Jan-11,25.0,13379.374,0.022913256955810146,306.56503436988544,176.23605236315322,-21.772195637504336
67 changes: 67 additions & 0 deletions test/unit/volume.py
Expand Up @@ -9,13 +9,15 @@
MFIIndicator,
OnBalanceVolumeIndicator,
VolumeWeightedAveragePrice,
VolumePriceTrendIndicator,
acc_dist_index,
ease_of_movement,
force_index,
money_flow_index,
on_balance_volume,
sma_ease_of_movement,
volume_weighted_average_price,
volume_price_trend
)


Expand Down Expand Up @@ -253,6 +255,71 @@ def test_vwap2(self):
self._df[target].tail(), result.tail(), check_names=False
)

class TestVolumePriceTrendIndicator(unittest.TestCase):
"""
Original VPT: https://en.wikipedia.org/wiki/Volume%E2%80%93price_trend
One more: https://www.barchart.com/education/technical-indicators/price_volume_trend
According to TradingView: PVT = [((CurrentClose - PreviousClose) / PreviousClose) x Volume] + PreviousPVT
Smoothed version (by Alex Orekhov (everget)): https://ru.tradingview.com/script/3Ah2ALck-Price-Volume-Trend/
His script is using `pvt` (TradingView built-in variable) as described in TradingView documentation of PVT and just smoothing it with ema or sma by choice.
You can find smoothing here (13 row of script):
`signal = signalType == "EMA" ? ema(pvt, signalLength) : sma(pvt, signalLength)`
"""

_filename = "test/data/cs-vpt.csv"

@classmethod
def setUpClass(cls):
cls._df = pd.read_csv(cls._filename, sep=",")
cls._params = dict( # default VPT params, unsmoothed
close=cls._df["Close"],
volume=cls._df["Volume"],
fillna=False,
smoothing_factor=None,
dropnans=False
)
cls._params_smoothed = dict( # smoothed VPT params
close=cls._df["Close"],
volume=cls._df["Volume"],
fillna=False,
smoothing_factor=14,
dropnans=False
)
cls._indicator_default = VolumePriceTrendIndicator(**cls._params)
cls._indicator_smoothed = VolumePriceTrendIndicator(**cls._params_smoothed)

@classmethod
def tearDownClass(cls):
del cls._df

def test_vpt1(self):
target = "unsmoothed vpt"
result = volume_price_trend(**self._params)
pd.testing.assert_series_equal(
self._df[target].tail(), result.tail(), check_names=False
)

def test_vpt2(self):
target = "unsmoothed vpt"
result = self._indicator_default.volume_price_trend()
pd.testing.assert_series_equal(
self._df[target].tail(), result.tail(), check_names=False
)

def test_vpt3(self):
target = "14-smoothed vpt"
result = volume_price_trend(**self._params_smoothed)
pd.testing.assert_series_equal(
self._df[target].tail(), result.tail(), check_names=False
)

def test_vpt4(self):
target = "14-smoothed vpt"
result = self._indicator_default.volume_price_trend()
pd.testing.assert_series_equal(
self._df[target].tail(), result.tail(), check_names=False
)

if __name__ == "__main__":
unittest.main()

0 comments on commit e679c30

Please sign in to comment.