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 call_exp(self, other_args: List[str]): """Process exp command""" parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, prog="exp", description="See and set expiration date", ) parser.add_argument( "-i", "--index", dest="index", action="store", type=int, default=-1, choices=range(len(self.expiry_dates)), help="Select index for expiry date.", ) parser.add_argument( "-d", "--date", dest="date", type=str, choices=self.expiry_dates + [""], help="Select date (YYYY-MM-DD)", default="", ) if other_args and "-" not in other_args[0][0]: other_args.insert(0, "-i") ns_parser = parse_known_args_and_warn(parser, other_args) if ns_parser: if self.ticker: # Print possible expiry dates if ns_parser.index == -1 and not ns_parser.date: console.print("\nAvailable expiry dates:") for i, d in enumerate(self.expiry_dates): console.print(f" {(2 - len(str(i))) * ' '}{i}. {d}") console.print("") elif ns_parser.date: if ns_parser.date in self.expiry_dates: console.print(f"Expiration set to {ns_parser.date} \n") self.selected_date = ns_parser.date self.update_runtime_choices() else: console.print("Expiration not an option") else: expiry_date = self.expiry_dates[ns_parser.index] console.print(f"Expiration set to {expiry_date} \n") self.selected_date = expiry_date self.update_runtime_choices() if self.selected_date: self.chain = yfinance_model.get_option_chain( self.ticker, self.selected_date) self.update_runtime_choices() else: console.print("Please load a ticker using `load <ticker>`.\n")
def test_get_option_chain(mocker, recorder): # FORCE SINGLE THREADING yf_download = yfinance_model.yf.download def mock_yf_download(*args, **kwargs): kwargs["threads"] = False return yf_download(*args, **kwargs) mocker.patch("yfinance.download", side_effect=mock_yf_download) result_tuple = yfinance_model.get_option_chain( ticker="PM", expiration="2022-01-07", ) result_tuple = (result_tuple.calls, result_tuple.puts) recorder.capture_list(result_tuple)
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 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)
def plot_volume_open_interest( ticker: str, expiry: str, min_sp: float, max_sp: float, min_vol: float, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot volume and open interest Parameters ---------- ticker: str Stock ticker expiry: str Option expiration min_sp: float Min strike price max_sp: float Max strike price min_vol: float Min volume to consider export: str Format for exporting data external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls puts = options.puts current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) # Process Calls Data df_calls = 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(current_price, 2) df_puts = 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(current_price, 2) 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) if min_vol == -1 and min_sp == -1 and 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 min_vol > -1: df_calls = df_calls[df_calls["oi+v"] > min_vol] df_puts = df_puts[df_puts["oi+v"] < -min_vol] if min_sp > -1: df_calls = df_calls[df_calls["strike"] > min_sp] df_puts = df_puts[df_puts["strike"] > min_sp] if max_sp > -1: df_calls = df_calls[df_calls["strike"] < max_sp] df_puts = df_puts[df_puts["strike"] < max_sp] if df_calls.empty and df_puts.empty: console.print( "The filtering applied is too strong, there is no data available for such conditions.\n" ) return # Initialize the matplotlib figure if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) else: if len(external_axes) != 1: logger.error("Expected list of one axis item.") console.print("[red]Expected list of one axis item./n[/red]") return (ax, ) = external_axes # 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) 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, current_price) # find where the spot is on the graph spot_line = ax.axhline(spot_index, ls="--", alpha=0.3) # draw max pain line max_pain_index = bisect_left(s, max_pain) max_pain_line = ax.axhline(max_pain_index, ls="-", alpha=0.3, color="red") 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) ax.set_title( f"{ticker} volumes for {expiry} \n(open interest displayed only during market hours)", ) ax.invert_yaxis() 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}", ] ax.legend(handles=handles[:], labels=labels, loc="lower left") sns.despine(left=True, bottom=True) theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)), "voi_yf", options, )
def plot_plot( ticker: str, expiration: str, put: bool, x: str, y: str, custom: str, export: str, external_axes: Optional[List[plt.Axes]] = None, ) -> None: """Generate a graph custom graph based on user input Parameters ---------- ticker: str Stock ticker expiration: str Option expiration min_sp: float Min strike price put: bool put option instead of call x: str variable to display in x axis y: str variable to display in y axis custom: str type of plot export: str type of data to export external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ convert = { "ltd": "lastTradeDate", "s": "strike", "lp": "lastPrice", "b": "bid", "a": "ask", "c": "change", "pc": "percentChange", "v": "volume", "oi": "openInterest", "iv": "impliedVolatility", } if custom == "smile": x = "strike" y = "impliedVolatility" else: x = convert[x] y = convert[y] varis = op_helpers.opt_chain_cols chain = yfinance_model.get_option_chain(ticker, expiration) values = chain.puts if put else chain.calls if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) else: if len(external_axes) != 1: logger.error("Expected list of one axis item.") console.print("[red]Expected list of one axis item./n[/red]") return (ax, ) = external_axes x_data = values[x] y_data = values[y] ax.plot(x_data, y_data, "--bo") option = "puts" if put else "calls" ax.set_title( f"{varis[y]['label']} vs. {varis[x]['label']} for {ticker} {option} on {expiration}" ) ax.set_ylabel(varis[y]["label"]) ax.set_xlabel(varis[x]["label"]) if varis[x]["format"] == "date": ax.get_xaxis().set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) ax.get_xaxis().set_major_locator(mdates.DayLocator(interval=1)) elif varis[x]["format"]: ax.get_xaxis().set_major_formatter(varis[x]["format"]) if varis[y]["format"] == "date": ax.get_yaxis().set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) ax.get_yaxis().set_major_locator(mdates.DayLocator(interval=1)) elif varis[y]["format"]: ax.get_yaxis().set_major_formatter(varis[y]["format"]) theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() export_data(export, os.path.dirname(os.path.abspath(__file__)), "plot")
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, }
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: 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 plot_vol( ticker: str, expiry: str, min_sp: float, max_sp: float, calls_only: bool, puts_only: bool, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot volume Parameters ---------- ticker: str Ticker expiry: str Expiry date for options min_sp: float Min strike to consider max_sp: float Max strike to consider calls_only: bool Show calls only puts_only: bool Show puts only export: str Format to export file external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls puts = options.puts current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) if min_sp == -1: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp == -1: max_strike = 1.25 * current_price else: max_strike = max_sp if calls_only and puts_only: console.print("Both flags selected, please select one", "\n") return call_v = calls.set_index("strike")["volume"] / 1000 put_v = puts.set_index("strike")["volume"] / 1000 if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) else: if len(external_axes) != 1: console.print("[red]Expected list of one axis item./n[/red]") return (ax,) = external_axes if not calls_only: put_v.plot( x="strike", y="volume", label="Puts", ax=ax, marker="o", ls="-", ) if not puts_only: call_v.plot( x="strike", y="volume", label="Calls", ax=ax, marker="o", ls="-", ) ax.axvline(current_price, lw=2, ls="--", label="Current Price", alpha=0.7) ax.set_xlabel("Strike Price") ax.set_ylabel("Volume (1k) ") ax.set_xlim(min_strike, max_strike) ax.legend() ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}") theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)), "vol_yf", options, )
def risk_neutral_vals( ticker: str, exp: str, put: bool, df: pd.DataFrame, mini: float, maxi: float, risk: float, ) -> None: """Prints current options prices and risk neutral values [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 df : pd.DataFrame Estimates for stocks prices and probabilities mini : float Minimum strike price to show maxi : float Maximum strike price to show risk : float The risk-free rate for the asset """ if put: chain = get_option_chain(ticker, exp).puts else: chain = get_option_chain(ticker, exp).calls r_date = datetime.strptime(exp, "%Y-%m-%d").date() delta = (r_date - date.today()).days vals = [] if risk is None: risk = get_rf() for _, row in chain.iterrows(): vals.append([ row["strike"], row["lastPrice"], op_helpers.rn_payoff(row["strike"], df, put, delta, risk), ]) new_df = pd.DataFrame(vals, columns=["Strike", "Last Price", "Value"], dtype=float) new_df["Difference"] = new_df["Last Price"] - new_df["Value"] if mini is None: mini = new_df.Strike.quantile(0.25) if maxi is None: maxi = new_df.Strike.quantile(0.75) new_df = new_df[new_df["Strike"] >= mini] new_df = new_df[new_df["Strike"] <= maxi] print_rich_table( new_df, headers=[x.title() for x in new_df.columns], show_index=False, title="Risk Neutral Values", ) console.print()
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, }
def plot_oi( ticker: str, expiry: str, min_sp: float, max_sp: float, calls_only: bool, puts_only: bool, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot open interest Parameters ---------- ticker: str Ticker expiry: str Expiry date for options min_sp: float Min strike to consider max_sp: float Max strike to consider calls_only: bool Show calls only puts_only: bool Show puts only export: str Format to export file external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ options = yfinance_model.get_option_chain(ticker, expiry) export_data( export, os.path.dirname(os.path.abspath(__file__)), "oi_yf", options, ) calls = options.calls puts = options.puts current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) if min_sp == -1: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp == -1: max_strike = 1.25 * current_price else: max_strike = max_sp if calls_only and puts_only: console.print("Both flags selected, please select one", "\n") return 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) if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) else: if len(external_axes) != 1: logger.error("Expected list of one axis item.") console.print("[red]Expected list of one axis item./n[/red]") return (ax, ) = external_axes if not calls_only: put_oi.plot( x="strike", y="openInterest", label="Puts", ax=ax, marker="o", ls="-", ) if not puts_only: call_oi.plot( x="strike", y="openInterest", label="Calls", ax=ax, marker="o", ls="-", ) ax.axvline(current_price, lw=2, ls="--", label="Current Price", alpha=0.7) ax.axvline(max_pain, lw=3, label=f"Max Pain: {max_pain}", alpha=0.7) ax.set_xlabel("Strike Price") ax.set_ylabel("Open Interest (1k) ") ax.set_xlim(min_strike, max_strike) ax.legend() ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}") theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output()
def plot_vol( ticker: str, expiry: str, min_sp: float, max_sp: float, calls_only: bool, puts_only: bool, export: str = "", ): """Plot volume Parameters ---------- ticker: str Ticker expiry: str Expiry date for options min_sp: float Min strike to consider max_sp: float Max strike to consider calls_only: bool Show calls only puts_only: bool Show puts only export: str Format to export file """ options = yfinance_model.get_option_chain(ticker, expiry) calls = options.calls puts = options.puts current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) if min_sp == -1: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp == -1: max_strike = 1.25 * current_price else: max_strike = max_sp if calls_only and puts_only: console.print("Both flags selected, please select one", "\n") return call_v = calls.set_index("strike")["volume"] / 1000 put_v = puts.set_index("strike")["volume"] / 1000 plt.style.use("classic") fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) if not calls_only: put_v.plot( x="strike", y="volume", label="Puts", ax=ax, marker="o", ls="-", c="r", ) if not puts_only: 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) if gtff.USE_ION: plt.ion() ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}") plt.legend(loc=0) fig.tight_layout(pad=1) plt.show() export_data( export, os.path.dirname(os.path.abspath(__file__)), "vol_yf", options, ) console.print("")
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 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 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 plot_oi( ticker: str, expiry: str, min_sp: float, max_sp: float, calls_only: bool, puts_only: bool, export: str = "", ): """Plot open interest Parameters ---------- ticker: str Ticker expiry: str Expiry date for options min_sp: float Min strike to consider max_sp: float Max strike to consider calls_only: bool Show calls only puts_only: bool Show puts only export: str Format to export file """ options = yfinance_model.get_option_chain(ticker, expiry) export_data( export, os.path.dirname(os.path.abspath(__file__)), "oi_yf", options, ) calls = options.calls puts = options.puts current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) if min_sp == -1: min_strike = 0.75 * current_price else: min_strike = min_sp if max_sp == -1: max_strike = 1.25 * current_price else: max_strike = max_sp if calls_only and puts_only: print("Both flags selected, please select one", "\n") return 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("classic") fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI) if not calls_only: put_oi.plot( x="strike", y="openInterest", label="Puts", ax=ax, marker="o", ls="-", c="r", ) if not puts_only: 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.7) ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.7) ax.grid("on") ax.set_xlabel("Strike Price") ax.set_ylabel("Open Interest (1k) ") ax.set_xlim(min_strike, max_strike) if gtff.USE_ION: plt.ion() ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}") plt.legend(loc=0) fig.tight_layout(pad=1) plt.show() print("")
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 plot_plot(ticker: str, expiration: str, put: bool, x: str, y: str, custom: str, export: str) -> None: """Generate a graph custom graph based on user input Parameters ---------- ticker: str Stock ticker expiration: str Option expiration min_sp: float Min strike price put: bool put option instead of call x: str variable to display in x axis y: str variable to display in y axis custom: str type of plot export: str type of data to export """ convert = { "ltd": "lastTradeDate", "s": "strike", "lp": "lastPrice", "b": "bid", "a": "ask", "c": "change", "pc": "percentChange", "v": "volume", "oi": "openInterest", "iv": "impliedVolatility", } x = convert[x] y = convert[y] varis = op_helpers.opt_chain_cols chain = yfinance_model.get_option_chain(ticker, expiration) values = chain.puts if put else chain.calls _, ax = plt.subplots() if custom == "smile": x = "strike" y = "impliedVolatility" x_data = values[x] y_data = values[y] ax.plot(x_data, y_data, "--bo") word = "puts" if put else "calls" ax.set_title( f"{varis[y]['label']} vs. {varis[x]['label']} for {ticker} {word} on {expiration}" ) ax.set_ylabel(varis[y]["label"]) ax.set_xlabel(varis[x]["label"]) if varis[x]["format"] == "date": plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1)) plt.gcf().autofmt_xdate() elif varis[x]["format"]: ax.xaxis.set_major_formatter(varis[x]["format"]) if varis[y]["format"] == "date": plt.gca().yaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) plt.gca().yaxis.set_major_locator(mdates.DayLocator(interval=1)) elif varis[y]["format"]: ax.yaxis.set_major_formatter(varis[y]["format"]) if gtff.USE_ION: plt.ion() plt.show() export_data(export, os.path.dirname(os.path.abspath(__file__)), "plot") print("")
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 get_historical_greeks( ticker: str, expiry: str, chain_id: str, strike: float, put: bool ) -> pd.DataFrame: """Get histoical option greeks Parameters ---------- ticker: str Stock ticker expiry: str Option expiration date chain_id: str OCC option symbol. Overwrites other inputs strike: float Strike price to look for put: bool Is this a put option? Returns ------- df: pd.DataFrame Dataframe containing historical greeks """ if not chain_id: options = yfinance_model.get_option_chain(ticker, expiry) if put: options = options.puts else: options = options.calls chain_id = options.loc[options.strike == strike, "contractSymbol"].values[0] r = requests.get(f"https://api.syncretism.io/ops/historical/{chain_id}") if r.status_code != 200: console.print("Error in request.") return pd.DataFrame() history = r.json() iv, delta, gamma, theta, rho, vega, premium, price, time = ( [], [], [], [], [], [], [], [], [], ) for entry in history: time.append(pd.to_datetime(entry["timestamp"], unit="s")) iv.append(entry["impliedVolatility"]) gamma.append(entry["gamma"]) delta.append(entry["delta"]) theta.append(entry["theta"]) rho.append(entry["rho"]) vega.append(entry["vega"]) premium.append(entry["premium"]) price.append(entry["regularMarketPrice"]) data = { "iv": iv, "gamma": gamma, "delta": delta, "theta": theta, "rho": rho, "vega": vega, "premium": premium, "price": price, } df = pd.DataFrame(data, index=time) return df
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)