Refactor options menu

This commit is contained in:
didier
2021-04-14 02:01:57 +01:00
parent af221e0a20
commit 32efa688cf
4 changed files with 298 additions and 198 deletions

View File

@@ -9,7 +9,7 @@ import yfinance as yf
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.options import volume
from gamestonk_terminal.options import volume_view
from gamestonk_terminal.menu import session
@@ -17,16 +17,15 @@ class OptionsController:
"""Options Controller class."""
# Command choices
CHOICES = ["help", "q", "quit", "exp", "volume"]
CHOICES = ["help", "q", "quit", "exp", "voi"]
def __init__(
self,
ticker: str,
):
def __init__(self, ticker: str, last_adj_close_price: float):
"""Construct data."""
self.ticker = ticker
self.raw_data_options = yf.Ticker(self.ticker)
self.expiry_date = self.raw_data_options.options[0]
self.yf_ticker_data = yf.Ticker(self.ticker)
self.expiry_date = self.yf_ticker_data.options[0]
self.options = self.yf_ticker_data.option_chain(self.expiry_date)
self.last_adj_close_price = last_adj_close_price
self.op_parser = argparse.ArgumentParser(add_help=False, prog="op")
self.op_parser.add_argument(
"cmd",
@@ -48,7 +47,7 @@ class OptionsController:
action="store",
type=int,
default=-1,
choices=range(len(self.raw_data_options.options)),
choices=range(len(self.yf_ticker_data.options)),
help=f"Expiry date index for {self.ticker}.",
)
@@ -64,12 +63,13 @@ class OptionsController:
# Print possible expiry dates
if ns_parser.n_date == -1:
print("\nAvailable expiry dates:")
for i, d in enumerate(self.raw_data_options.options):
for i, d in enumerate(self.yf_ticker_data.options):
print(f" {(2-len(str(i)))*' '}{i}. {d}")
# It means an expiry date was correctly selected
else:
self.expiry_date = self.raw_data_options.options[ns_parser.n_date]
self.expiry_date = self.yf_ticker_data.options[ns_parser.n_date]
self.options = self.yf_ticker_data.option_chain(self.expiry_date)
print(f"\nSelected expiry date : {self.expiry_date}")
except Exception as e:
@@ -89,7 +89,7 @@ class OptionsController:
print(f"Selected expiry date: {expiry_date}")
print("")
print(" exp see/set expiry date")
print(" volume plot options trading volume / open interest")
print(" voi volume + open interest options trading plot")
print("")
return
@@ -121,26 +121,27 @@ class OptionsController:
"""Process Quit command - quit the program."""
return True
def call_volume(self, _):
"""Process volume command."""
volume.volume_graph(
self.raw_data_options,
def call_voi(self, other_args: List[str]):
"""Process voi command."""
volume_view.volume_open_interest_graph(
other_args,
self.ticker,
self.expiry_date,
volume_percentile_threshold=60,
self.last_adj_close_price,
self.options.calls,
self.options.puts,
)
print("")
def call_exp(self, other_args: List[str]):
"""Process exp command."""
self.expiry_dates(self, other_args)
def menu(ticker: str):
"""Options info Menu."""
def menu(ticker: str, last_adj_close_price: float):
""" Options Menu. """
try:
op_controller = OptionsController(ticker)
op_controller = OptionsController(ticker, last_adj_close_price)
op_controller.call_help(None)
except IndexError:
print("No options found for " + ticker)

View File

@@ -1,176 +0,0 @@
"""options info. [Source: Yahoo Finance]."""
from bisect import bisect_left
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from gamestonk_terminal.helper_funcs import plot_autoscale
from gamestonk_terminal import config_plot as cfgPlot
def volume_graph(raw_data, ticker_name, exp_date, volume_percentile_threshold=50):
"""Docstring make linter hap."""
# SET VOLUME TO BE FILTERED, default = 50
PERCENTILE_THRESHOLD = volume_percentile_threshold
TICKER_NAME = ticker_name
raw_data_options = raw_data
EXP_DATE = exp_date
# current stock price
spot = __get_current_spot(raw_data_options)
calls = __parse_opt_data(raw_data_options, EXP_DATE)
puts = __parse_opt_data(raw_data_options, EXP_DATE, is_calls=False)
calls = __add_max_pain_data(calls, spot)
puts = __add_max_pain_data(puts, spot, is_calls=False)
max_pain = __calc_max_pain(calls, puts)
# Initialize the matplotlib figure
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
# _, ax = plt.subplots(figsize=(12, 10))
# make x axis symmetric
axis_origin = max(abs(max(puts["oi+v"])), abs(max(calls["oi+v"])))
ax.set_xlim(-axis_origin, +axis_origin)
VOLUME_THRESHOLD = np.percentile(calls["oi+v"], PERCENTILE_THRESHOLD)
sns.set_style(style="darkgrid")
g = sns.barplot(
x="oi+v",
y="strike",
data=calls[calls["oi+v"] > VOLUME_THRESHOLD],
label="Calls: Open Interest",
color="lightgreen",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=calls[calls["oi+v"] > VOLUME_THRESHOLD],
label="Calls: Volume",
color="green",
orient="h",
)
g = sns.barplot(
x="oi+v",
y="strike",
data=puts[puts["oi+v"] < -VOLUME_THRESHOLD],
label="Puts: Open Interest",
color="pink",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=puts[puts["oi+v"] < -VOLUME_THRESHOLD],
label="Puts: Volume",
color="red",
orient="h",
)
# draw spot line
s = [float(strike.get_text()) for strike in ax.get_yticklabels()]
spot_index = bisect_left(s, spot) # find where the spot is on the graph
spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3)
# draw max pain line
max_pain_index = bisect_left(s, max_pain)
max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3)
max_pain_line.set_linewidth(5)
# ax.axhline(max_pain_index, ls='--')
# format ticklabels without - for puts
g.set_xticks(g.get_xticks())
xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()]
g.set_xticklabels(xlabels)
plt.title(
f"{TICKER_NAME.upper()} volumes for {EXP_DATE} (open interest displayed only during market hours)"
)
ax.invert_yaxis()
# ax.spines['left'].set_position('center')
_ = ax.legend()
handles, _ = ax.get_legend_handles_labels()
handles.append(spot_line)
handles.append(max_pain_line)
# create legend labels + add to graph
labels = [
"Calls open interest",
"Calls volume ",
"Puts open interest",
"Puts volume",
"Current stock price",
f"Max pain = {max_pain}",
]
plt.legend(handles=handles[:], labels=labels)
sns.despine(left=True, bottom=True)
plt.show()
def __add_max_pain_data(df, spot, is_calls=True):
# max pain parsing + calculation
df["spot"] = round(spot, 2)
if is_calls:
df["dv"] = spot - df.index
else:
df["dv"] = df.index - spot
df["dv"] = df["dv"].apply(lambda x: max(0, x))
df["dv"] = abs(df["dv"] * df["volume"])
return df
def __calc_max_pain(calls, puts):
df = pd.merge(calls, puts, left_index=True, right_index=True)
df["dv"] = round(df["dv_x"] + df["dv_y"], 2)
max_pain = df["dv"].idxmax()
return max_pain
def __parse_opt_data(raw_data_options, exp_date, is_calls=True):
# get option chain for specific expiration
opt = raw_data_options.option_chain(exp_date)
# PARSE DATA
if is_calls:
option_data = opt.calls
flag = "calls"
else:
option_data = opt.puts
flag = "puts"
data = option_data.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
data["strike"] = data.index
data["type"] = flag
if is_calls:
data["openInterest"] = data["openInterest"]
data["volume"] = data["volume"]
else:
data["openInterest"] = -data["openInterest"]
data["volume"] = -data["volume"]
data["oi+v"] = data["openInterest"] + data["volume"]
return data
def __get_current_spot(raw_data_options):
return raw_data_options.history().tail(1)["Close"].iloc[0]

