def __init__(self, ticker: str, expiration: str, queue: List[str] = None): """Constructor""" super().__init__(queue) self.chain = get_option_chain(ticker, expiration) self.calls = list( zip( self.chain.calls["strike"].tolist(), self.chain.calls["lastPrice"].tolist(), )) self.puts = list( zip( self.chain.puts["strike"].tolist(), self.chain.puts["lastPrice"].tolist(), )) self.ticker = ticker self.current_price = get_price(ticker) self.expiration = expiration self.options: List[Dict[str, str]] = [] self.underlying = 0 self.call_index_choices = range(len(self.calls)) self.put_index_choices = range(len(self.puts)) if session and gtff.USE_PROMPT_TOOLKIT: choices: dict = {c: {} for c in self.controller_choices} choices["pick"] = {c: {} for c in self.underlying_asset_choices} choices["add"] = { str(c): {} for c in list(range(max(len(self.puts), len(self.calls)))) } # This menu contains dynamic choices that may change during runtime self.choices = choices self.completer = NestedCompleter.from_nested_dict(choices)
def __init__(self, ticker: str, expiration: str, queue: List[str] = None): """Construct""" self.payoff_parser = argparse.ArgumentParser(add_help=False, prog="payoff") self.payoff_parser.add_argument("cmd", choices=self.CHOICES) self.chain = get_option_chain(ticker, expiration) self.calls = list( zip( self.chain.calls["strike"].tolist(), self.chain.calls["lastPrice"].tolist(), )) self.puts = list( zip( self.chain.puts["strike"].tolist(), self.chain.puts["lastPrice"].tolist(), )) self.ticker = ticker self.current_price = get_price(ticker) self.expiration = expiration self.options: List[Dict[str, str]] = [] self.underlying = 0 self.call_index_choices = range(len(self.calls)) self.put_index_choices = range(len(self.puts)) self.completer: Union[None, NestedCompleter] = None if session and gtff.USE_PROMPT_TOOLKIT: self.choices: dict = {c: {} for c in self.CHOICES} self.choices["pick"] = { c: {} for c in self.underlying_asset_choices } self.choices["add"] = { str(c): {} for c in list(range(max(len(self.puts), len(self.calls)))) } if queue: self.queue = queue else: self.queue = list()
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 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, }
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 show_parity(ticker: str, exp: str, put: bool, ask: bool, mini: float, maxi: float, export: str) -> None: """Prints options and whether they are under or over priced [Source: Yahoo Finance] Parameters ---------- ticker : str Ticker to get expirations for exp : str Expiration to use for options put : bool Whether to use puts or calls ask : bool Whether to use ask or lastPrice mini : float Minimum strike price to show maxi : float Maximum strike price to show export : str Export data """ r_date = datetime.strptime(exp, "%Y-%m-%d").date() delta = (r_date - date.today()).days rate = ((1 + get_rf())**(delta / 365)) - 1 stock = get_price(ticker) div_info = yfinance_model.get_dividend(ticker) div_dts = div_info.index.values.tolist() if div_dts: last_div = pd.to_datetime(div_dts[-1]) if len(div_dts) > 3: avg_div = np.mean(div_info.to_list()[-4:]) else: avg_div = np.mean(div_info.to_list()) next_div = last_div + timedelta(days=91) dividends = [] while next_div < datetime.strptime(exp, "%Y-%m-%d"): day_dif = (next_div - datetime.now()).days dividends.append((avg_div, day_dif)) next_div += timedelta(days=91) div_pvs = [x[0] / ((1 + get_rf())**(x[1] / 365)) for x in dividends] pv_dividend = sum(div_pvs) else: pv_dividend = 0 chain = get_option_chain(ticker, exp) name = "ask" if ask else "lastPrice" o_type = "put" if put else "call" calls = chain.calls[["strike", name]].copy() calls = calls.rename(columns={name: "callPrice"}) puts = chain.puts[["strike", name]].copy() puts = puts.rename(columns={name: "putPrice"}) opts = pd.merge(calls, puts, on="strike") opts = opts.dropna() opts = opts.loc[opts["callPrice"] * opts["putPrice"] != 0] opts["callParity"] = (opts["putPrice"] + stock - (opts["strike"] / (1 + rate)) - pv_dividend) opts["putParity"] = ((opts["strike"] / (1 + rate)) + opts["callPrice"] - stock + pv_dividend) diff = o_type + " Difference" opts[diff] = opts[o_type + "Price"] - opts[o_type + "Parity"] opts["distance"] = abs(stock - opts["strike"]) filtered = opts.copy() if mini is None: mini = filtered.strike.quantile(0.25) if maxi is None: maxi = filtered.strike.quantile(0.75) filtered = filtered.loc[filtered["strike"] >= mini] filtered = filtered.loc[filtered["strike"] <= maxi] show = filtered[["strike", diff]].copy() if ask: console.print( "Warning: Options with no current ask price not shown.\n") print_rich_table( show, headers=[x.title() for x in show.columns], show_index=False, title="Warning: Low volume options may be difficult to trade.", ) export_data( export, os.path.dirname(os.path.abspath(__file__)), "parity", show, ) console.print()
def show_greeks( ticker: str, div_cont: float, expire: str, rf: float = None, opt_type: int = 1, mini: float = None, maxi: float = None, show_all: bool = False, ) -> None: """ Shows the greeks for a given option Parameters ---------- ticker : str The ticker value of the option div_cont : float The dividend continuous rate expire : str The date of expiration rf : float The risk-free rate opt_type : Union[1, -1] The option type 1 is for call and -1 is for put mini : float The minimum strike price to include in the table maxi : float The maximum strike price to include in the table all : bool Whether to show all greeks """ s = yfinance_model.get_price(ticker) chains = yfinance_model.get_option_chain(ticker, expire) chain = chains.calls if opt_type == 1 else chains.puts if mini is None: mini = chain.strike.quantile(0.25) if maxi is None: maxi = chain.strike.quantile(0.75) chain = chain[chain["strike"] >= mini] chain = chain[chain["strike"] <= maxi] risk_free = rf if rf is not None else get_rf() expire_dt = datetime.strptime(expire, "%Y-%m-%d") dif = (expire_dt - datetime.now()).seconds / (60 * 60 * 24) strikes = [] for _, row in chain.iterrows(): vol = row["impliedVolatility"] opt = Option(s, row["strike"], risk_free, div_cont, dif, vol, opt_type) result = [ row["strike"], row["impliedVolatility"], opt.Delta(), opt.Gamma(), opt.Vega(), opt.Theta(), ] if show_all: result += [ opt.Rho(), opt.Phi(), opt.Charm(), opt.Vanna(0.01), opt.Vomma(0.01), ] strikes.append(result) columns = [ "Strike", "Implied Vol", "Delta", "Gamma", "Vega", "Theta", ] if show_all: columns += ["Rho", "Phi", "Charm", "Vanna", "Vomma"] df = pd.DataFrame(strikes, columns=columns) print_rich_table(df, headers=list(df.columns), show_index=False, title="Greeks") console.print() return None
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)
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)
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 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)