separates volatility models from cones function

This commit is contained in:
Danglewood
2023-02-27 16:50:05 -08:00
parent fe17100102
commit 4159b7cad2
4 changed files with 381 additions and 149 deletions

View File

@@ -13,6 +13,8 @@ from openbb_terminal.decorators import log_start_end
logger = logging.getLogger(__name__)
MAMODES = ["ema", "sma", "wma", "hma", "zlma"]
# These are parameters for the volatility models
VOLATILITY_MODELS = [
"STD",
"Parkinson",
@@ -177,6 +179,339 @@ def atr(
)
@log_start_end(log=logger)
def standard_deviation(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = False,
clean: bool = True,
) -> pd.DataFrame:
"""Standard deviation measures how widely returns are dispersed from the average return.
It is the most common (and biased) estimator of volatility.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.DataFrame : results
Dataframe with results.
Example
-------
>>> data = openbb.stocks.load('SPY')
>>> df = openbb.ta.standard_deviation(data)
"""
trading_periods = 365 if is_crypto else 252
log_return = (data["Close"] / data["Close"].shift(1)).apply(np.log)
result = log_return.rolling(window=window, center=False).std() * np.sqrt(
trading_periods
)
if clean:
return result.dropna()
return result
@log_start_end(log=logger)
def parkinson(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = False,
clean=True,
) -> pd.DataFrame:
"""Parkinson volatility uses the high and low price of the day rather than just close to close prices.
It is useful for capturing large price movements during the day.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.DataFrame : results
Dataframe with results.
Example
-------
>>> data = openbb.stocks.load('BTC-USD')
>>> df = openbb.ta.parkinson(data, is_crypto = True)
"""
trading_periods = 365 if is_crypto else 252
rs = (1.0 / (4.0 * np.log(2.0))) * (
(data["High"] / data["Low"]).apply(np.log)
) ** 2.0
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
@log_start_end(log=logger)
def garman_klass(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = False,
clean=True,
) -> pd.DataFrame:
"""Garman-Klass volatility extends Parkinson volatility by taking into account the opening and closing price.
As markets are most active during the opening and closing of a trading session.
It makes volatility estimation more accurate.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.DataFrame : results
Dataframe with results.
Example
-------
>>> data = openbb.stocks.load('BTC-USD')
>>> df = openbb.ta.garman_klass(data, is_crypto = True)
"""
trading_periods = 365 if is_crypto else 252
log_hl = (data["High"] / data["Low"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
rs = 0.5 * log_hl**2 - (2 * np.log(2) - 1) * log_co**2
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
@log_start_end(log=logger)
def hodges_tompkins(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = False,
clean=True,
) -> pd.DataFrame:
"""Hodges-Tompkins volatility is a bias correction for estimation using an overlapping data sample.
It produces unbiased estimates and a substantial gain in efficiency.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.DataFrame : results
Dataframe with results.
Example
-------
>>> data = openbb.stocks.load('BTC-USD')
>>> df = openbb.ta.hodges_tompkins(data, is_crypto = True)
"""
trading_periods = 365 if is_crypto else 252
log_return = (data["Close"] / data["Close"].shift(1)).apply(np.log)
vol = log_return.rolling(window=window, center=False).std() * np.sqrt(
trading_periods
)
h = window
n = (log_return.count() - h) + 1
adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2)))
result = vol * adj_factor
if clean:
return result.dropna()
return result
@log_start_end(log=logger)
def rogers_satchell(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = False,
clean=True,
) -> pd.Series:
"""Rogers-Satchell is an estimator for measuring the volatility with an average return not equal to zero.
Unlike Parkinson and Garman-Klass estimators, Rogers-Satchell incorporates a drift term,
mean return not equal to zero.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.Series : results
Pandas Series with results.
Example
-------
>>> data = openbb.stocks.load('BTC-USD')
>>> df = openbb.ta.rogers_satchell(data, is_crypto = True)
"""
trading_periods = 365 if is_crypto else 252
log_ho = (data["High"] / data["Open"]).apply(np.log)
log_lo = (data["Low"] / data["Open"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
def yang_zhang(
data: pd.DataFrame,
window: int = 30,
trading_periods: int = 252,
is_crypto: bool = True,
clean=True,
) -> pd.DataFrame:
"""Yang-Zhang volatility is the combination of the overnight (close-to-open volatility).
It is a weighted average of the Rogers-Satchell volatility and the open-to-close volatility.
Parameters
----------
data : pd.DataFrame
Dataframe of OHLC prices.
window : int [default: 30]
Length of window to calculate standard deviation.
trading_periods : int [default: 252]
Number of trading periods in a year.
is_crypto : bool [default: False]
If true, trading_periods is defined as 365.
clean : bool [default: True]
Whether to clean the data or not by dropping NaN values.
Returns
-------
pd.DataFrame : results
Dataframe with results.
Example
-------
>>> data = openbb.stocks.load('BTC-USD')
>>> df = openbb.ta.yang_zhang(data, is_crypto = True)
"""
trading_periods = 365 if is_crypto else 252
log_ho = (data["High"] / data["Open"]).apply(np.log)
log_lo = (data["Low"] / data["Open"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
log_oc = (data["Open"] / data["Close"].shift(1)).apply(np.log)
log_oc_sq = log_oc**2
log_cc = (data["Close"] / data["Close"].shift(1)).apply(np.log)
log_cc_sq = log_cc**2
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (
1.0 / (window - 1.0)
)
open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (
1.0 / (window - 1.0)
)
window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))
k = 0.34 / (1.34 + (window + 1) / (window - 1))
result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(np.sqrt) * np.sqrt(
trading_periods
)
if clean:
return result.dropna()
return result
@log_start_end(log=logger)
def cones(
data: pd.DataFrame,
@@ -228,11 +563,21 @@ def cones(
Example
-------
df = openbb.stocks.load("AAPL")
cones_df = openbb.ta.cones(data = df, lower_q = 0.10, upper_q = 0.90)
"""
>>> df = openbb.stocks.load("AAPL")
>>> cones_df = openbb.ta.cones(data = df, lower_q = 0.10, upper_q = 0.90)
n_days: int = 365 if is_crypto else 252
>>> cones_df = openbb.ta.cones(df,0.15,0.85,False,"Garman-Klass")
"""
estimator = pd.DataFrame()
if lower_q > upper_q:
lower_q, upper_q = upper_q, lower_q
if (lower_q >= 1) or (upper_q >= 1):
print("Error: lower_q and upper_q must be between 0 and 1")
cones_df = pd.DataFrame()
return cones_df
try:
lower_q_label = str(int(lower_q * 100))
@@ -247,152 +592,28 @@ def cones(
realized = []
data = data.sort_index(ascending=False)
def standard_deviation(data, window=30, trading_periods=n_days, clean=True):
"""Standard deviation measures how widely returns are dispersed from the average return.
It is the most common (and biased) estimator of volatility."""
log_return = (data["Close"] / data["Close"].shift(1)).apply(np.log)
result = log_return.rolling(window=window, center=False).std() * np.sqrt(
trading_periods
)
if clean:
return result.dropna()
return result
def parkinson(data, window=30, trading_periods=n_days, clean=True):
"""Parkinson volatility uses the high and low price of the day rather than just close to close prices.
It is useful for capturing large price movements during the day."""
rs = (1.0 / (4.0 * np.log(2.0))) * (
(data["High"] / data["Low"]).apply(np.log)
) ** 2.0
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
def garman_klass(data, window=30, trading_periods=n_days, clean=True):
"""Garman-Klass volatility extends Parkinson volatility by taking into account the opening and closing price.
As markets are most active during the opening and closing of a trading session.
It makes volatility estimation more accurate.
"""
log_hl = (data["High"] / data["Low"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
rs = 0.5 * log_hl**2 - (2 * np.log(2) - 1) * log_co**2
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
def hodges_tompkins(data, window=30, trading_periods=n_days, clean=True):
"""Hodges-Tompkins volatility is a bias correction for estimation using an overlapping data sample.
It produces unbiased estimates and a substantial gain in efficiency."""
log_return = (data["Close"] / data["Close"].shift(1)).apply(np.log)
vol = log_return.rolling(window=window, center=False).std() * np.sqrt(
trading_periods
)
h = window
n = (log_return.count() - h) + 1
adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2)))
result = vol * adj_factor
if clean:
return result.dropna()
return result
def rogers_satchell(data, window=30, trading_periods=n_days, clean=True):
"""Rogers-Satchell is an estimator for measuring the volatility with an average return not equal to zero.
Unlike Parkinson and Garman-Klass estimators, Rogers-Satchell incorporates a drift term,
mean return not equal to zero.
"""
log_ho = (data["High"] / data["Open"]).apply(np.log)
log_lo = (data["Low"] / data["Open"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
def f(v):
return (trading_periods * v.mean()) ** 0.5
result = rs.rolling(window=window, center=False).apply(func=f)
if clean:
return result.dropna()
return result
def yang_zhang(data, window=30, trading_periods=n_days, clean=True):
"""Yang-Zhang volatility is the combination of the overnight (close-to-open volatility).
It is a weighted average of the Rogers-Satchell volatility and the open-to-close volatility.
"""
log_ho = (data["High"] / data["Open"]).apply(np.log)
log_lo = (data["Low"] / data["Open"]).apply(np.log)
log_co = (data["Close"] / data["Open"]).apply(np.log)
log_oc = (data["Open"] / data["Close"].shift(1)).apply(np.log)
log_oc_sq = log_oc**2
log_cc = (data["Close"] / data["Close"].shift(1)).apply(np.log)
log_cc_sq = log_cc**2
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (
1.0 / (window - 1.0)
)
open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (
1.0 / (window - 1.0)
)
window_rs = rs.rolling(window=window, center=False).sum() * (
1.0 / (window - 1.0)
)
k = 0.34 / (1.34 + (window + 1) / (window - 1))
result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(
np.sqrt
) * np.sqrt(trading_periods)
if clean:
return result.dropna()
return result
for window in windows:
# Looping to build a dataframe with realized volatility over each window.
if model not in VOLATILITY_MODELS:
print("Model not available. Available models: ", VOLATILITY_MODELS)
elif model == "STD":
estimator = standard_deviation(window=window, data=data)
estimator = standard_deviation(
window=window, data=data, is_crypto=is_crypto
)
elif model == "Parkinson":
estimator = parkinson(window=window, data=data)
estimator = parkinson(window=window, data=data, is_crypto=is_crypto)
elif model == "Garman-Klass":
estimator = garman_klass(window=window, data=data)
estimator = garman_klass(window=window, data=data, is_crypto=is_crypto)
elif model == "Hodges-Tompkins":
estimator = hodges_tompkins(window=window, data=data)
estimator = hodges_tompkins(
window=window, data=data, is_crypto=is_crypto
)
elif model == "Rogers-Satchell":
estimator = rogers_satchell(window=window, data=data)
estimator = rogers_satchell(
window=window, data=data, is_crypto=is_crypto
)
elif model == "Yang-Zhang":
estimator = yang_zhang(window=window, data=data)
estimator = yang_zhang(window=window, data=data, is_crypto=is_crypto)
min_.append(estimator.min())
max_.append(estimator.max())
median.append(estimator.median())
@@ -417,8 +638,4 @@ def cones(
except Exception:
cones_df = pd.DataFrame()
if lower_q or upper_q > 0:
print(
"Upper and lower quantiles should be expressed as a value between 0 and 1"
)
return cones_df

View File

@@ -378,7 +378,6 @@ def display_cones(
is_crypto: bool = False,
export: str = "",
sheet_name: Optional[str] = None,
external_axes: Optional[List[plt.Axes]] = None,
):
"""Plots the realized volatility quantiles for the loaded ticker.
The model used to calculate the volatility is selectable.
@@ -422,8 +421,6 @@ def display_cones(
Format of export file
sheet_name: str
Optionally specify the name of the sheet the data is exported to.
external_axes : Optional[List[plt.Axes]], optional
External axes (1 axis is expected in the list), by default None
Examples
--------
@@ -473,8 +470,7 @@ def display_cones(
plt.xticks(df_ta.index)
plt.tight_layout(pad=2.0)
if external_axes is None:
theme.visualize_output()
theme.visualize_output()
export_data(
export,

View File

@@ -47,6 +47,12 @@ class TaRoot(Category):
`obv_chart`: Plots OBV technical indicator\n
`rsi`: Relative strength index\n
`rsi_chart`: Plots RSI Indicator\n
`rvol_std`: Standard deviation model for realized volatility.\n
`rvol_parkinson`: Parkinson's model for realized volatility.\n
`rvol_garman_klass`: Garman-Klass model for realized volatility.\n
`rvol_hodges_tompkins`: Hodges-Tompkins model for realized volatility.\n
`rvol_rogers-satchell`: Rogers-Satterthwaite model for realized volatility.\n
`rvol_yang_zhang`: Yang-Zhang model for realized volatility.\n
`cones`: Realized Volatility Cones\n
`cones_chart`: Plots Realized Volatility Cones\n
`sma`: Gets simple moving average (SMA) for stock\n
@@ -100,6 +106,12 @@ class TaRoot(Category):
self.obv_chart = lib.common_ta_volume_view.display_obv
self.rsi = lib.common_ta_momentum_model.rsi
self.rsi_chart = lib.common_ta_momentum_view.display_rsi
self.rvol_std = lib.common_ta_volatility_model.standard_deviation
self.rvol_parkinson = lib.common_ta_volatility_model.parkinson
self.rvol_garman_klass = lib.common_ta_volatility_model.garman_klass
self.rvol_hodges_tompkins = lib.common_ta_volatility_model.hodges_tompkins
self.rvol_rogers_satchell = lib.common_ta_volatility_model.rogers_satchell
self.rvol_yang_zhang = lib.common_ta_volatility_model.yang_zhang
self.cones = lib.common_ta_volatility_model.cones
self.cones_chart = lib.common_ta_volatility_view.display_cones
self.sma = lib.common_ta_overlap_model.sma

View File

@@ -540,6 +540,7 @@ ta.bbands,common_ta_volatility_model.bbands,common_ta_volatility_view.display_bb
ta.cci,common_ta_momentum_model.cci,common_ta_momentum_view.display_cci
ta.cg,common_ta_momentum_model.cg,common_ta_momentum_view.display_cg
ta.clenow,common_ta_momentum_model.clenow_momentum,common_ta_momentum_view.display_clenow_momentum
ta.cones,common_ta_volatility_model.cones,common_ta_volatility_view.display_cones
ta.demark,common_ta_momentum_model.demark_seq,common_ta_momentum_view.display_demark
ta.donchian,common_ta_volatility_model.donchian,common_ta_volatility_view.display_donchian
ta.ema,common_ta_overlap_model.ema,
@@ -551,6 +552,12 @@ ta.ma,common_ta_overlap_view.view_ma,common_ta_overlap_view.view_ma
ta.macd,common_ta_momentum_model.macd,common_ta_momentum_view.display_macd
ta.obv,common_ta_volume_model.obv,common_ta_volume_view.display_obv
ta.rsi,common_ta_momentum_model.rsi,common_ta_momentum_view.display_rsi
ta.rvol_garman_klass,common_ta_volatility_model.garman_klass,
ta.rvol_hodges_tompkins,common_ta_volatility_model.rvol_hodges_tompkins,
ta.rvol_parkinson,common_ta_volatility_model.rvol_parkinson,
ta.rvol_rogers_satchell,common_ta_volatility_model.rvol_rogers_satchell,
ta.rvol_std,common_ta_volatility_model.rvol_standard_deviation,
ta.rvol_yang_zhang,common_ta_volatility_model.rvol_yang_zhang,
ta.sma,common_ta_overlap_model.sma,
ta.stoch,common_ta_momentum_model.stoch,common_ta_momentum_view.display_stoch
ta.vwap,common_ta_overlap_model.vwap,common_ta_overlap_view.view_vwap
1 trail model view
540 ta.cci common_ta_momentum_model.cci common_ta_momentum_view.display_cci
541 ta.cg common_ta_momentum_model.cg common_ta_momentum_view.display_cg
542 ta.clenow common_ta_momentum_model.clenow_momentum common_ta_momentum_view.display_clenow_momentum
543 ta.cones common_ta_volatility_model.cones common_ta_volatility_view.display_cones
544 ta.demark common_ta_momentum_model.demark_seq common_ta_momentum_view.display_demark
545 ta.donchian common_ta_volatility_model.donchian common_ta_volatility_view.display_donchian
546 ta.ema common_ta_overlap_model.ema
552 ta.macd common_ta_momentum_model.macd common_ta_momentum_view.display_macd
553 ta.obv common_ta_volume_model.obv common_ta_volume_view.display_obv
554 ta.rsi common_ta_momentum_model.rsi common_ta_momentum_view.display_rsi
555 ta.rvol_garman_klass common_ta_volatility_model.garman_klass
556 ta.rvol_hodges_tompkins common_ta_volatility_model.rvol_hodges_tompkins
557 ta.rvol_parkinson common_ta_volatility_model.rvol_parkinson
558 ta.rvol_rogers_satchell common_ta_volatility_model.rvol_rogers_satchell
559 ta.rvol_std common_ta_volatility_model.rvol_standard_deviation
560 ta.rvol_yang_zhang common_ta_volatility_model.rvol_yang_zhang
561 ta.sma common_ta_overlap_model.sma
562 ta.stoch common_ta_momentum_model.stoch common_ta_momentum_view.display_stoch
563 ta.vwap common_ta_overlap_model.vwap common_ta_overlap_view.view_vwap