Wave PM (Whistler Active Volatility Energy Price Mass)
References
Definition
- The Wave PM (Whistler Active Volatility Energy Price Mass) indicator is an oscillator described in the Mark Whistler’s book “Volatility Illuminated”.
- it was designed to help read cycles of volatility. Read when a strong trend is about to start and end, along with the potential duration of lateral chop.
- it is not a directional oscillator
Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100
import warnings
warnings.filterwarnings("ignore")
import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234
1234
#S&P 500 (^GSPC), Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)
#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT', 'CL=F', 'GC=F', '^TNX']
benchmark_tickers = ['^GSPC']
tickers = benchmark_tickers + ['GSK', 'NVO', 'PFE', 'DAL']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
# def history(self, period="1mo", interval="1d",
# start=None, end=None, prepost=False, actions=True,
# auto_adjust=True, back_adjust=False,
# proxy=None, rounding=False, tz=None, timeout=None, **kwargs):
dfs = {}
for ticker in tickers:
cur_data = yf.Ticker(ticker)
hist = cur_data.history(period="max", start='2000-01-01')
print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
dfs[ticker] = hist
2022-09-05 18:05:09.006954 ^GSPC (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:05:09.398440 GSK (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:05:09.787225 NVO (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:05:10.271711 PFE (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:05:10.547051 DAL (3863, 7) 2007-05-03 00:00:00 2022-09-02 00:00:00
ticker = 'DAL'
dfs[ticker].tail(5)
| Open | High | Low | Close | Volume | Dividends | Stock Splits | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2022-08-29 | 32.200001 | 32.349998 | 31.850000 | 32.029999 | 8758400 | 0.0 | 0 |
| 2022-08-30 | 32.250000 | 32.450001 | 31.469999 | 31.719999 | 7506400 | 0.0 | 0 |
| 2022-08-31 | 31.969999 | 32.020000 | 31.059999 | 31.070000 | 7450000 | 0.0 | 0 |
| 2022-09-01 | 30.650000 | 31.139999 | 29.940001 | 31.090000 | 8572700 | 0.0 | 0 |
| 2022-09-02 | 31.440001 | 31.830000 | 30.700001 | 30.940001 | 8626500 | 0.0 | 0 |
Define Wave PM calculation function
def cal_wavepm(ohlc: pd.DataFrame, period: int = 14, lookback_period: int = 100, column: str = "close") -> pd.Series:
"""
The Wave PM (Whistler Active Volatility Energy Price Mass) indicator is an oscillator described in the Mark
Whistler’s book “Volatility Illuminated”.
:param DataFrame ohlc: data
:param int period: period for moving average
:param int lookback_period: period for oscillator lookback
:return Series: WAVE PM
"""
ma = ohlc[column].rolling(window=period).mean()
std = ohlc[column].rolling(window=period).std(ddof=0)
def tanh(x):
two = np.where(x > 0, -2, 2)
what = two * x
ex = np.exp(what)
j = 1 - ex
k = ex - 1
l = np.where(x > 0, j, k)
output = l / (1 + ex)
return output
def osc(input_dev, mean, power):
variance = pd.Series(power).rolling(window=lookback_period).sum() / lookback_period
calc_dev = np.sqrt(variance) * mean
y = (input_dev / calc_dev)
oscLine = tanh(y)
return oscLine
dev = 3.2 * std
power = np.power(dev / ma, 2)
wavepm = osc(dev, ma, power)
return pd.Series(wavepm, name=f"WAVEPM{period}")
Calculate Wave PM
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
cal_wavepm
<function __main__.cal_wavepm(ohlc: pandas.core.frame.DataFrame, period: int = 14, lookback_period: int = 100, column: str = 'close') -> pandas.core.series.Series>
df_ta = cal_wavepm(df, period=10, lookback_period=90, column='Close')
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
610
df_ta = cal_wavepm(df, period=14, lookback_period=120, column='Close')
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
21
display(df.head(5))
display(df.tail(5))
| Open | High | Low | Close | Volume | WAVEPM10 | WAVEPM14 | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2007-05-03 | 19.32 | 19.50 | 18.25 | 18.40 | 8052800 | NaN | NaN |
| 2007-05-04 | 18.88 | 18.96 | 18.39 | 18.64 | 5437300 | NaN | NaN |
| 2007-05-07 | 18.83 | 18.91 | 17.94 | 18.08 | 2646300 | NaN | NaN |
| 2007-05-08 | 17.76 | 17.76 | 17.14 | 17.44 | 4166100 | NaN | NaN |
| 2007-05-09 | 17.54 | 17.94 | 17.44 | 17.58 | 7541100 | NaN | NaN |
| Open | High | Low | Close | Volume | WAVEPM10 | WAVEPM14 | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2022-08-29 | 32.20 | 32.35 | 31.85 | 32.03 | 8758400 | 0.626965 | 0.479660 |
| 2022-08-30 | 32.25 | 32.45 | 31.47 | 31.72 | 7506400 | 0.599117 | 0.539114 |
| 2022-08-31 | 31.97 | 32.02 | 31.06 | 31.07 | 7450000 | 0.633094 | 0.610828 |
| 2022-09-01 | 30.65 | 31.14 | 29.94 | 31.09 | 8572700 | 0.609835 | 0.649787 |
| 2022-09-02 | 31.44 | 31.83 | 30.70 | 30.94 | 8626500 | 0.640151 | 0.661071 |
df[['WAVEPM10', 'WAVEPM14']].hist(bins=50, figsize=(10, 4))
array([[<AxesSubplot:title={'center':'WAVEPM10'}>,
<AxesSubplot:title={'center':'WAVEPM14'}>]], dtype=object)

#https://github.com/matplotlib/mplfinance
#this package help visualize financial data
import mplfinance as mpf
import matplotlib.colors as mcolors
# all_colors = list(mcolors.CSS4_COLORS.keys())#"CSS Colors"
# all_colors = list(mcolors.TABLEAU_COLORS.keys()) # "Tableau Palette",
all_colors = ['dodgerblue', 'firebrick','limegreen','skyblue','lightgreen', 'navy','yellow','plum', 'yellowgreen']
# all_colors = list(mcolors.BASE_COLORS.keys()) #"Base Colors",
#https://github.com/matplotlib/mplfinance/issues/181#issuecomment-667252575
#list of colors: https://matplotlib.org/stable/gallery/color/named_colors.html
#https://github.com/matplotlib/mplfinance/blob/master/examples/styles.ipynb
def make_3panels2(main_data, mid_panel, chart_type='candle', names=None,
figratio=(14,9), fill_weights = (0, 0)):
"""
main chart type: default is candle. alternatives: ohlc, line
example:
start = 200
names = {'main_title': 'MAMA: MESA Adaptive Moving Average',
'sub_tile': 'S&P 500 (^GSPC)', 'y_tiles': ['price', 'Volume [$10^{6}$]']}
make_candle(df.iloc[-start:, :5], df.iloc[-start:][['MAMA', 'FAMA']], names = names)
"""
style = mpf.make_mpf_style(base_mpf_style='yahoo', #charles
base_mpl_style = 'seaborn-whitegrid',
# marketcolors=mpf.make_marketcolors(up="r", down="#0000CC",inherit=True),
gridcolor="whitesmoke",
gridstyle="--", #or None, or - for solid
gridaxis="both",
edgecolor = 'whitesmoke',
facecolor = 'white', #background color within the graph edge
figcolor = 'white', #background color outside of the graph edge
y_on_right = False,
rc = {'legend.fontsize': 'small',#or number
#'figure.figsize': (14, 9),
'axes.labelsize': 'small',
'axes.titlesize':'small',
'xtick.labelsize':'small',#'x-small', 'small','medium','large'
'ytick.labelsize':'small'
},
)
if (chart_type is None) or (chart_type not in ['ohlc', 'line', 'candle', 'hollow_and_filled']):
chart_type = 'candle'
len_dict = {'candle':2, 'ohlc':3, 'line':1, 'hollow_and_filled':2}
kwargs = dict(type=chart_type, figratio=figratio, volume=True, volume_panel=2,
panel_ratios=(4,2,1), tight_layout=True, style=style, returnfig=True)
if names is None:
names = {'main_title': '', 'sub_tile': ''}
added_plots = { }
fb_bbands2_ = dict(y1=fill_weights[0]*np.ones(mid_panel.shape[0]),
y2=fill_weights[1]*np.ones(mid_panel.shape[0]),color="lightskyblue",alpha=0.1,interpolate=True)
fb_bbands2_['panel'] = 1
fb_bbands= [fb_bbands2_]
i = 0
for name_, data_ in mid_panel.iteritems():
added_plots[name_] = mpf.make_addplot(data_, panel=1, width=1, color=all_colors[i], secondary_y=False)
i = i + 1
fig, axes = mpf.plot(main_data, **kwargs,
addplot=list(added_plots.values()),
fill_between=fb_bbands)
# add a new suptitle
fig.suptitle(names['main_title'], y=1.05, fontsize=12, x=0.1285)
axes[0].set_title(names['sub_tile'], fontsize=10, style='italic', loc='left')
# axes[2].set_ylabel('WAVEPM10')
# axes[0].set_ylabel(names['y_tiles'][0])
# axes[2].set_ylabel(names['y_tiles'][1])
return fig, axes
start = -500
end = -400
names = {'main_title': f'{ticker}',
'sub_tile': 'Wave PM (Whistler Active Volatility Energy Price Mass): 0.9 - Danger, 0.7 - Breakout, 0.5 - Consolidation, 0.35 - Gear Change'}
aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']],
df.iloc[start:end][['WAVEPM10', 'WAVEPM14']],
chart_type='hollow_and_filled',names = names,
fill_weights = (0.35, .9))
