diff --git a/README.md b/README.md index 2176400..d2f6e39 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Supported statistics/indicators are: * KST: Know Sure Thing * PGO: Pretty Good Oscillator * PSL: Psychological Line +* PVO: Percentage Volume Oscillator ## Installation @@ -1022,6 +1023,27 @@ Example: * `df['psl_10']` retrieves the PSL with window 10. * `df['high_12_psl']` retrieves the PSL of high price with window 10. +#### [Percentage Volume Oscillator(PVO)](https://school.stockcharts.com/doku.php?id=technical_indicators:percentage_volume_oscillator_pvo) + +The Percentage Volume Oscillator (PVO) is a momentum oscillator for volume. +The PVO measures the difference between two volume-based moving averages as +a percentage of the larger moving average. + +Formular: + +* Percentage Volume Oscillator (PVO): + ((12-day EMA of Volume - 26-day EMA of Volume)/26-day EMA of Volume) x 100 +* Signal Line: 9-day EMA of PVO +* PVO Histogram: PVO - Signal Line + +Example: +* `df['pvo']` derives from the difference of 2 exponential moving average. +* `df['pvos]` is the signal line. +* `df['pvoh']` is he histogram line. + +The period of short, long EMA and signal line can be tuned with +`set_dft_window('pvo', (short, long, signal))`. The default +windows are 12 and 26 and 9. ## Issues diff --git a/stockstats.py b/stockstats.py index 7e5be16..0d03100 100644 --- a/stockstats.py +++ b/stockstats.py @@ -73,6 +73,7 @@ class StockStatsError(Exception): 'pdi': 14, 'pgo': 14, 'ppo': (12, 26, 9), # short, long, signal + 'pvo': (12, 26, 9), # short, long, signal 'psl': 12, 'rsi': 14, 'rsv': 9, @@ -1120,6 +1121,37 @@ def _get_macd(self, meta: _Meta): self[macds] = self.ema(self[macd], signal_w) self[macdh] = self[macd] - self[macds] + def _ppo_and_pvo(self, name: str, col_name: str, meta: _Meta): + volume = self[col_name] + short_w, long_w, signal_w = meta.int0, meta.int1, meta.int2 + pvo_short = self.ema(volume, short_w) + pvo_long = self.ema(volume, long_w) + self[name] = (pvo_short - pvo_long) / pvo_long * 100 + self[f'{name}s'] = self.ema(self[name], signal_w) + self[f'{name}h'] = self[name] - self[f'{name}s'] + + def _get_pvo(self, meta: _Meta): + """ Percentage Volume Oscillator + + The Percentage Volume Oscillator (PVO) is a momentum oscillator for + volume. The PVO measures the difference between two volume-based + moving averages as a percentage of the larger moving average. + + https://school.stockcharts.com/doku.php?id=technical_indicators:percentage_volume_oscillator_pvo + + Percentage Volume Oscillator (PVO): + {(12_EOV - 26_EOV)/26_EOV} x 100 + + Where: + * 12_EOV is the 12-day EMA of Volume + * 26_EOV is the 26-day EMA of Volume + + Signal Line: 9-day EMA of PVO + + PVO Histogram: PVO - Signal Line + """ + return self._ppo_and_pvo('pvo', 'volume', meta) + def _get_ppo(self, meta: _Meta): """ Percentage Price Oscillator @@ -1132,13 +1164,7 @@ def _get_ppo(self, meta: _Meta): PPO Histogram: PPO - Signal Line """ - close = self['close'] - short_w, long_w, signal_w = meta.int0, meta.int1, meta.int2 - ppo_short = self.ema(close, short_w) - ppo_long = self.ema(close, long_w) - self['ppo'] = (ppo_short - ppo_long) / ppo_long * 100 - self['ppos'] = self.ema(self['ppo'], signal_w) - self['ppoh'] = self['ppo'] - self['ppos'] + return self._ppo_and_pvo('ppo', 'close', meta) def _eri(self, window): ema = self.ema(self['close'], window, adjust=False) @@ -1738,6 +1764,7 @@ def handler(self): ('tp',): self._get_tp, ('boll', 'boll_ub', 'boll_lb'): self._get_boll, ('macd', 'macds', 'macdh'): self._get_macd, + ('pvo', 'pvos', 'pvoh'): self._get_pvo, ('ppo', 'ppos', 'ppoh'): self._get_ppo, ('cr', 'cr-ma1', 'cr-ma2', 'cr-ma3'): self._get_cr, ('tr',): self._get_tr, diff --git a/test.py b/test.py index 7337384..11b5cf9 100644 --- a/test.py +++ b/test.py @@ -1079,3 +1079,10 @@ def test_psl(self): high_psl12 = stock['high_12_psl'] assert_that(high_psl12[20110118], near_to(41.666)) assert_that(high_psl12[20110127], near_to(41.666)) + + def test_pvo(self): + stock = self.get_stock_90days() + _ = stock[['pvo', 'pvos', 'pvoh']] + assert_that(stock['pvo'].loc[20110331], near_to(3.4708)) + assert_that(stock['pvos'].loc[20110331], near_to(-3.7464)) + assert_that(stock['pvoh'].loc[20110331], near_to(7.2173))