From 32efa688cf5a005231ec4443cf3b527a7c2e36dc Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 14 Apr 2021 02:01:57 +0100 Subject: [PATCH 1/5] Refactor options menu --- gamestonk_terminal/options/op_controller.py | 43 +-- gamestonk_terminal/options/volume.py | 176 ------------- gamestonk_terminal/options/volume_view.py | 273 ++++++++++++++++++++ terminal.py | 4 +- 4 files changed, 298 insertions(+), 198 deletions(-) delete mode 100644 gamestonk_terminal/options/volume.py create mode 100644 gamestonk_terminal/options/volume_view.py diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py index 9459833b180..3600a50e2ba 100644 --- a/gamestonk_terminal/options/op_controller.py +++ b/gamestonk_terminal/options/op_controller.py @@ -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) diff --git a/gamestonk_terminal/options/volume.py b/gamestonk_terminal/options/volume.py deleted file mode 100644 index 3b87ac8efe7..00000000000 --- a/gamestonk_terminal/options/volume.py +++ /dev/null @@ -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] diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py new file mode 100644 index 00000000000..26db5219a27 --- /dev/null +++ b/gamestonk_terminal/options/volume_view.py @@ -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 diff --git a/terminal.py b/terminal.py index f2441ccbdab..cfcc432bb04 100644 --- a/terminal.py +++ b/terminal.py @@ -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() From e0e15abeba714ee285763ba006b9ccae2a6b033a Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 14 Apr 2021 02:04:14 +0100 Subject: [PATCH 2/5] remove unused import --- gamestonk_terminal/options/volume_view.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py index 26db5219a27..e4aadb946a3 100644 --- a/gamestonk_terminal/options/volume_view.py +++ b/gamestonk_terminal/options/volume_view.py @@ -8,7 +8,6 @@ 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, From ce0cc6a6588832bbac4830c963ee1519b56f4dd1 Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 14 Apr 2021 02:27:22 +0100 Subject: [PATCH 3/5] add vcalls and vputs commands to options menu --- gamestonk_terminal/options/op_controller.py | 30 ++- gamestonk_terminal/options/volume_view.py | 195 ++++++++++++++++---- 2 files changed, 188 insertions(+), 37 deletions(-) diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py index 3600a50e2ba..01eb7e0df67 100644 --- a/gamestonk_terminal/options/op_controller.py +++ b/gamestonk_terminal/options/op_controller.py @@ -17,7 +17,7 @@ class OptionsController: """Options Controller class.""" # Command choices - CHOICES = ["help", "q", "quit", "exp", "voi"] + CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs"] def __init__(self, ticker: str, last_adj_close_price: float): """Construct data.""" @@ -90,6 +90,8 @@ class OptionsController: print("") print(" exp see/set expiry date") print(" voi volume + open interest options trading plot") + print(" vcalls calls volume + open interest plot") + print(" vputs puts volume + open interest plot") print("") return @@ -121,9 +123,13 @@ class OptionsController: """Process Quit command - quit the program.""" return True + def call_exp(self, other_args: List[str]): + """Process exp command.""" + self.expiry_dates(self, other_args) + def call_voi(self, other_args: List[str]): """Process voi command.""" - volume_view.volume_open_interest_graph( + volume_view.plot_volume_open_interest( other_args, self.ticker, self.expiry_date, @@ -132,9 +138,23 @@ class OptionsController: self.options.puts, ) - def call_exp(self, other_args: List[str]): - """Process exp command.""" - self.expiry_dates(self, other_args) + def call_vcalls(self, other_args: List[str]): + """Process vcalls command.""" + volume_view.plot_calls_volume_open_interest( + other_args, + self.ticker, + self.last_adj_close_price, + self.options.calls, + ) + + def call_vputs(self, other_args: List[str]): + """Process vcalls command.""" + volume_view.plot_puts_volume_open_interest( + other_args, + self.ticker, + self.last_adj_close_price, + self.options.puts, + ) def menu(ticker: str, last_adj_close_price: float): diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py index e4aadb946a3..4a84471e04e 100644 --- a/gamestonk_terminal/options/volume_view.py +++ b/gamestonk_terminal/options/volume_view.py @@ -18,7 +18,7 @@ from gamestonk_terminal import config_plot as cfgPlot from gamestonk_terminal import feature_flags as gtff -def volume_open_interest_graph( +def plot_volume_open_interest( other_args: List[str], ticker: str, exp_date: str, @@ -235,38 +235,169 @@ def get_calls_puts_maxpain( 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"]) + df_calls = pd.DataFrame() + df_puts = pd.DataFrame() + max_pain = 0 - # 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"]) + if not op_calls.empty: + # 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"]) - # 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() + if not op_puts.empty: + # 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"]) + + if not op_calls.empty and not op_puts.empty: + # 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 + + +def plot_calls_volume_open_interest( + other_args: List[str], + ticker: str, + last_adj_close_price: float, + op_calls: 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 + last_adj_close_price: float + Last adjusted closing price + op_calls: pd.DataFrame + Option data calls + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="vcalls", + description=""" + Plots Calls Volume + Open Interest. [Source: Yahoo Finance] + """, + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_calls, _, _ = get_calls_puts_maxpain( + op_calls, pd.DataFrame(), last_adj_close_price + ) + + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + + plt.axvline(last_adj_close_price, lw=2) + plt.bar( + df_calls["strike"], + df_calls["volume"] + df_calls["openInterest"], + color="lightgreen", + ) + plt.bar(df_calls["strike"], df_calls["volume"], color="green") + + plt.title("Calls Volume") + plt.legend(["Stock Price", "Open Interest", "Volume"]) + plt.xlabel("Strike Price") + plt.ylabel("Volume") + + if gtff.USE_ION: + plt.ion() + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + return + + +def plot_puts_volume_open_interest( + other_args: List[str], + ticker: str, + last_adj_close_price: float, + 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 + last_adj_close_price: float + Last adjusted closing price + op_puts: pd.DataFrame + Option data puts + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="vputs", + description=""" + Plots Puts Volume + Open Interest. [Source: Yahoo Finance] + """, + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + _, df_puts, _ = get_calls_puts_maxpain( + pd.DataFrame(), op_puts, last_adj_close_price + ) + + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + + plt.axvline(last_adj_close_price, lw=2) + plt.bar( + df_puts["strike"], + -df_puts["volume"] - df_puts["openInterest"], + color="pink", + ) + plt.bar(df_puts["strike"], -df_puts["volume"], color="red") + + plt.title("Puts Volume") + plt.legend(["Stock Price", "Open Interest", "Volume"]) + plt.xlabel("Strike Price") + plt.ylabel("Volume") + + if gtff.USE_ION: + plt.ion() + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + return From 14feae913c2c76fa4744229ef7b56d4b3405a368 Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 14 Apr 2021 21:45:13 +0100 Subject: [PATCH 4/5] improve current options plots --- gamestonk_terminal/options/op_controller.py | 2 + gamestonk_terminal/options/volume_view.py | 64 ++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py index 01eb7e0df67..e373ba1f677 100644 --- a/gamestonk_terminal/options/op_controller.py +++ b/gamestonk_terminal/options/op_controller.py @@ -143,6 +143,7 @@ class OptionsController: volume_view.plot_calls_volume_open_interest( other_args, self.ticker, + self.expiry_date, self.last_adj_close_price, self.options.calls, ) @@ -152,6 +153,7 @@ class OptionsController: volume_view.plot_puts_volume_open_interest( other_args, self.ticker, + self.expiry_date, self.last_adj_close_price, self.options.puts, ) diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py index 4a84471e04e..fed8eb63c6f 100644 --- a/gamestonk_terminal/options/volume_view.py +++ b/gamestonk_terminal/options/volume_view.py @@ -282,6 +282,7 @@ def get_calls_puts_maxpain( def plot_calls_volume_open_interest( other_args: List[str], ticker: str, + exp_date: str, last_adj_close_price: float, op_calls: pd.DataFrame, ): @@ -293,6 +294,8 @@ def plot_calls_volume_open_interest( 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 @@ -305,6 +308,22 @@ def plot_calls_volume_open_interest( Plots Calls Volume + Open Interest. [Source: Yahoo Finance] """, ) + 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) @@ -315,6 +334,16 @@ def plot_calls_volume_open_interest( op_calls, pd.DataFrame(), last_adj_close_price ) + if ns_parser.min_sp == -1: + min_strike = np.percentile(df_calls["strike"], 25) + else: + min_strike = ns_parser.min_sp + + if ns_parser.max_sp == -1: + max_strike = np.percentile(df_calls["strike"], 75) + else: + max_strike = ns_parser.max_sp + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) plt.axvline(last_adj_close_price, lw=2) @@ -325,10 +354,11 @@ def plot_calls_volume_open_interest( ) plt.bar(df_calls["strike"], df_calls["volume"], color="green") - plt.title("Calls Volume") + plt.title(f"{ticker} calls volumes for {exp_date} ") plt.legend(["Stock Price", "Open Interest", "Volume"]) plt.xlabel("Strike Price") plt.ylabel("Volume") + plt.xlim([min_strike, max_strike]) if gtff.USE_ION: plt.ion() @@ -344,6 +374,7 @@ def plot_calls_volume_open_interest( def plot_puts_volume_open_interest( other_args: List[str], ticker: str, + exp_date: str, last_adj_close_price: float, op_puts: pd.DataFrame, ): @@ -355,6 +386,8 @@ def plot_puts_volume_open_interest( 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_puts: pd.DataFrame @@ -367,6 +400,22 @@ def plot_puts_volume_open_interest( Plots Puts Volume + Open Interest. [Source: Yahoo Finance] """, ) + 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) @@ -377,6 +426,16 @@ def plot_puts_volume_open_interest( pd.DataFrame(), op_puts, last_adj_close_price ) + if ns_parser.min_sp == -1: + min_strike = np.percentile(df_puts["strike"], 25) + else: + min_strike = ns_parser.min_sp + + if ns_parser.max_sp == -1: + max_strike = np.percentile(df_puts["strike"], 75) + else: + max_strike = ns_parser.max_sp + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) plt.axvline(last_adj_close_price, lw=2) @@ -387,10 +446,11 @@ def plot_puts_volume_open_interest( ) plt.bar(df_puts["strike"], -df_puts["volume"], color="red") - plt.title("Puts Volume") + plt.title(f"{ticker} puts volumes for {exp_date} ") plt.legend(["Stock Price", "Open Interest", "Volume"]) plt.xlabel("Strike Price") plt.ylabel("Volume") + plt.xlim([min_strike, max_strike]) if gtff.USE_ION: plt.ion() From 1b16cd18806a96813059c33ed56be53f81f1a64a Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 14 Apr 2021 23:13:43 +0100 Subject: [PATCH 5/5] improve vcalls and vputs default plots --- gamestonk_terminal/options/volume_view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py index fed8eb63c6f..a20e4cafc54 100644 --- a/gamestonk_terminal/options/volume_view.py +++ b/gamestonk_terminal/options/volume_view.py @@ -335,12 +335,12 @@ def plot_calls_volume_open_interest( ) if ns_parser.min_sp == -1: - min_strike = np.percentile(df_calls["strike"], 25) + min_strike = 0.75 * last_adj_close_price else: min_strike = ns_parser.min_sp if ns_parser.max_sp == -1: - max_strike = np.percentile(df_calls["strike"], 75) + max_strike = 1.25 * last_adj_close_price else: max_strike = ns_parser.max_sp @@ -427,12 +427,12 @@ def plot_puts_volume_open_interest( ) if ns_parser.min_sp == -1: - min_strike = np.percentile(df_puts["strike"], 25) + min_strike = 0.75 * last_adj_close_price else: min_strike = ns_parser.min_sp if ns_parser.max_sp == -1: - max_strike = np.percentile(df_puts["strike"], 75) + max_strike = 1.25 * last_adj_close_price else: max_strike = ns_parser.max_sp