mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-12 20:31:18 +00:00
188
gamestonk_terminal/options/op_controller.py
Normal file
188
gamestonk_terminal/options/op_controller.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""Options Controller Module."""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import yfinance as yf
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
from gamestonk_terminal.helper_funcs import get_flair
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.options import volume
|
||||
from gamestonk_terminal.menu import session
|
||||
|
||||
|
||||
class OptionsController:
|
||||
"""Options Controller class."""
|
||||
|
||||
# Command choices
|
||||
CHOICES = ["help", "q", "quit", "exp", "volume"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ticker: str,
|
||||
):
|
||||
"""Construct data."""
|
||||
self.ticker = ticker
|
||||
self.raw_data_options = yf.Ticker(self.ticker)
|
||||
self.expiry_date = ""
|
||||
|
||||
self.__set_exp_date()
|
||||
|
||||
self.op_parser = argparse.ArgumentParser(add_help=False, prog="ca")
|
||||
self.op_parser.add_argument(
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
|
||||
def __set_exp_date(self, date_index=0):
|
||||
self.expiry_date = self.raw_data_options.options[date_index]
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def print_exp_dates(expiry_date, all_dates):
|
||||
"""Print all available expiry dates."""
|
||||
print(f"Current selected expiry date: [{expiry_date}]")
|
||||
print("\nAvailable dates:\n")
|
||||
for i, d in enumerate(all_dates):
|
||||
if len(str(i)) == 1:
|
||||
spacer = " "
|
||||
else:
|
||||
spacer = ""
|
||||
print(f" [{i}] {spacer}{d}")
|
||||
print(
|
||||
f"\nPlease select date: input a number between 0 and {len(all_dates)-1}\n"
|
||||
)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def print_help(expiry_date):
|
||||
"""Print help."""
|
||||
print("\nOptions insight Mode:")
|
||||
print(" help show this menu again")
|
||||
print(" q quit this menu, and shows back to main menu")
|
||||
print(" quit quit to abandon program")
|
||||
print("")
|
||||
|
||||
print(
|
||||
f" exp set expiry date, current selected date : [{expiry_date}]"
|
||||
)
|
||||
print(" volume plot options trading volume / open interest")
|
||||
print("")
|
||||
return
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input.
|
||||
|
||||
Returns
|
||||
-------
|
||||
True, False or None
|
||||
False - quit the menu
|
||||
True - quit the program
|
||||
None - continue in the menu
|
||||
"""
|
||||
(known_args, other_args) = self.op_parser.parse_known_args(an_input.split())
|
||||
|
||||
return getattr(
|
||||
self, "call_" + known_args.cmd, lambda: "Command not recognized!"
|
||||
)(other_args)
|
||||
|
||||
def call_help(self, _):
|
||||
"""Process Help command."""
|
||||
self.print_help(self.expiry_date)
|
||||
|
||||
def call_q(self, _):
|
||||
"""Process Q command - quit the menu."""
|
||||
return False
|
||||
|
||||
def call_quit(self, _):
|
||||
"""Process Quit command - quit the program."""
|
||||
return True
|
||||
|
||||
def call_volume(self, _):
|
||||
"""Process volume command."""
|
||||
volume.volume_graph(
|
||||
self.raw_data_options,
|
||||
self.ticker,
|
||||
self.expiry_date,
|
||||
volume_percentile_threshold=60,
|
||||
)
|
||||
print("")
|
||||
|
||||
def call_exp(self, _):
|
||||
"""Process exp command."""
|
||||
self.print_exp_dates(self.expiry_date, self.raw_data_options.options)
|
||||
|
||||
date_index_choices = list(range(len(self.raw_data_options.options) - 1))
|
||||
while True:
|
||||
# Get input command from user
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
completer = NestedCompleter.from_nested_dict(
|
||||
{c: None for c in date_index_choices}
|
||||
)
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} (exp)> ",
|
||||
completer=completer,
|
||||
)
|
||||
else:
|
||||
an_input = input(f"{get_flair()} (exp)> ")
|
||||
|
||||
try:
|
||||
plt.close("all")
|
||||
|
||||
process_input = an_input
|
||||
|
||||
if process_input is not None:
|
||||
try:
|
||||
self.__set_exp_date(int(an_input))
|
||||
print(f"\nNew selected expiry date : [{self.expiry_date}]\n")
|
||||
return
|
||||
# menu(self.ticker, self)
|
||||
except IndexError:
|
||||
print(
|
||||
f"\nSelection must be between [0 - {len(self.raw_data_options.options) - 1}]\n"
|
||||
)
|
||||
continue
|
||||
except ValueError:
|
||||
print("The command selected doesn't exist\n")
|
||||
self.print_exp_dates(
|
||||
self.expiry_date, self.raw_data_options.options
|
||||
)
|
||||
|
||||
except SystemExit:
|
||||
print("The command selected doesn't exist\n")
|
||||
continue
|
||||
|
||||
|
||||
def menu(ticker: str, op_con=None):
|
||||
"""Options info Menu."""
|
||||
if op_con is None:
|
||||
op_controller = OptionsController(ticker)
|
||||
else:
|
||||
op_controller = op_con
|
||||
op_controller.call_help("")
|
||||
|
||||
while True:
|
||||
# Get input command from user
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
completer = NestedCompleter.from_nested_dict(
|
||||
{c: None for c in op_controller.CHOICES}
|
||||
)
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} (op)> ",
|
||||
completer=completer,
|
||||
)
|
||||
else:
|
||||
an_input = input(f"{get_flair()} (op)> ")
|
||||
|
||||
try:
|
||||
plt.close("all")
|
||||
|
||||
process_input = op_controller.switch(an_input)
|
||||
|
||||
if process_input is not None:
|
||||
return process_input
|
||||
|
||||
except SystemExit:
|
||||
print("The command selected doesn't exist\n")
|
||||
continue
|
||||
@@ -1,59 +0,0 @@
|
||||
import argparse
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import get_flair
|
||||
from gamestonk_terminal.options import volume as vol
|
||||
from gamestonk_terminal.menu import session
|
||||
|
||||
|
||||
def opt_menu(s_ticker):
|
||||
|
||||
# Add list of arguments that the options parser accepts
|
||||
opt_parser = argparse.ArgumentParser(prog="opt", add_help=False)
|
||||
choices = ["help", "q", "quit", "volume", "oi"]
|
||||
opt_parser.add_argument("cmd", choices=choices)
|
||||
completer = NestedCompleter.from_nested_dict({c: None for c in choices})
|
||||
|
||||
# print_options(s_ticker, s_start, s_interval)
|
||||
|
||||
# Loop forever and ever
|
||||
while True:
|
||||
# Get input command from user
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
as_input = session.prompt(
|
||||
f"{get_flair()} (opt)> ",
|
||||
completer=completer,
|
||||
)
|
||||
else:
|
||||
as_input = input(f"{get_flair()} (opt)> ")
|
||||
|
||||
# Parse fundamental analysis command of the list of possible commands
|
||||
try:
|
||||
(ns_known_args, l_args) = opt_parser.parse_known_args(as_input.split())
|
||||
|
||||
except SystemExit:
|
||||
print("The command selected doesn't exist\n")
|
||||
continue
|
||||
|
||||
if ns_known_args.cmd == "help":
|
||||
pass
|
||||
# print_options(s_ticker)
|
||||
|
||||
elif ns_known_args.cmd == "q":
|
||||
# Just leave the options menu
|
||||
return False
|
||||
|
||||
elif ns_known_args.cmd == "quit":
|
||||
# Abandon the program
|
||||
return True
|
||||
|
||||
elif ns_known_args.cmd == "volume":
|
||||
# call the volume graph
|
||||
vol.volume_graph(l_args, s_ticker)
|
||||
|
||||
elif ns_known_args.cmd == "oi":
|
||||
# call the volume graph
|
||||
vol.open_interest_graph(l_args, s_ticker)
|
||||
else:
|
||||
print("Command not recognized!")
|
||||
@@ -1,144 +1,176 @@
|
||||
import argparse
|
||||
import yfinance as yf
|
||||
"""options info. [Source: Yahoo Finance]."""
|
||||
from bisect import bisect_left
|
||||
import seaborn as sns
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import plotly.express as px
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
parse_known_args_and_warn,
|
||||
valid_date,
|
||||
)
|
||||
import numpy as np
|
||||
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal import config_plot as cfgPlot
|
||||
|
||||
|
||||
def open_interest_graph(l_args, s_ticker):
|
||||
""" Show traded options volume. [Source: Yahoo Finance] """
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="volume",
|
||||
description="""Display volume graph for a date. [Source: Yahoo Finance].""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--expiry",
|
||||
type=valid_date,
|
||||
dest="s_expiry_date",
|
||||
help="The expiry date (format YYYY-MM-DD) for the option chain",
|
||||
required=True,
|
||||
)
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, l_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
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
|
||||
|
||||
__get_open_interest_graph(
|
||||
s_ticker, ns_parser.s_expiry_date.strftime("%Y-%m-%d")
|
||||
)
|
||||
TICKER_NAME = ticker_name
|
||||
raw_data_options = raw_data
|
||||
EXP_DATE = exp_date
|
||||
|
||||
except SystemExit:
|
||||
print("")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
# 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)
|
||||
|
||||
def volume_graph(l_args, s_ticker):
|
||||
""" Show traded options volume. [Source: Yahoo Finance] """
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="volume",
|
||||
description="""Display volume graph for a date. [Source: Yahoo Finance].""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--expiry",
|
||||
type=valid_date,
|
||||
dest="s_expiry_date",
|
||||
help="The expiry date (format YYYY-MM-DD) for the option chain",
|
||||
required=True,
|
||||
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",
|
||||
)
|
||||
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, l_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
__get_volume_graph(s_ticker, ns_parser.s_expiry_date.strftime("%Y-%m-%d"))
|
||||
|
||||
except SystemExit:
|
||||
print("")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def __get_open_interest_graph(ticker_name, exp_date):
|
||||
df = __get_volume_data(ticker_name, exp_date)
|
||||
__generate_graph_sns(df, ticker_name, exp_date, True)
|
||||
# __generate_graph_plotly(df, ticker_name, exp_date)
|
||||
|
||||
|
||||
def __get_volume_graph(ticker_name, exp_date):
|
||||
df = __get_volume_data(ticker_name, exp_date)
|
||||
__generate_graph_sns(df, ticker_name, exp_date)
|
||||
# __generate_graph_plotly(df, ticker_name, exp_date)
|
||||
|
||||
|
||||
def __pull_call_put_data(call_put, flag):
|
||||
df = call_put.pivot_table(
|
||||
index="strike", values=["volume", "openInterest"], aggfunc="sum"
|
||||
g = sns.barplot(
|
||||
x="volume",
|
||||
y="strike",
|
||||
data=calls[calls["oi+v"] > VOLUME_THRESHOLD],
|
||||
label="Calls: Volume",
|
||||
color="green",
|
||||
orient="h",
|
||||
)
|
||||
|
||||
df.reindex()
|
||||
g = sns.barplot(
|
||||
x="oi+v",
|
||||
y="strike",
|
||||
data=puts[puts["oi+v"] < -VOLUME_THRESHOLD],
|
||||
label="Puts: Open Interest",
|
||||
color="pink",
|
||||
orient="h",
|
||||
)
|
||||
|
||||
df["strike"] = df.index
|
||||
df["type"] = flag
|
||||
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 __get_volume_data(ticker_name, exp_date):
|
||||
def __calc_max_pain(calls, puts):
|
||||
|
||||
option_chain = yf.Ticker(ticker_name).option_chain(exp_date)
|
||||
df = pd.merge(calls, puts, left_index=True, right_index=True)
|
||||
df["dv"] = round(df["dv_x"] + df["dv_y"], 2)
|
||||
|
||||
calls = __pull_call_put_data(option_chain.calls, "calls")
|
||||
|
||||
puts = __pull_call_put_data(option_chain.puts, "puts")
|
||||
|
||||
volume_data = pd.concat([calls, puts], axis=0)
|
||||
# dataframe
|
||||
return volume_data
|
||||
max_pain = df["dv"].idxmax()
|
||||
return max_pain
|
||||
|
||||
|
||||
def __generate_graph_plotly(df, ticker_name, exp_date, for_open_interest=False):
|
||||
# version with plotly express
|
||||
if for_open_interest:
|
||||
op_type = "openInterest"
|
||||
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:
|
||||
op_type = "volume"
|
||||
option_data = opt.puts
|
||||
flag = "puts"
|
||||
|
||||
fig = px.line(
|
||||
df,
|
||||
x="strike",
|
||||
y=op_type,
|
||||
title=f"{ticker_name} options {op_type} for {exp_date}",
|
||||
color="type",
|
||||
)
|
||||
fig.show()
|
||||
data = option_data.pivot_table(
|
||||
index="strike", values=["volume", "openInterest"], aggfunc="sum"
|
||||
).reindex()
|
||||
|
||||
return
|
||||
data["strike"] = data.index
|
||||
data["type"] = flag
|
||||
|
||||
|
||||
def __generate_graph_sns(df, ticker_name, exp_date, for_open_interest=False):
|
||||
# version with seaborn express
|
||||
if for_open_interest:
|
||||
op_type = "openInterest"
|
||||
if is_calls:
|
||||
data["openInterest"] = data["openInterest"]
|
||||
data["volume"] = data["volume"]
|
||||
else:
|
||||
op_type = "volume"
|
||||
data["openInterest"] = -data["openInterest"]
|
||||
data["volume"] = -data["volume"]
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
fig = sns.lineplot(
|
||||
data=df, x="strike", y=op_type, hue="type", palette=["limegreen", "tomato"]
|
||||
)
|
||||
data["oi+v"] = data["openInterest"] + data["volume"]
|
||||
return data
|
||||
|
||||
plt.title(f"{ticker_name} options {op_type} for {exp_date}")
|
||||
|
||||
plt.show()
|
||||
return fig
|
||||
def __get_current_spot(raw_data_options):
|
||||
return raw_data_options.history().tail(1)["Close"].iloc[0]
|
||||
|
||||
@@ -23,7 +23,7 @@ from gamestonk_terminal.papermill import papermill_controller as mill
|
||||
from gamestonk_terminal.behavioural_analysis import ba_controller
|
||||
from gamestonk_terminal.technical_analysis import ta_menu as tam
|
||||
from gamestonk_terminal.comparison_analysis import ca_controller
|
||||
from gamestonk_terminal.options import op_menu as opm
|
||||
from gamestonk_terminal.options import op_controller
|
||||
from gamestonk_terminal.fred import fred_menu as fm
|
||||
from gamestonk_terminal.portfolio import port_controller
|
||||
|
||||
@@ -32,12 +32,13 @@ from gamestonk_terminal.portfolio import port_controller
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Gamestonk Terminal is an awesome stock market terminal that has been developed for fun,
|
||||
while I saw my GME shares tanking. But hey, I like the stock.
|
||||
"""
|
||||
|
||||
# Enable VT100 Escape Sequence for WINDOWS 10 Ver. 1607
|
||||
if sys.platform == "win32":
|
||||
os.system("")
|
||||
@@ -200,7 +201,7 @@ def main():
|
||||
b_quit = ddm.dd_menu(df_stock, s_ticker, s_start, s_interval)
|
||||
|
||||
elif ns_known_args.opt == "op":
|
||||
b_quit = opm.opt_menu(s_ticker)
|
||||
b_quit = op_controller.menu(s_ticker)
|
||||
|
||||
elif ns_known_args.opt == "fred":
|
||||
b_quit = fm.fred_menu()
|
||||
|
||||
Reference in New Issue
Block a user