def display_mean_std(name: str, df: pd.DataFrame, target: str, length: int, export: str = ""): """View rolling spread Parameters ---------- name : str Stock ticker df : pd.DataFrame Dataframe target : str Column in data to look at length : int Length of window export : str Format to export data """ data = df[target] rolling_mean, rolling_std = rolling_model.get_rolling_avg(data, length) fig, axMean = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) axMean.plot( data.index, data.values, label=name, linewidth=2, color="black", ) axMean.plot(rolling_mean, linestyle="--", linewidth=3, color="blue") axMean.set_xlabel("Time") axMean.set_ylabel("Values", color="blue") axMean.legend(["Real values", "Rolling Mean"], loc=2) axMean.tick_params(axis="y", labelcolor="blue") axStd = axMean.twinx() axStd.plot( rolling_std, label="Rolling std", linestyle="--", color="green", linewidth=3, alpha=0.6, ) axStd.set_ylabel("Std Deviation") axStd.legend(["Rolling std"], loc=1) axStd.set_ylabel(f"{target} standard deviation", color="green") axStd.tick_params(axis="y", labelcolor="green") axMean.set_title("Rolling mean and std with window " + str(length) + " applied to " + name + target) axMean.set_xlim([data.index[0], data.index[-1]]) axMean.grid(b=True, which="major", color="#666666", linestyle="-") if gtff.USE_ION: plt.ion() fig.tight_layout(pad=1) plt.gcf().autofmt_xdate() plt.show() console.print("") export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "rolling", rolling_mean.join(rolling_std, lsuffix="_mean", rsuffix="_sd"), )
def display_spread(name: str, df: pd.DataFrame, target: str, length: int, export: str = ""): """View rolling spread Parameters ---------- name : str Stock ticker df : pd.DataFrame Dataframe target: str Column in data to look at length : int Length of window export : str Format to export data """ data = df[target] df_sd, df_var = rolling_model.get_spread(data, length) fig, axes = plt.subplots(3, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) ax = axes[0] ax.set_title(f"{name} Spread") ax.plot(data.index, data.values, "fuchsia", lw=1) ax.set_xlim(data.index[0], data.index[-1]) ax.set_ylabel("Value") ax.set_title(f"Spread of {name} {target}") ax.yaxis.set_label_position("right") ax.grid(b=True, which="major", color="#666666", linestyle="-") ax.minorticks_on() ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) ax1 = axes[1] ax1.plot(df_sd.index, df_sd.values, "b", lw=1, label="stdev") ax1.set_xlim(data.index[0], data.index[-1]) ax1.set_ylabel("Stdev") ax1.yaxis.set_label_position("right") ax1.grid(b=True, which="major", color="#666666", linestyle="-") ax1.minorticks_on() ax1.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) ax2 = axes[2] ax2.plot(df_var.index, df_var.values, "g", lw=1, label="variance") ax2.set_xlim(data.index[0], data.index[-1]) ax2.set_ylabel("Variance") ax2.yaxis.set_label_position("right") ax2.grid(b=True, which="major", color="#666666", linestyle="-") if gtff.USE_ION: plt.ion() plt.gcf().autofmt_xdate() fig.tight_layout(pad=1) plt.show() console.print("") export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "spread", df_sd.join(df_var, lsuffix="_sd", rsuffix="_var"), )
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 display_luna_circ_supply_change( days: int, export: str, supply_type: str = LUNA_CIR_SUPPLY_CHANGE, limit: int = 5, external_axes: Optional[List[plt.Axes]] = None, ): """Display Luna circulating supply stats Parameters ---------- days: int Number of days supply_type: str Supply type to unpack json export: str Export type limit: int Number of results display on the terminal Default: 5 external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None Returns None ------- """ df = smartstake_model.get_luna_supply_stats(supply_type, days) if df.empty: return # This plot has 1 axis if not external_axes: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=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 ax.plot( df.index, df["circulatingSupplyInMil"], c="black", label="Circulating Supply", ) ax.plot( df.index, df["liquidCircSupplyInMil"], c="red", label="Liquid Circulating Supply", ) ax.plot(df.index, df["stakeFromCircSupplyInMil"], c="green", label="Stake of Supply") ax.plot( df.index, df["recentTotalLunaBurntInMil"], c="blue", label="Supply Reduction (Luna Burnt)", ) ax.grid() ax.set_ylabel("Millions") ax.set_xlabel("Time") ax.set_title("Luna Circulating Supply Changes (In Millions)") ax.set_xlim(df.index[0], df.index[-1]) ax.legend(loc="best") theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() RAW_COLS = [ "circulatingSupplyInMil", "liquidCircSupplyInMil", "circSupplyChangeInMil", "recentTotalLunaBurntInMil", ] export_data( export, os.path.dirname(os.path.abspath(__file__)), "lcsc", df[RAW_COLS], ) df.index = df.index.strftime("%Y-%m-%d") df = df.sort_index(ascending=False) print_rich_table( df[RAW_COLS].head(limit), headers=[ "Circ Supply", "Liquid Circ Supply", "Supply Change", "Supply Reduction (Luna Burnt)", ], show_index=True, index_name="Time", title="Luna Circulating Supply Changes (in Millions)", )
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_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 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 display_fred_series( d_series: Dict[str, Dict[str, str]], start_date: str, raw: bool = False, export: str = "", limit: int = 10, external_axes: Optional[List[plt.Axes]] = None, ): """Display (multiple) series from https://fred.stlouisfed.org. [Source: FRED] Parameters ---------- series : str FRED Series ID from https://fred.stlouisfed.org. For multiple series use: series1,series2,series3 start_date : str Starting date (YYYY-MM-DD) of data raw : bool Output only raw data export : str Export data to csv,json,xlsx or png,jpg,pdf,svg file limit: int Number of raw data rows to show external_axes : Optional[List[plt.Axes]], optional External axes (3 axes are expected in the list), by default None """ series_ids = list(d_series.keys()) data = pd.DataFrame() for s_id in series_ids: data = pd.concat( [ data, pd.DataFrame(fred_model.get_series_data(s_id, start_date), columns=[s_id]), ], axis=1, ) data = data.dropna() # Try to get everything onto the same 0-10 scale. # To do so, think in scientific notation. Divide the data by whatever the E would be if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: if len(external_axes) != 3: console.print("[red]Expected list of 3 axis items./n[/red]") return (ax, ) = external_axes if len(series_ids) == 1: s_id = series_ids[0] sub_dict: Dict = d_series[s_id] title = f"{sub_dict['title']} ({sub_dict['units']})" ax.plot(data.index, data, label="\n".join(textwrap.wrap(title, 80))) else: for s_id, sub_dict in d_series.items(): data_to_plot = data[s_id].dropna() exponent = int(np.log10(data_to_plot.max())) data_to_plot /= 10**exponent multiplier = f"x {format_units(10**exponent)}" if exponent > 0 else "" title = f"{sub_dict['title']} ({sub_dict['units']}) {'['+multiplier+']' if multiplier else ''}" ax.plot( data_to_plot.index, data_to_plot, label="\n".join(textwrap.wrap(title, 80)) if len(series_ids) < 5 else title, ) ax.legend(prop={"size": 10}, bbox_to_anchor=(0, 1), loc="lower left") if data.empty: console.print("[red]No data[/red]\n") else: ax.set_xlim(data.index[0], data.index[-1]) theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() data.index = [x.strftime("%Y-%m-%d") for x in data.index] if raw: print_rich_table( data.tail(limit), headers=list(data.columns), show_index=True, index_name="Date", ) console.print("") export_data( export, os.path.dirname(os.path.abspath(__file__)), "plot", data, )
def upcoming_earning_release_dates(num_pages: int, num_earnings: int, export: str): """Displays upcoming earnings release dates Parameters ---------- num_pages: int Number of pages to scrap num_earnings: int Number of upcoming earnings release dates export : str Export dataframe data to csv,json,xlsx file """ # TODO: Check why there are repeated companies # TODO: Create a similar command that returns not only upcoming, but antecipated earnings # i.e. companies where expectation on their returns are high df_earnings = seeking_alpha_model.get_next_earnings(num_pages) pd.set_option("display.max_colwidth", None) if export: l_earnings = [] l_earnings_dates = [] for n_days, earning_date in enumerate(df_earnings.index.unique()): if n_days > (num_earnings - 1): break # TODO: Potentially extract Market Cap for each Ticker, and sort # by Market Cap. Then cut the number of tickers shown to 10 with # bigger market cap. Didier attempted this with yfinance, but # the computational time involved wasn't worth pursuing that solution. df_earn = df_earnings[earning_date == df_earnings.index][[ "Ticker", "Name" ]].dropna() if export: l_earnings_dates.append(earning_date.date()) l_earnings.append(df_earn) df_earn.index = df_earn["Ticker"].values df_earn.drop(columns=["Ticker"], inplace=True) print_rich_table( df_earn, show_index=True, headers=[f"Earnings on {earning_date.date()}"], title="Upcoming Earnings Releases", ) if export: for i, _ in enumerate(l_earnings): l_earnings[i].reset_index(drop=True, inplace=True) df_data = pd.concat(l_earnings, axis=1, ignore_index=True) df_data.columns = l_earnings_dates export_data( export, os.path.dirname(os.path.abspath(__file__)), "upcoming", df_data, )