View File

@@ -0,0 +1,273 @@
""" Volume view """
__docformat__ = "numpy"
from bisect import bisect_left
import seaborn as sns
from typing import List
import argparse
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import yfinance
from gamestonk_terminal.helper_funcs import (
plot_autoscale,
check_non_negative,
parse_known_args_and_warn,
)
from gamestonk_terminal import config_plot as cfgPlot
from gamestonk_terminal import feature_flags as gtff
def volume_open_interest_graph(
other_args: List[str],
ticker: str,
exp_date: str,
last_adj_close_price: float,
op_calls: pd.DataFrame,
op_puts: pd.DataFrame,
):
"""Options volume and open interest
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
ticker : str
Main ticker to compare income
exp_date : str
Expiry date of the option
last_adj_close_price: float
Last adjusted closing price
op_calls: pd.DataFrame
Option data calls
op_puts: pd.DataFrame
Option data puts
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="voi",
description="""
Plots Volume + Open Interest of calls vs puts. [Source: Yahoo Finance]
""",
)
parser.add_argument(
"-v",
"--minv",
dest="min_vol",
type=check_non_negative,
default=-1,
help="minimum volume (considering open interest) threshold of the plot.",
)
parser.add_argument(
"-m",
"--min",
dest="min_sp",
type=check_non_negative,
default=-1,
help="minimum strike price to consider in the plot.",
)
parser.add_argument(
"-M",
"--max",
dest="max_sp",
type=check_non_negative,
default=-1,
help="maximum strike price to consider in the plot.",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
df_calls, df_puts, max_pain = get_calls_puts_maxpain(
op_calls, op_puts, last_adj_close_price
)
if (
ns_parser.min_vol == -1
and ns_parser.min_sp == -1
and ns_parser.max_sp == -1
):
# If no argument provided, we use the percentile 50 to get 50% of upper volume data
volume_percentile_threshold = 50
min_vol_calls = np.percentile(df_calls["oi+v"], volume_percentile_threshold)
min_vol_puts = np.percentile(df_puts["oi+v"], volume_percentile_threshold)
df_calls = df_calls[df_calls["oi+v"] > min_vol_calls]
df_puts = df_puts[df_puts["oi+v"] < min_vol_puts]
else:
if ns_parser.min_vol > -1:
df_calls = df_calls[df_calls["oi+v"] > ns_parser.min_vol]
df_puts = df_puts[df_puts["oi+v"] < -ns_parser.min_vol]
if ns_parser.min_sp > -1:
df_calls = df_calls[df_calls["strike"] > ns_parser.min_sp]
df_puts = df_puts[df_puts["strike"] > ns_parser.min_sp]
if ns_parser.max_sp > -1:
df_calls = df_calls[df_calls["strike"] < ns_parser.max_sp]
df_puts = df_puts[df_puts["strike"] < ns_parser.max_sp]
if df_calls.empty and df_puts.empty:
print(
"The filtering applied is too strong, there is no data available for such conditions.\n"
)
return
# Initialize the matplotlib figure
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
# make x axis symmetric
axis_origin = max(abs(max(df_puts["oi+v"])), abs(max(df_calls["oi+v"])))
ax.set_xlim(-axis_origin, +axis_origin)
sns.set_style(style="darkgrid")
g = sns.barplot(
x="oi+v",
y="strike",
data=df_calls,
label="Calls: Open Interest",
color="lightgreen",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=df_calls,
label="Calls: Volume",
color="green",
orient="h",
)
g = sns.barplot(
x="oi+v",
y="strike",
data=df_puts,
label="Puts: Open Interest",
color="pink",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=df_puts,
label="Puts: Volume",
color="red",
orient="h",
)
# draw spot line
s = [float(strike.get_text()) for strike in ax.get_yticklabels()]
spot_index = bisect_left(
s, last_adj_close_price
) # find where the spot is on the graph
spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3)
# draw max pain line
max_pain_index = bisect_left(s, max_pain)
max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3)
max_pain_line.set_linewidth(5)
# format ticklabels without - for puts
g.set_xticks(g.get_xticks())
xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()]
g.set_xticklabels(xlabels)
plt.title(
f"{ticker} volumes for {exp_date} (open interest displayed only during market hours)"
)
ax.invert_yaxis()
_ = ax.legend()
handles, _ = ax.get_legend_handles_labels()
handles.append(spot_line)
handles.append(max_pain_line)
# create legend labels + add to graph
labels = [
"Calls open interest",
"Calls volume ",
"Puts open interest",
"Puts volume",
"Current stock price",
f"Max pain = {max_pain}",
]
plt.legend(handles=handles[:], labels=labels)
sns.despine(left=True, bottom=True)
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e, "\n")
return
def get_calls_puts_maxpain(
op_calls: pd.DataFrame, op_puts: pd.DataFrame, last_adj_close_price: float
):
"""Get calls and puts dataframes, and max pain
Parameters
----------
op_calls: pd.DataFrame
Option data calls
op_puts: pd.DataFrame
Option data puts
last_adj_close_price: float
Last adjusted closing price
Returns
----------
pd.DataFrame
Processed calls dataframe
pd.DataFrame
Processed puts dataframe
float
Max pain
"""
# Process Calls Data
df_calls = op_calls.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
df_calls["strike"] = df_calls.index
df_calls["type"] = "calls"
df_calls["openInterest"] = df_calls["openInterest"]
df_calls["volume"] = df_calls["volume"]
df_calls["oi+v"] = df_calls["openInterest"] + df_calls["volume"]
df_calls["spot"] = round(last_adj_close_price, 2)
df_calls["dv"] = last_adj_close_price - df_calls.index
df_calls["dv"] = df_calls["dv"].apply(lambda x: max(0, x))
df_calls["dv"] = abs(df_calls["dv"] * df_calls["volume"])
# Process Puts Data
df_puts = op_puts.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
df_puts["strike"] = df_puts.index
df_puts["type"] = "puts"
df_puts["openInterest"] = df_puts["openInterest"]
df_puts["volume"] = -df_puts["volume"]
df_puts["openInterest"] = -df_puts["openInterest"]
df_puts["oi+v"] = df_puts["openInterest"] + df_puts["volume"]
df_puts["spot"] = round(last_adj_close_price, 2)
df_puts["dv"] = df_puts.index - last_adj_close_price
df_puts["dv"] = df_puts["dv"].apply(lambda x: max(0, x))
df_puts["dv"] = abs(df_puts["dv"] * df_puts["volume"])
# Get max pain
df_opt = pd.merge(df_calls, df_puts, left_index=True, right_index=True)
df_opt["dv"] = round(df_opt["dv_x"] + df_opt["dv_y"], 2)
max_pain = df_opt["dv"].idxmax()
return df_calls, df_puts, max_pain

View File

@@ -231,7 +231,9 @@ def main():
b_quit = eda_controller.menu(df_stock, s_ticker, s_start, s_interval)
elif ns_known_args.opt == "op":
b_quit = op_controller.menu(s_ticker)
b_quit = op_controller.menu(
s_ticker, df_stock["5. adjusted close"].values[-1]
)
elif ns_known_args.opt == "fred":
b_quit = fred_controller.menu()