def test_option_expirations_no_dates(mocker): # MOCK TICKER mocker.patch( target="gamestonk_terminal.stocks.options.yfinance_model.yf.Ticker", ) # MOCK OPTION mocker.patch( target= "gamestonk_terminal.stocks.options.yfinance_model.yf.Ticker.option", return_value=(), ) yfinance_model.option_expirations(ticker="PM")
def expiry_autocomp(inter: disnake.AppCmdInter, tickerr: str): file = open("ticker", "rb") data = pickle.load(file) file.close() print(data) dates = yfinance_model.option_expirations(data[-1]) return [dates for dates in dates][:24]
def __init__(self, ticker: str, queue: List[str] = None): """Constructor""" super().__init__(queue) self.ticker = ticker self.prices = pd.DataFrame(columns=["Price", "Chance"]) self.selected_date = "" self.chain = None if ticker: if TRADIER_TOKEN == "REPLACE_ME": # nosec console.print("Loaded expiry dates from Yahoo Finance") self.expiry_dates = yfinance_model.option_expirations( self.ticker) else: console.print("Loaded expiry dates from Tradier") self.expiry_dates = tradier_model.option_expirations( self.ticker) else: self.expiry_dates = [] if session and gtff.USE_PROMPT_TOOLKIT: choices: dict = {c: {} for c in self.controller_choices} choices["unu"]["-s"] = {c: {} for c in self.unu_sortby_choices} choices["pcr"] = {c: {} for c in self.pcr_length_choices} choices["disp"] = {c: {} for c in self.presets} choices["scr"] = {c: {} for c in self.presets} choices["grhist"]["-g"] = { c: {} for c in self.grhist_greeks_choices } choices["load"]["-s"] = {c: {} for c in self.load_source_choices} choices["load"]["--source"] = { c: {} for c in self.hist_source_choices } choices["load"]["-s"] = {c: {} for c in self.voi_source_choices} choices["plot"]["-x"] = {c: {} for c in self.plot_vars_choices} choices["plot"]["-y"] = {c: {} for c in self.plot_vars_choices} choices["plot"]["-c"] = {c: {} for c in self.plot_custom_choices} # This menu contains dynamic choices that may change during runtime self.choices = choices self.completer = NestedCompleter.from_nested_dict(choices)
def call_load(self, other_args: List[str]): """Process load command""" parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, prog="load", description="Load a ticker into option menu", ) parser.add_argument( "-t", "--ticker", action="store", dest="ticker", required="-h" not in other_args, help="Stock ticker", ) parser.add_argument( "-s", "--source", choices=self.load_source_choices, dest="source", default=None, help="Source to get option expirations from", ) if other_args and "-" not in other_args[0][0]: other_args.insert(0, "-t") ns_parser = parse_known_args_and_warn(parser, other_args) if ns_parser: self.ticker = ns_parser.ticker.upper() self.update_runtime_choices() if TRADIER_TOKEN == "REPLACE_ME" or ns_parser.source == "yf": # nosec self.expiry_dates = yfinance_model.option_expirations( self.ticker) else: self.expiry_dates = tradier_model.option_expirations( self.ticker) console.print("") if self.ticker and self.selected_date: self.chain = yfinance_model.get_option_chain( self.ticker, self.selected_date)
async def vol_command( ctx, ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options VOL""" try: # Debug if cfg.DEBUG: logger.debug("!stocks.opt.vol %s %s %s %s", ticker, expiry, min_sp, max_sp) # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) current_price = yfinance_model.get_price(ticker) if min_sp is None: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp is None: max_strike = 1.25 * current_price else: max_strike = max_sp calls = options.calls puts = options.puts call_v = calls.set_index("strike")["volume"] / 1000 put_v = puts.set_index("strike")["volume"] / 1000 plt.style.use("seaborn") fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) put_v.plot( x="strike", y="volume", label="Puts", ax=ax, marker="o", ls="-", c="r", ) call_v.plot( x="strike", y="volume", label="Calls", ax=ax, marker="o", ls="-", c="g", ) ax.axvline( current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7 ) ax.grid("on") ax.set_xlabel("Strike Price") ax.set_ylabel("Volume (1k) ") ax.set_xlim(min_strike, max_strike) ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}") plt.legend(loc=0) fig.tight_layout(pad=1) imagefile = "opt_vol.png" plt.savefig("opt_vol.png") image = discord.File(imagefile) if cfg.DEBUG: logger.debug("Image: %s", imagefile) title = f"Volume for {ticker.upper()} expiring {expiry}" embed = discord.Embed(title=title, colour=cfg.COLOR) embed.set_image(url="attachment://opt_vol.png") embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) os.remove("opt_vol.png") await ctx.send(embed=embed, file=image) except Exception as e: embed = discord.Embed( title="ERROR Options: Volume", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed)
def vol_command( ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options VOL""" # Debug if cfg.DEBUG: logger.debug("opt-vol %s %s %s %s", ticker, expiry, min_sp, max_sp) # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) current_price = yfinance_model.get_price(ticker) if min_sp is None: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp is None: max_strike = 1.25 * current_price else: max_strike = max_sp calls = options.calls puts = options.puts call_v = calls.set_index("strike")["volume"] / 1000 put_v = puts.set_index("strike")["volume"] / 1000 df_opt = pd.merge(put_v, call_v, left_index=True, right_index=True) dmax = df_opt.values.max() fig = go.Figure() fig.add_trace( go.Scatter( x=call_v.index, y=call_v.values, name="Calls", mode="lines+markers", line=dict(color="green", width=3), )) fig.add_trace( go.Scatter( x=put_v.index, y=put_v.values, name="Puts", mode="lines+markers", line=dict(color="red", width=3), )) fig.add_trace( go.Scatter( x=[current_price, current_price], y=[0, dmax], mode="lines", line=dict(color="gold", width=2), name="Current Price", )) fig.update_xaxes( range=[min_strike, max_strike], constrain="domain", ) fig.update_layout( margin=dict(l=0, r=0, t=60, b=20), template=cfg.PLT_SCAT_STYLE_TEMPLATE, title=f"Volume for {ticker.upper()} expiring {expiry}", title_x=0.5, legend_title="", xaxis_title="Strike", yaxis_title="Volume (1k)", xaxis=dict(rangeslider=dict(visible=False), ), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), dragmode="pan", ) config = dict({"scrollZoom": True}) imagefile = "opt_vol.png" # Check if interactive settings are enabled plt_link = "" if cfg.INTERACTIVE: html_ran = helpers.uuid_get() fig.write_html(f"in/vol_{html_ran}.html", config=config) plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vol_{html_ran}.html)" fig.update_layout( width=800, height=500, ) imagefile = helpers.image_border(imagefile, fig=fig) return { "title": f"Volume for {ticker.upper()} expiring {expiry}", "description": plt_link, "imagefile": imagefile, }
def chain_command( ticker: str = None, expiry: str = None, opt_type: str = None, min_sp: float = None, max_sp: float = None, ): """Show calls/puts for given ticker and expiration""" # Debug if cfg.DEBUG: logger.debug("opt-chain %s %s %s %s %s", ticker, expiry, opt_type, min_sp, max_sp) # Check for argument if not ticker: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, str(expiry)) calls_df = options.calls.fillna(0) puts_df = options.puts.fillna(0) column_map = { "openInterest": "oi", "volume": "vol", "impliedVolatility": "iv" } columns = [ "strike", "bid", "ask", "volume", "openInterest", "impliedVolatility", ] if opt_type == "Calls": df = calls_df[columns].rename(columns=column_map) if opt_type == "Puts": df = puts_df[columns].rename(columns=column_map) min_strike = np.percentile(df["strike"], 1) max_strike = np.percentile(df["strike"], 100) if min_sp: min_strike = min_sp if max_sp: max_strike = max_sp if min_sp > max_sp: # type: ignore min_sp, max_sp = max_strike, min_strike df = df[df["strike"] >= min_strike] df = df[df["strike"] <= max_strike] df["iv"] = pd.to_numeric(df["iv"].astype(float)) formats = {"iv": "{:.2f}"} for col, f in formats.items(): df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 df.set_index("strike", inplace=True) title = ( f"Stocks: {opt_type} Option Chain for {ticker.upper()} on {expiry} [yfinance]" ) embeds: list = [] # Output i, i2, end = 0, 0, 20 df_pg = [] embeds_img = [] dindex = len(df.index) while i < dindex: df_pg = df.iloc[i:end] df_pg.append(df_pg) fig = df2img.plot_dataframe( df_pg, fig_size=(1000, (40 + (40 * 20))), col_width=[3, 3, 3, 3], tbl_cells=dict(height=35, ), font=dict( family="Consolas", size=20, ), template="plotly_dark", paper_bgcolor="rgba(0, 0, 0, 0)", ) imagefile = save_image(f"opt-chain{i}.png", fig) uploaded_image = gst_imgur.upload_image(imagefile, title="something") image_link = uploaded_image.link embeds_img.append(f"{image_link}", ) embeds.append(disnake.Embed( title=title, colour=cfg.COLOR, ), ) i2 += 1 i += 20 end += 20 os.remove(imagefile) # Author/Footer for i in range(0, i2): embeds[i].set_author( name=cfg.AUTHOR_NAME, url=cfg.AUTHOR_URL, icon_url=cfg.AUTHOR_ICON_URL, ) embeds[i].set_footer( text=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) i = 0 for i in range(0, i2): embeds[i].set_image(url=embeds_img[i]) i += 1 embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") choices = [ disnake.SelectOption(label="Home", value="0", emoji="🟢"), ] return { "view": Menu, "title": title, "embed": embeds, "choices": choices, "embeds_img": embeds_img, }
async def oi_command( ctx, ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options OI""" try: # Debug print(f"!stocks.opt.oi {ticker} {expiry} {min_sp} {max_sp}") # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls puts = options.puts current_price = yfinance_model.get_price(ticker) min_strike = 0.75 * current_price max_strike = 1.95 * current_price if len(calls) > 40: min_strike = 0.75 * current_price max_strike = 1.25 * current_price if min_sp: min_strike = min_sp if max_sp: max_strike = max_sp call_oi = calls.set_index("strike")["openInterest"] / 1000 put_oi = puts.set_index("strike")["openInterest"] / 1000 df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) df_opt = df_opt.rename( columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} ) max_pain = op_helpers.calculate_max_pain(df_opt) fig = go.Figure() dmax = df_opt[["OI_call", "OI_put"]].values.max() dmin = df_opt[["OI_call", "OI_put"]].values.min() fig.add_trace( go.Scatter( x=df_opt.index, y=df_opt["OI_call"], name="Calls", mode="lines+markers", line=dict(color="green", width=3), ) ) fig.add_trace( go.Scatter( x=df_opt.index, y=df_opt["OI_put"], name="Puts", mode="lines+markers", line=dict(color="red", width=3), ) ) fig.add_trace( go.Scatter( x=[current_price, current_price], y=[dmin, dmax], mode="lines", line=dict(color="gold", width=2), name="Current Price", ) ) fig.add_trace( go.Scatter( x=[max_pain, max_pain], y=[dmin, dmax], mode="lines", line=dict(color="grey", width=3, dash="dash"), name=f"Max Pain: {max_pain}", ) ) fig.update_xaxes( range=[min_strike, max_strike], constrain="domain", ) fig.update_layout( margin=dict(l=0, r=0, t=60, b=20), template=cfg.PLT_SCAT_STYLE_TEMPLATE, title=f"Open Interest for {ticker.upper()} expiring {expiry}", title_x=0.5, legend_title="", xaxis_title="Strike", yaxis_title="Open Interest (1k)", xaxis=dict( rangeslider=dict(visible=False), ), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), dragmode="pan", ) config = dict({"scrollZoom": True}) imagefile = "opt_oi.png" # Check if interactive settings are enabled plt_link = "" if cfg.INTERACTIVE: html_ran = random.randint(69, 69420) fig.write_html(f"in/cci_{html_ran}.html", config=config) plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cci_{html_ran}.html)" fig.update_layout( width=800, height=500, ) fig.write_image(imagefile) img = Image.open(imagefile) im_bg = Image.open(cfg.IMG_BG) h = img.height + 240 w = img.width + 520 img = img.resize((w, h), Image.ANTIALIAS) x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) img = img.convert("RGB") im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) im_bg.save(imagefile, "PNG", quality=100) image = Image.open(imagefile) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) image = disnake.File(imagefile) if cfg.DEBUG: print(f"Image URL: {imagefile}") title = f"Open Interest for {ticker.upper()} expiring {expiry}" embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) embed.set_image(url=f"attachment://{imagefile}") embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) os.remove(imagefile) await ctx.send(embed=embed, file=image) except Exception as e: embed = disnake.Embed( title="ERROR Options: Open Interest", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=30.0)
def __init__(self, ticker: str, queue: List[str] = None): """Constructor""" self.op_parser = argparse.ArgumentParser(add_help=False, prog="op") self.op_parser.add_argument( "cmd", choices=self.CHOICES, ) self.completer: Union[None, NestedCompleter] = None if session and gtff.USE_PROMPT_TOOLKIT: self.choices: dict = {c: {} for c in self.CHOICES} self.choices["unu"]["-s"] = { c: {} for c in self.unu_sortby_choices } self.choices["pcr"] = {c: {} for c in self.pcr_length_choices} self.choices["disp"] = {c: {} for c in self.presets} self.choices["scr"] = {c: {} for c in self.presets} self.choices["grhist"]["-g"] = { c: {} for c in self.grhist_greeks_choices } self.choices["load"]["-s"] = { c: {} for c in self.load_source_choices } self.choices["load"]["--source"] = { c: {} for c in self.hist_source_choices } self.choices["load"]["-s"] = { c: {} for c in self.voi_source_choices } self.choices["plot"]["-x"] = { c: {} for c in self.plot_vars_choices } self.choices["plot"]["-y"] = { c: {} for c in self.plot_vars_choices } self.choices["plot"]["-c"] = { c: {} for c in self.plot_custom_choices } self.ticker = ticker self.prices = pd.DataFrame(columns=["Price", "Chance"]) self.selected_date = "" self.chain = None if ticker: if TRADIER_TOKEN == "REPLACE_ME": print("Loaded expiry dates from Yahoo Finance") self.expiry_dates = yfinance_model.option_expirations( self.ticker) else: print("Loaded expiry dates from Tradier") self.expiry_dates = tradier_model.option_expirations( self.ticker) else: self.expiry_dates = [] if queue: self.queue = queue else: self.queue = list()
def smile_command( ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options Smile""" # Debug if imps.DEBUG: logger.debug("opt smile %s %s %s %s", ticker, expiry, min_sp, max_sp) # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls.fillna(0.0) puts = options.puts.fillna(0.0) current_price = yfinance_model.get_price(ticker) min_strike = 0.60 * current_price max_strike = 1.95 * current_price if len(calls) > 40: min_strike = 0.60 * current_price max_strike = 1.50 * current_price if min_sp: min_strike = min_sp if max_sp: max_strike = max_sp fig = go.Figure() fig.add_trace( go.Scatter( x=calls["strike"], y=calls["impliedVolatility"].interpolate(method="nearest"), name="Calls", mode="lines+markers", marker=dict( color="#00ACFF", size=4.5, ), line=dict(color="#00ACFF", width=2, dash="dash"), )) fig.add_trace( go.Scatter( x=puts["strike"], y=puts["impliedVolatility"].interpolate(method="nearest"), name="Puts", mode="lines+markers", marker=dict( color="#e4003a", size=4.5, ), line=dict(color="#e4003a", width=2, dash="dash"), )) if imps.PLT_WATERMARK: fig.add_layout_image(imps.PLT_WATERMARK) fig.update_xaxes( range=[min_strike, max_strike], constrain="domain", ) fig.update_layout( margin=dict(l=20, r=0, t=60, b=20), template=imps.PLT_SCAT_STYLE_TEMPLATE, font=imps.PLT_FONT, title= f"<b>Implied Volatility vs. Strike for {ticker.upper()} expiring {expiry}</b>", title_x=0.5, legend_title="", xaxis_title="Strike", yaxis_title="Implied Volatility", yaxis=dict( side="right", fixedrange=False, nticks=20, ), xaxis=dict( rangeslider=dict(visible=False), nticks=20, ), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), dragmode="pan", ) imagefile = "opt_smile.png" # Check if interactive settings are enabled plt_link = "" if imps.INTERACTIVE: plt_link = imps.inter_chart(fig, imagefile, callback=False) fig.update_layout( width=800, height=500, ) imagefile = imps.image_border(imagefile, fig=fig) return { "title": f"Implied Volatility vs. Stirke for {ticker.upper()} expiring {expiry}", "description": plt_link, "imagefile": imagefile, }
async def vol_command( ctx, ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options VOL""" try: # Debug if cfg.DEBUG: print(f"opt-vol {ticker} {expiry} {min_sp} {max_sp}") # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) current_price = yfinance_model.get_price(ticker) if min_sp is None: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp is None: max_strike = 1.25 * current_price else: max_strike = max_sp calls = options.calls puts = options.puts call_v = calls.set_index("strike")["volume"] / 1000 put_v = puts.set_index("strike")["volume"] / 1000 df_opt = pd.merge(put_v, call_v, left_index=True, right_index=True) dmax = df_opt.values.max() fig = go.Figure() fig.add_trace( go.Scatter( x=call_v.index, y=call_v.values, name="Calls", mode="lines+markers", line=dict(color="green", width=3), )) fig.add_trace( go.Scatter( x=put_v.index, y=put_v.values, name="Puts", mode="lines+markers", line=dict(color="red", width=3), )) fig.add_trace( go.Scatter( x=[current_price, current_price], y=[0, dmax], mode="lines", line=dict(color="gold", width=2), name="Current Price", )) fig.update_xaxes( range=[min_strike, max_strike], constrain="domain", ) fig.update_layout( margin=dict(l=0, r=0, t=60, b=20), template=cfg.PLT_SCAT_STYLE_TEMPLATE, title=f"Volume for {ticker.upper()} expiring {expiry}", title_x=0.5, legend_title="", xaxis_title="Strike", yaxis_title="Volume (1k)", xaxis=dict(rangeslider=dict(visible=False), ), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), dragmode="pan", ) config = dict({"scrollZoom": True}) imagefile = "opt_vol.png" fig.write_image(imagefile) # Check if interactive settings are enabled plt_link = "" if cfg.INTERACTIVE: html_ran = random.randint(69, 69420) fig.write_html(f"in/vol_{html_ran}.html", config=config) plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vol_{html_ran}.html)" img = Image.open(imagefile) im_bg = Image.open(cfg.IMG_BG) h = img.height + 240 w = img.width + 520 # Paste fig onto background img and autocrop background img = img.resize((w, h), Image.ANTIALIAS) x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) img = img.convert("RGB") im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) im_bg.save(imagefile, "PNG", quality=100) image = Image.open(imagefile) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) image = discord.File(imagefile) if cfg.DEBUG: print(f"Image: {imagefile}") title = f"Volume for {ticker.upper()} expiring {expiry}" embed = discord.Embed(title=title, description=plt_link, colour=cfg.COLOR) embed.set_image(url=f"attachment://{imagefile}") embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) os.remove(imagefile) await ctx.send(embed=embed, file=image) except Exception as e: embed = discord.Embed( title="ERROR Options: Volume", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=30.0)
def chain_command( ticker: str = None, expiry: str = None, opt_type: str = None, min_sp: float = None, max_sp: float = None, ): """Show calls/puts for given ticker and expiration""" # Debug if imps.DEBUG: logger.debug("opt chain %s %s %s %s %s", ticker, expiry, opt_type, min_sp, max_sp) # Check for argument if not ticker: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, str(expiry)) calls_df = options.calls.fillna(0) puts_df = options.puts.fillna(0) column_map = { "openInterest": "oi", "volume": "vol", "impliedVolatility": "iv" } columns = [ "strike", "bid", "ask", "volume", "openInterest", "impliedVolatility", ] if opt_type == "Calls": df = calls_df[columns].rename(columns=column_map) if opt_type == "Puts": df = puts_df[columns].rename(columns=column_map) min_strike = np.percentile(df["strike"], 1) max_strike = np.percentile(df["strike"], 100) if min_sp: min_strike = min_sp if max_sp: max_strike = max_sp if min_sp > max_sp: # type: ignore min_sp, max_sp = max_strike, min_strike df = df[df["strike"] >= min_strike] df = df[df["strike"] <= max_strike] df["iv"] = pd.to_numeric(df["iv"].astype(float)) formats = {"iv": "{:.2f}"} for col, f in formats.items(): df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 df.columns = df.columns.str.capitalize() df.set_index("Strike", inplace=True) title = ( f"Stocks: {opt_type} Option Chain for {ticker.upper()} on\n{expiry} [yfinance]" ) embeds: list = [] # Output i, i2, end = 0, 0, 20 df_pg, embeds_img, images_list = [], [], [] while i < len(df.index): df_pg = df.iloc[i:end] df_pg.append(df_pg) fig = imps.plot_df( df_pg, fig_size=(570, (40 + (40 * 20))), col_width=[3.1, 3.1, 3.1, 3.5], tbl_header=imps.PLT_TBL_HEADER, tbl_cells=imps.PLT_TBL_CELLS, font=imps.PLT_TBL_FONT, row_fill_color=imps.PLT_TBL_ROW_COLORS, paper_bgcolor="rgba(0, 0, 0, 0)", ) fig.update_traces(cells=(dict(align=["center", "right"]))) imagefile = "opt-chain.png" imagefile = imps.save_image(imagefile, fig) if imps.IMAGES_URL or not imps.IMG_HOST_ACTIVE: image_link = imps.multi_image(imagefile) images_list.append(imagefile) else: image_link = imps.multi_image(imagefile) embeds_img.append(f"{image_link}", ) embeds.append(disnake.Embed( title=title, colour=imps.COLOR, ), ) i2 += 1 i += 20 end += 20 # Author/Footer for i in range(0, i2): embeds[i].set_author( name=imps.AUTHOR_NAME, url=imps.AUTHOR_URL, icon_url=imps.AUTHOR_ICON_URL, ) embeds[i].set_footer( text=imps.AUTHOR_NAME, icon_url=imps.AUTHOR_ICON_URL, ) i = 0 for i in range(0, i2): embeds[i].set_image(url=embeds_img[i]) i += 1 embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") choices = [ disnake.SelectOption(label="Home", value="0", emoji="🟢"), ] return { "view": imps.Menu, "title": title, "embed": embeds, "choices": choices, "embeds_img": embeds_img, "images_list": images_list, }
async def opt( self, ctx: discord.ext.commands.Context, ticker="", expiration="", strike="", put="", ): """Stocks Context - Shows Options Menu Run `!help OptionsCommands` to see the list of available commands. Returns ------- Sends a message to the discord user with the commands from the stocks/options context. The user can then select a reaction to trigger a command. """ logger.info("!stocks.opt %s %s %s %s", ticker, expiration, strike, put) async with ctx.typing(): await asyncio.sleep(0.2) if TRADIER_TOKEN == "REPLACE_ME": # nosec dates = yfinance_model.option_expirations(ticker) else: dates = tradier_model.option_expirations(ticker) index_dates = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] if not ticker: current = 0 text = ( "```0️⃣ !stocks.opt.unu```\n" "Provide a ticker and expiration date with this menu,\n" "\ne.g.\n!stocks.opt TSLA 0-9\n!stocks.opt TSLA 2021-06-04" ) if (ticker != "") and (expiration == ""): current = 1 text = ("```0️⃣ !stocks.opt.unu\n" f"1️⃣ !stocks.opt.exp {ticker}\n" f"2️⃣ !stocks.opt.iv {ticker}\n```") if expiration: current = 2 exp = int(expiration.replace("-", "")) if exp > 9 and (expiration not in dates) and (exp not in index_dates): call_arg = (strike, put) func_cmd = opt_command expiry = None await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg) return if exp in index_dates: expiration = dates[int(expiration)] hist = f"7️⃣ !stocks.opt.hist {ticker} (strike*) (c/p*) {expiration}\n\n* Required" if strike and put: hist = f"7️⃣ !stocks.opt.hist {ticker} {strike} {put} {expiration}" current = 3 text = ("```0️⃣ !stocks.opt.unu\n" f"1️⃣ !stocks.opt.exp {ticker}\n" f"2️⃣ !stocks.opt.iv {ticker}\n" f"3️⃣ !stocks.opt.calls {ticker} {expiration} \n" f"4️⃣ !stocks.opt.puts {ticker} {expiration} \n" f"5️⃣ !stocks.opt.oi {ticker} {expiration} \n" f"6️⃣ !stocks.opt.vol {ticker} {expiration} \n" f"{hist}```") if put == "p": put = bool(True) if put == "c": put = bool(False) title = "Stocks: Options Menu" embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) msg = await ctx.send(embed=embed, delete_after=60.0) if current == 0: emoji_list = ["0️⃣"] if current == 1: emoji_list = ["0️⃣", "1️⃣", "2️⃣"] if current == 2: emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣"] if current == 3: emoji_list = [ "0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣" ] for emoji in emoji_list: await msg.add_reaction(emoji) def check(reaction, user): return user == ctx.message.author and str( reaction.emoji) in emoji_list try: reaction, _ = await gst_bot.wait_for("reaction_add", timeout=cfg.MENU_TIMEOUT, check=check) if reaction.emoji == "0️⃣": logger.info("Reaction selected: 0") await unu_command(ctx) elif reaction.emoji == "1️⃣": logger.info("Reaction selected: 1") await expirations_command(ctx, ticker) elif reaction.emoji == "2️⃣": logger.info("Reaction selected: 2") await iv_command(ctx, ticker) elif reaction.emoji == "3️⃣": logger.info("Reaction selected: 3") await calls_command(ctx, ticker, expiration) elif reaction.emoji == "4️⃣": logger.info("Reaction selected: 4") await puts_command(ctx, ticker, expiration) elif reaction.emoji == "5️⃣": logger.info("Reaction selected: 5") await oi_command(ctx, ticker, expiration) elif reaction.emoji == "6️⃣": logger.info("Reaction selected: 6") await vol_command(ctx, ticker, expiration) elif reaction.emoji == "7️⃣": logger.info("Reaction selected: 7") strike = float(strike) await hist_command(ctx, ticker, expiration, strike, put) for emoji in emoji_list: await msg.remove_reaction(emoji, ctx.bot.user) except asyncio.TimeoutError: for emoji in emoji_list: await msg.remove_reaction(emoji, ctx.bot.user) if cfg.DEBUG: embed = discord.Embed( description="Error timeout - you snooze you lose! 😋", colour=cfg.COLOR, title="TIMEOUT Stocks: Options Menu", ).set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=30.0)
async def overview_command( ctx, ticker: str = None, expiry: str = None, min_sp: float = None, max_sp: float = None, ): """Options Overview""" try: # Debug startTime2 = time.time() if cfg.DEBUG: print(f"!stocks.opt.iv {ticker} {expiry} {min_sp} {max_sp}") # Check for argument if ticker is None: raise Exception("Stock ticker is required") # Get options info/dates, Look for logo_url df = get_options_info(ticker) # Barchart Options IV Overview dates = yfinance_model.option_expirations(ticker) # Expiration dates tup = f"{ticker.upper()}" url = yf.Ticker(tup).info["logo_url"] url += "?raw=true" if url else "" if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, str(expiry)) calls = options.calls puts = options.puts current_price = yfinance_model.get_price(ticker) min_strike2 = np.percentile(calls["strike"], 1) max_strike2 = np.percentile(calls["strike"], 100) min_strike = 0.75 * current_price max_strike = 1.95 * current_price if len(calls) > 40: min_strike = 0.75 * current_price max_strike = 1.25 * current_price if min_sp: min_strike = min_sp min_strike2 = min_sp if max_sp: max_strike = max_sp max_strike2 = max_sp if min_sp > max_sp: # type: ignore min_sp, max_sp = max_strike2, min_strike2 call_oi = calls.set_index("strike")["openInterest"] / 1000 put_oi = puts.set_index("strike")["openInterest"] / 1000 df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) df_opt = df_opt.rename(columns={ "openInterest_x": "OI_call", "openInterest_y": "OI_put" }) max_pain = op_helpers.calculate_max_pain(df_opt) fig = go.Figure() dmax = df_opt[["OI_call", "OI_put"]].values.max() dmin = df_opt[["OI_call", "OI_put"]].values.min() fig.add_trace( go.Scatter( x=df_opt.index, y=df_opt["OI_call"], name="Calls", mode="lines+markers", line=dict(color="green", width=3), )) fig.add_trace( go.Scatter( x=df_opt.index, y=df_opt["OI_put"], name="Puts", mode="lines+markers", line=dict(color="red", width=3), )) fig.add_trace( go.Scatter( x=[current_price, current_price], y=[dmin, dmax], mode="lines", line=dict(color="gold", width=2), name="Current Price", )) fig.add_trace( go.Scatter( x=[max_pain, max_pain], y=[dmin, dmax], mode="lines", line=dict(color="grey", width=3, dash="dash"), name=f"Max Pain: {max_pain}", )) fig.update_xaxes( range=[min_strike, max_strike], constrain="domain", ) fig.update_layout( margin=dict(l=0, r=0, t=60, b=20), template=cfg.PLT_SCAT_STYLE_TEMPLATE, title=f"Open Interest for {ticker.upper()} expiring {expiry}", title_x=0.5, legend_title="", xaxis_title="Strike", yaxis_title="Open Interest (1k)", xaxis=dict(rangeslider=dict(visible=False), ), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), dragmode="pan", ) config = dict({"scrollZoom": True}) imagefile = "opt_oi.png" fig.write_image(imagefile) plt_link = "" if cfg.INTERACTIVE: html_ran = random.randint(69, 69420) fig.write_html(f"in/oi_{html_ran}.html", config=config) plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)" img = Image.open(imagefile) im_bg = Image.open(cfg.IMG_BG) h = img.height + 240 w = img.width + 520 img = img.resize((w, h), Image.ANTIALIAS) x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) img = img.convert("RGB") im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) im_bg.save(imagefile, "PNG", quality=100) image = Image.open(imagefile) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) uploaded_image_oi = gst_imgur.upload_image(imagefile, title="something") image_link_oi = uploaded_image_oi.link column_map = { "openInterest": "oi", "volume": "vol", "impliedVolatility": "iv" } columns = [ "strike", "bid", "ask", "volume", "openInterest", "impliedVolatility", ] calls_df = calls[columns].rename(columns=column_map) puts_df = puts[columns].rename(columns=column_map) calls_df = calls_df[calls_df["strike"] >= min_strike2] calls_df = calls_df[calls_df["strike"] <= max_strike2] puts_df = puts_df[puts_df["strike"] >= min_strike2] puts_df = puts_df[puts_df["strike"] <= max_strike2] calls_df["iv"] = pd.to_numeric(calls_df["iv"].astype(float)) puts_df["iv"] = pd.to_numeric(puts_df["iv"].astype(float)) formats = {"iv": "{:.2f}"} for col, f in formats.items(): calls_df[col] = calls_df[col].map(lambda x: f.format(x) # pylint: disable=W0640 ) puts_df[col] = puts_df[col].map(lambda x: f.format(x) # pylint: disable=W0640 ) calls_df.set_index("strike", inplace=True) puts_df.set_index("strike", inplace=True) if "-" in df.iloc[0, 1]: iv = f"```diff\n- {df.iloc[0, 1]}\n```" else: iv = f"```yaml\n {df.iloc[0, 1]}\n```" pfix, sfix = f"{ticker.upper()} ", f" expiring {expiry}" if expiry == dates[0]: pfix = f"{ticker.upper()} Weekly " sfix = "" embeds = [ disnake.Embed( title=f"{ticker.upper()} Overview", color=cfg.COLOR, ), disnake.Embed( title=f"{pfix}Open Interest{sfix}", description=plt_link, colour=cfg.COLOR, ), ] choices = [ disnake.SelectOption(label=f"{ticker.upper()} Overview", value="0", emoji="🟢"), disnake.SelectOption(label=f"{pfix}Open Interest{sfix}", value="1", emoji="🟢"), ] embeds_img = [] i, i2, end = 0, 0, 20 df_calls = [] dindex = len(calls_df.index) while i <= dindex: df_calls = calls_df.iloc[i:end] df_calls.append(df_calls) figp = df2img.plot_dataframe( df_calls, fig_size=(1000, (40 + (40 * 20))), col_width=[3, 3, 3, 3], tbl_cells=cfg.PLT_TBL_CELLS, font=cfg.PLT_TBL_FONT, template=cfg.PLT_TBL_STYLE_TEMPLATE, paper_bgcolor="rgba(0, 0, 0, 0)", ) imagefile = f"opt-calls{i}.png" df2img.save_dataframe(fig=figp, filename=imagefile) image = Image.open(imagefile) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) uploaded_image = gst_imgur.upload_image(imagefile, title="something") image_link = uploaded_image.link embeds_img.append(f"{image_link}", ) embeds.append( disnake.Embed( title=f"{pfix}Calls{sfix}", colour=cfg.COLOR, ), ) i2 += 1 i += 20 end += 20 os.remove(imagefile) # Add Calls page field i, page, puts_page = 2, 0, 3 i3 = i2 + 2 choices.append( disnake.SelectOption(label="Calls Page 1", value="2", emoji="🟢"), ) for i in range(2, i3): page += 1 puts_page += 1 embeds[i].add_field(name=f"Calls Page {page}", value="_ _", inline=True) # Puts Pages i, end = 0, 20 df_puts = [] dindex = len(puts_df.index) while i <= dindex: df_puts = puts_df.iloc[i:end] df_puts.append(df_puts) figp = df2img.plot_dataframe( df_puts, fig_size=(1000, (40 + (40 * 20))), col_width=[3, 3, 3, 3], tbl_cells=cfg.PLT_TBL_CELLS, font=cfg.PLT_TBL_FONT, template=cfg.PLT_TBL_STYLE_TEMPLATE, paper_bgcolor="rgba(0, 0, 0, 0)", ) imagefile = f"opt-puts{i}.png" df2img.save_dataframe(fig=figp, filename=imagefile) image = Image.open(imagefile) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) uploaded_image = gst_imgur.upload_image(imagefile, title="something") image_link = uploaded_image.link embeds_img.append(f"{image_link}", ) embeds.append( disnake.Embed( title=f"{pfix}Puts{sfix}", colour=cfg.COLOR, ), ) i2 += 1 i += 20 end += 20 os.remove(imagefile) # Add Puts page field i, page = 0, 0 puts_page -= 1 i2 += 2 choices.append( disnake.SelectOption(label="Puts Page 1", value=f"{puts_page}", emoji="🟢"), ) for i in range(puts_page, i2): page += 1 embeds[i].add_field(name=f"Puts Page {page}", value="_ _", inline=True) # Author/Footer for i in range(0, i2): embeds[i].set_author( name=cfg.AUTHOR_NAME, url=cfg.AUTHOR_URL, icon_url=cfg.AUTHOR_ICON_URL, ) embeds[i].set_footer( text=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) # Set images to Pages i = 0 img_i = 0 embeds[1].set_image(url=image_link_oi) for i in range(2, i2): embeds[i].set_image(url=embeds_img[img_i]) img_i += 1 i += 1 if url: embeds[0].set_thumbnail(url=f"{url}") else: embeds[0].set_thumbnail(url=cfg.AUTHOR_ICON_URL) # Overview Section embeds[0].add_field(name=f"{df.iloc[0, 0]}", value=iv, inline=False) embeds[0].add_field(name=f"•{df.iloc[1, 0]}", value=f"```css\n{df.iloc[1, 1]}\n```", inline=True) for N in range(2, 6): embeds[0].add_field( name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", value=f"```css\n{df.iloc[N, 1]}\n```", inline=True, ) embeds[0].add_field(name="_ _", value="_ _", inline=False) for N in range(6, 8): embeds[0].add_field( name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", value=f"```css\n{df.iloc[N, 1]}\n```", inline=True, ) embeds[0].add_field(name="_ _", value="_ _", inline=False) for N in range(8, 10): embeds[0].add_field( name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", value=f"```css\n{df.iloc[N, 1]}\n```", inline=True, ) embeds[0].add_field(name="_ _", value="_ _", inline=False) for N in range(10, 12): embeds[0].add_field( name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", value=f"```css\n{df.iloc[N, 1]}\n```", inline=True, ) embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") executionTime2 = time.time() - startTime2 print( f"\n> {__name__} is finished: time in seconds: {executionTime2}\n") await ctx.send(embed=embeds[0], view=Menu(embeds, choices)) except Exception as e: embed = disnake.Embed( title="ERROR Options: Overview", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=30.0)
async def expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg: tuple = None): if TRADIER_TOKEN == "REPLACE_ME": # nosec dates = yfinance_model.option_expirations(ticker) else: dates = tradier_model.option_expirations(ticker) index_dates = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] if expiry is not None: try: if expiry not in dates: exp = int(expiry.replace("-", "")) if (expiry not in dates) and (exp not in index_dates): raise Exception("Enter a valid expiration date.") if expiry in dates: if call_arg is None: await func_cmd(ctx, ticker, expiry) else: await func_cmd(ctx, ticker, expiry, *call_arg) return if exp in index_dates: expiry = dates[int(expiry)] if call_arg is None: await func_cmd(ctx, ticker, expiry) else: await func_cmd(ctx, ticker, expiry, *call_arg) return except Exception as e: embed = discord.Embed( title="ERROR Options: Expiry Date", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=10.0) return if not dates: embed = discord.Embed( title="ERROR Options", colour=cfg.COLOR, description="Enter a valid stock ticker", ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=10.0) return text = ("```0️⃣ " + dates[0] + "\n" "1️⃣ " + dates[1] + "\n" "2️⃣ " + dates[2] + "\n" "3️⃣ " + dates[3] + "\n" "4️⃣ " + dates[4] + "\n" "5️⃣ " + dates[5] + "\n" "6️⃣ " + dates[6] + "\n" "7️⃣ " + dates[7] + "\n" "8️⃣ " + dates[8] + "\n" "9️⃣ " + dates[9] + "```") title = " " + ticker.upper() + " Options: Expiry Date" embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) msg = await ctx.send(embed=embed, delete_after=15.0) emoji_list = [ "0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣" ] for emoji in emoji_list: await msg.add_reaction(emoji) def check(reaction, user): return user == ctx.message.author and str(reaction.emoji) in emoji_list try: reaction, _ = await gst_bot.wait_for("reaction_add", timeout=cfg.MENU_TIMEOUT, check=check) for N in range(0, 10): if reaction.emoji == emoji_list[N]: logger.info("Reaction selected: %d", N) expiry = dates[N] if call_arg is None: await func_cmd(ctx, ticker, expiry) else: await func_cmd(ctx, ticker, expiry, *call_arg) for emoji in emoji_list: await msg.remove_reaction(emoji, ctx.bot.user) except asyncio.TimeoutError: for emoji in emoji_list: await msg.remove_reaction(emoji, ctx.bot.user) if cfg.DEBUG: embed = discord.Embed( description="Error timeout - you snooze you lose! 😋", colour=cfg.COLOR, title="TIMEOUT " + ticker.upper() + " Options: Expiry Date", ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed, delete_after=10.0)
def options_data( ticker: str = None, expiry: str = None, min_sp: float = None, max_sp: float = None, ): # Debug if imps.DEBUG: logger.debug("opt overview %s %s %s %s", ticker, expiry, min_sp, max_sp) # Check for argument if ticker is None: raise Exception("Stock ticker is required") # Get options info/dates, Look for logo_url if "^" not in ticker: df_bcinfo = get_options_info(ticker) # Barchart Options IV Overview df_bcinfo = df_bcinfo.fillna("") df_bcinfo = df_bcinfo.set_axis( [ " ", "", ], axis="columns", ) df_bcinfo[""] = df_bcinfo[""].str.lstrip() else: df_bcinfo = "" dates = yfinance_model.option_expirations(ticker) # Expiration dates tup = f"{ticker.upper()}" url = yf.Ticker(tup).info["logo_url"] url += "?raw=true" if url else "" if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, str(expiry)) calls = options.calls.fillna(0) puts = options.puts.fillna(0) current_price = yfinance_model.get_price(ticker) min_strike2 = np.percentile(calls["strike"], 1) max_strike2 = np.percentile(calls["strike"], 100) min_strike = 0.75 * current_price max_strike = 1.95 * current_price if len(calls) > 40: min_strike = 0.75 * current_price max_strike = 1.25 * current_price if min_sp: min_strike = min_sp min_strike2 = min_sp if max_sp: max_strike = max_sp max_strike2 = max_sp if min_sp > max_sp: # type: ignore min_sp, max_sp = max_strike2, min_strike2 call_oi = calls.set_index("strike")["openInterest"] / 1000 put_oi = puts.set_index("strike")["openInterest"] / 1000 df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) df_opt = df_opt.rename(columns={ "openInterest_x": "OI_call", "openInterest_y": "OI_put" }) max_pain = op_helpers.calculate_max_pain(df_opt) data = [ ticker, url, expiry, dates, df_bcinfo, calls, puts, df_opt, current_price, min_strike, max_strike, min_strike2, max_strike2, max_pain, ] return data
async def oi_command( ctx, ticker: str = None, expiry: str = "", min_sp: float = None, max_sp: float = None, ): """Options OI""" try: # Debug if cfg.DEBUG: logger.debug("!stocks.opt.oi %s %s %s %s", ticker, expiry, min_sp, max_sp) # Check for argument if ticker is None: raise Exception("Stock ticker is required") dates = yfinance_model.option_expirations(ticker) if not dates: raise Exception("Stock ticker is invalid") options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls puts = options.puts current_price = yfinance_model.get_price(ticker) if min_sp is None: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp is None: max_strike = 1.25 * current_price else: max_strike = max_sp call_oi = calls.set_index("strike")["openInterest"] / 1000 put_oi = puts.set_index("strike")["openInterest"] / 1000 df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) df_opt = df_opt.rename(columns={ "openInterest_x": "OI_call", "openInterest_y": "OI_put" }) max_pain = op_helpers.calculate_max_pain(df_opt) plt.style.use("seaborn") fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) put_oi.plot( x="strike", y="openInterest", label="Puts", ax=ax, marker="o", ls="-", c="r", ) call_oi.plot( x="strike", y="openInterest", label="Calls", ax=ax, marker="o", ls="-", c="g", ) ax.axvline(current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.4) ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.4) ax.grid("on") ax.set_xlabel("Strike Price") ax.set_ylabel("Open Interest (1k) ") ax.set_xlim(min_strike, max_strike) ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}") plt.legend(loc=0) fig.tight_layout() imagefile = "opt_oi.png" plt.savefig("opt_oi.png") image = discord.File(imagefile) if cfg.DEBUG: logger.debug("Image %s", imagefile) title = f"Open Interest for {ticker.upper()} expiring {expiry}" embed = discord.Embed(title=title, colour=cfg.COLOR) embed.set_image(url="attachment://opt_oi.png") embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) os.remove("opt_oi.png") await ctx.send(embed=embed, file=image) except Exception as e: embed = discord.Embed( title="ERROR Options: Open Interest", colour=cfg.COLOR, description=e, ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, ) await ctx.send(embed=embed)