diff --git a/discordbot/reaction_helper.py b/discordbot/reaction_helper.py new file mode 100644 index 00000000000..551897b201f --- /dev/null +++ b/discordbot/reaction_helper.py @@ -0,0 +1,126 @@ +import asyncio +import discord + +from gamestonk_terminal.config_terminal import TRADIER_TOKEN +from gamestonk_terminal.stocks.options import tradier_model, yfinance_model +from discordbot.run_discordbot import gst_bot +import discordbot.config_discordbot as cfg + + +async def expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg: tuple = None): + if TRADIER_TOKEN == "REPLACE_ME": + dates = yfinance_model.option_expirations(ticker) + else: + dates = tradier_model.option_expirations(ticker) + + index_dates = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + if expiry is not None: + try: + if expiry not in dates: + exp = int(expiry.replace("-", "")) + + if (expiry not in dates) and (exp not in index_dates): + raise Exception("Enter a valid expiration date.") + + if expiry in dates: + if call_arg is None: + await func_cmd(ctx, ticker, expiry) + else: + await func_cmd(ctx, ticker, expiry, *call_arg) + return + if exp in index_dates: + expiry = dates[int(expiry)] + if call_arg is None: + await func_cmd(ctx, ticker, expiry) + else: + await func_cmd(ctx, ticker, expiry, *call_arg) + return + + except Exception as e: + embed = discord.Embed( + title="ERROR Options: Expiry Date", + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + await ctx.send(embed=embed) + return + + if not dates: + embed = discord.Embed( + title="ERROR Options", + colour=cfg.COLOR, + description="Enter a valid stock ticker", + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) + return + + text = ( + "```0️⃣ " + dates[0] + "\n" + "1️⃣ " + dates[1] + "\n" + "2️⃣ " + dates[2] + "\n" + "3️⃣ " + dates[3] + "\n" + "4️⃣ " + dates[4] + "\n" + "5️⃣ " + dates[5] + "\n" + "6️⃣ " + dates[6] + "\n" + "7️⃣ " + dates[7] + "\n" + "8️⃣ " + dates[8] + "\n" + "9️⃣ " + dates[9] + "```" + ) + + title = " " + ticker.upper() + " Options: Expiry Date" + embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + msg = await ctx.send(embed=embed) + + emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"] + + for emoji in emoji_list: + await msg.add_reaction(emoji) + + def check(reaction, user): + return user == ctx.message.author and str(reaction.emoji) in emoji_list + + try: + reaction, _ = await gst_bot.wait_for( + "reaction_add", timeout=cfg.MENU_TIMEOUT, check=check + ) + for N in range(0, 10): + if reaction.emoji == emoji_list[N]: + if cfg.DEBUG: + print(f"Reaction selected: {N}") + expiry = dates[N] + if call_arg is None: + await func_cmd(ctx, ticker, expiry) + else: + await func_cmd(ctx, ticker, expiry, *call_arg) + + for emoji in emoji_list: + await msg.remove_reaction(emoji, ctx.bot.user) + + except asyncio.TimeoutError: + for emoji in emoji_list: + await msg.remove_reaction(emoji, ctx.bot.user) + embed = discord.Embed( + description="Error timeout - you snooze you lose! 😋", + colour=cfg.COLOR, + title="TIMEOUT " + ticker.upper() + " Options: Expiry Date", + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + await ctx.send(embed=embed) diff --git a/discordbot/stocks/options/hist.py b/discordbot/stocks/options/hist.py new file mode 100644 index 00000000000..3ef89591c2c --- /dev/null +++ b/discordbot/stocks/options/hist.py @@ -0,0 +1,103 @@ +import os +import discord +import mplfinance as mpf +import discordbot.config_discordbot as cfg +from gamestonk_terminal.stocks.options import tradier_model + + +async def hist_command( + ctx, ticker: str = None, expiry: str = "", strike: float = None, put: bool = False +): + """Plot historical option prices + + Parameters + ---------- + ticker: str + Stock ticker + expiry: str + accepts 0-9 + 0 being weeklies + 1+ for weeks out + prompts reaction helper if empty + strike: float + Option strike price + put: bool + c for call + p for put + """ + + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.opt.hist {ticker} {strike} {put} {expiry}") + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + if strike is None or put == "": + raise Exception( + 'A strike and c/p is required\n```bash\n"!hist {ticker} {strike} {c/p}"```' + ) + + chain_id = None + + df_hist = tradier_model.get_historical_options( + ticker, expiry, strike, put, chain_id + ) + + op_type = ["call", "put"][put] + + mc = mpf.make_marketcolors( + up="green", down="red", edge="black", wick="black", volume="in", ohlc="i" + ) + + s = mpf.make_mpf_style( + base_mpl_style="seaborn", marketcolors=mc, gridstyle=":", y_on_right=True + ) + + mpf.plot( + df_hist, + type="candle", + volume=True, + title=f"\n{ticker.upper()} {strike} {op_type} expiring {expiry} Historical", + tight_layout=True, + style=s, + figratio=(10, 7), + figscale=1.10, + figsize=(12, 5), + update_width_config=dict( + candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0 + ), + savefig="opt_hist.png", + ) + + imagefile = "opt_hist.png" + image = discord.File(imagefile) + + if cfg.DEBUG: + print(f"Image: {imagefile}") + title = " " + ticker.upper() + " Options: History" + embed = discord.Embed(title=title, colour=cfg.COLOR) + embed.set_image(url="attachment://opt_hist.png") + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + os.remove("opt_hist.png") + + await ctx.send(embed=embed, file=image) + + except Exception as e: + embed = discord.Embed( + title="ERROR Options: History", + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) diff --git a/discordbot/stocks/options/iv.py b/discordbot/stocks/options/iv.py new file mode 100644 index 00000000000..89f825b9655 --- /dev/null +++ b/discordbot/stocks/options/iv.py @@ -0,0 +1,48 @@ +import discord +from tabulate import tabulate + +import discordbot.config_discordbot as cfg +from gamestonk_terminal.stocks.options import barchart_model + + +async def iv_command(ctx, ticker: str = None): + """Options IV""" + + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.opt.iv {ticker}") + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + df = barchart_model.get_options_info(ticker) + tickerr = ticker.upper() + + report = "```" + tabulate(df, tablefmt="fancy_grid", showindex=False) + "```" + embed = discord.Embed( + title=" " + tickerr.__str__() + " Options: IV", + description=report, + colour=cfg.COLOR, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) + + except Exception as e: + embed = discord.Embed( + title="ERROR Options: IV", + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) diff --git a/discordbot/stocks/options/oi.py b/discordbot/stocks/options/oi.py index 25153ab38ad..bd797b4c2ea 100644 --- a/discordbot/stocks/options/oi.py +++ b/discordbot/stocks/options/oi.py @@ -3,31 +3,52 @@ import os import discord import matplotlib.pyplot as plt import pandas as pd -import yfinance as yf import discordbot.config_discordbot as cfg -from discordbot.run_discordbot import gst_imgur from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.options import op_helpers +from gamestonk_terminal.stocks.options import op_helpers, yfinance_model -async def oi_command(ctx, ticker: str = "", expiration_date: str = ""): - """Show open interest for expiration and ticker""" +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: - print(f"!stocks.opt.oi {ticker.upper()} {expiration_date}") + print(f"!stocks.opt.oi {ticker} {expiry} {min_sp} {max_sp}") - # Check for argument - if ticker == "": + # Check for argument + if ticker is None: raise Exception("Stock ticker is required") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + dates = yfinance_model.option_expirations(ticker) - options = yf.Ticker(ticker).option_chain(expiration_date) + 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.90 * current_price + else: + max_strike = max_sp + call_oi = calls.set_index("strike")["openInterest"] / 1000 put_oi = puts.set_index("strike")["openInterest"] / 1000 @@ -36,22 +57,10 @@ async def oi_command(ctx, ticker: str = "", expiration_date: str = ""): columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} ) - df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True).fillna(0) - df_opt = df_opt.rename( - columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} - ) max_pain = op_helpers.calculate_max_pain(df_opt) - current_price = float(yf.Ticker(ticker).info["regularMarketPrice"]) + plt.style.use("seaborn") + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - call_oi.plot( - x="strike", - y="openInterest", - label="Calls", - ax=ax, - marker="o", - ls="-", - c="g", - ) put_oi.plot( x="strike", y="openInterest", @@ -61,37 +70,48 @@ async def oi_command(ctx, ticker: str = "", expiration_date: str = ""): ls="-", c="r", ) - - ax.set_xlabel("Strike Price") - ax.set_ylabel("Open Interest (1k)") + 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.set_title(f"Open Interest for {ticker.upper()} on {expiration_date}") - ax.grid("on") - ax.legend() + 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() - plt.savefig("OI.png") - uploaded_image = gst_imgur.upload_image("OI.png", title="something") - image_link = uploaded_image.link + + imagefile = "opt_oi.png" + plt.savefig("opt_oi.png") + image = discord.File(imagefile) + if cfg.DEBUG: - print(f"Image URL: {image_link}") - title = f"Open Interest for {ticker} on {expiration_date}" + print(f"Image {imagefile}") + title = " " + ticker.upper() + " Options: Open Interest" 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, ) - embed.set_image(url=image_link) - os.remove("OI.png") + os.remove("opt_oi.png") - await ctx.send(embed=embed) + await ctx.send(embed=embed, file=image) except Exception as e: embed = discord.Embed( - title="ERROR Stock-Options: Open Interest", + title="ERROR Options: Open Interest", colour=cfg.COLOR, description=e, ) diff --git a/discordbot/stocks/options/options_menu.py b/discordbot/stocks/options/options_menu.py index 67b2469a426..974d13ffa7e 100644 --- a/discordbot/stocks/options/options_menu.py +++ b/discordbot/stocks/options/options_menu.py @@ -1,21 +1,58 @@ import asyncio - import discord import discordbot.config_discordbot as cfg from discordbot.run_discordbot import gst_bot +from discordbot.reaction_helper import expiry_dates_reaction + from discordbot.stocks.options.calls import calls_command from discordbot.stocks.options.expirations import expirations_command +from discordbot.stocks.options.hist import hist_command +from discordbot.stocks.options.iv import iv_command from discordbot.stocks.options.oi import oi_command from discordbot.stocks.options.puts import puts_command +from discordbot.stocks.options.unu import unu_command +from discordbot.stocks.options.vol import vol_command class OptionsCommands(discord.ext.commands.Cog): - """Dark Pool Shorts menu""" + """Options menu.""" def __init__(self, bot: discord.ext.commands.Bot): self.bot = bot + @discord.ext.commands.command(name="stocks.opt.iv", usage="[ticker]") + async def iv(self, ctx: discord.ext.commands.Context, ticker: str = None): + """Displays ticker options IV [Barchart] + + Parameters + ----------- + ticker: str + ticker, + """ + async with ctx.typing(): + await asyncio.sleep(0.2) + + await iv_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.opt.unu", usage="[ticker]") + async def unu( + self, + ctx: discord.ext.commands.Context, + num: int = None, + ): + """Unusual Options + + Parameters + ----------- + ticker: str + ticker + """ + async with ctx.typing(): + await asyncio.sleep(0.2) + + await unu_command(ctx, num) + @discord.ext.commands.command(name="stocks.opt.exp", usage="[ticker]") async def expirations(self, ctx: discord.ext.commands.Context, ticker=""): """Get available expirations [yfinance] @@ -28,10 +65,11 @@ class OptionsCommands(discord.ext.commands.Cog): await expirations_command(ctx, ticker) @discord.ext.commands.command( - name="stocks.opt.calls", usage="[ticker] [expiration]" + name="stocks.opt.calls", + usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]", ) async def calls( - self, ctx: discord.ext.commands.Context, ticker="", expiration: str = "" + self, ctx: discord.ext.commands.Context, ticker: str = None, expiry: str = None ): """Get call options for ticker and given expiration @@ -39,14 +77,27 @@ class OptionsCommands(discord.ext.commands.Cog): ---------- ticker: str Stock ticker - expiration: str - Expiration date + expiry: str + accepts 0-9 or YYYY-MM-DD + 0 weeklies, 1+ for weeks out + prompts reaction helper if empty """ - await calls_command(ctx, ticker, expiration) + async with ctx.typing(): + await asyncio.sleep(0.2) - @discord.ext.commands.command(name="stocks.opt.puts", usage="[ticker] [expiration]") + if cfg.DEBUG: + print(f"!stocks.opt.calls {ticker} {expiry}") + + func_cmd = calls_command + + await expiry_dates_reaction(ctx, ticker, expiry, func_cmd) + + @discord.ext.commands.command( + name="stocks.opt.puts", + usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]", + ) async def puts( - self, ctx: discord.ext.commands.Context, ticker="", expiration: str = "" + self, ctx: discord.ext.commands.Context, ticker: str = None, expiry: str = None ): """Get put options for ticker and given expiration @@ -54,25 +105,160 @@ class OptionsCommands(discord.ext.commands.Cog): ---------- ticker: str Stock ticker - expiration: str - Expiration date + expiry: str + accepts 0-9 or YYYY-MM-DD + 0 weeklies, 1+ for weeks out + prompts reaction helper if empty """ - await puts_command(ctx, ticker, expiration) + async with ctx.typing(): + await asyncio.sleep(0.2) - @discord.ext.commands.command(name="stocks.opt.oi", usage="[ticker] [expiration]") + if cfg.DEBUG: + print(f"!stocks.opt.puts {ticker} {expiry}") + + func_cmd = puts_command + + await expiry_dates_reaction(ctx, ticker, expiry, func_cmd) + + @discord.ext.commands.command( + name="stocks.opt.oi", + usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD] [min strike] [max strike]", + ) async def oi( - self, ctx: discord.ext.commands.Context, ticker="", expiration: str = "" + self, + ctx: discord.ext.commands.Context, + ticker: str = None, + expiry: str = None, + min_sp: float = None, + max_sp: float = None, ): - """Get put options for ticker and given expiration + """Display options open interest for ticker and given expiration [Max Pain] Parameters - ---------- + ----------- ticker: str - Stock ticker - expiration: str - Expiration date + ticker + strike: float + strike + expiry: str + accepts 0-9 or YYYY-MM-DD + min_sp: float + min strike price + max_sp:float + max strike price + + Sends a message to the discord user with the expiry dates if empty. + The user can then select a reaction to trigger the selected date. """ - await oi_command(ctx, ticker, expiration) + async with ctx.typing(): + await asyncio.sleep(0.2) + + if cfg.DEBUG: + print(f"!stocks.opt.oi {ticker} {expiry} {min_sp} {max_sp}") + + call_arg = (min_sp, max_sp) + func_cmd = oi_command + + await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg) + + @discord.ext.commands.command( + name="stocks.opt.hist", + usage="[ticker] [strike] [c or p (call/put)] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]", + ) + async def hist( + self, + ctx: discord.ext.commands.Context, + ticker: str = None, + strike: float = None, + put="", + expiry: str = None, + ): + """Display chart of given option historical price [Tradier] + + Parameters + ----------- + ticker: str + ticker + strike: float + strike + put: bool + c for call + p for put + expiry: str + accepts 0-9 or YYYY-MM-DD + + Sends a message to the discord user with the expiry dates if empty. + The user can then select a reaction to trigger the selected date. + """ + async with ctx.typing(): + await asyncio.sleep(0.2) + + if cfg.DEBUG: + print(f"!stocks.opt.hist {ticker} {strike} {put} {expiry}") + + if strike is None or put == "": + embed = discord.Embed( + title="ERROR Options: History", + colour=cfg.COLOR, + description='A strike and c/p is required\n```bash\n"!stocks.opt.hist {ticker} {strike} {c/p}"```', + ) + + await ctx.send(embed=embed) + return + + if put == "p": + put = bool(True) + if put == "c": + put = bool(False) + + call_arg = ( + strike, + put, + ) + func_cmd = hist_command + + await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg) + + @discord.ext.commands.command( + name="stocks.opt.vol", + usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD] [min strike] [max strike]", + ) + async def vol( + self, + ctx: discord.ext.commands.Context, + ticker: str = None, + expiry: str = None, + min_sp: float = None, + max_sp: float = None, + ): + """Display options volume for ticker and given expiration + + Parameters + ----------- + ticker: str + ticker + strike: float + strike + expiry: str + accepts 0-9 or YYYY-MM-DD + min_sp: float + min strike price + max_sp:float + max strike price + + Sends a message to the discord user with the expiry dates if empty. + The user can then select a reaction to trigger the selected date. + """ + async with ctx.typing(): + await asyncio.sleep(0.2) + + if cfg.DEBUG: + print(f"!stocks.opt.vol {ticker} {expiry} {min_sp} {max_sp}") + + call_arg = (min_sp, max_sp) + func_cmd = vol_command + + await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg) @discord.ext.commands.command(name="stocks.opt") async def opt(self, ctx: discord.ext.commands.Context, ticker="", expiration=""): @@ -161,7 +347,7 @@ class OptionsCommands(discord.ext.commands.Cog): embed = discord.Embed( description="Error timeout - you snooze you lose! 😋", colour=cfg.COLOR, - title="TIMEOUT Stocks: Government (GOV) Menu", + title="TIMEOUT Stocks: Options Menu", ).set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/options/unu.py b/discordbot/stocks/options/unu.py new file mode 100644 index 00000000000..e1ebe813dec --- /dev/null +++ b/discordbot/stocks/options/unu.py @@ -0,0 +1,109 @@ +import discord +import numpy as np +import pandas as pd +import requests +from tabulate import tabulate + +import discordbot.config_discordbot as cfg +from gamestonk_terminal.helper_funcs import get_user_agent + + +async def unu_command(ctx, num: int = None): + """Unusual Options""" + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.opt.unu {num}") + + # Check for argument + if num is None: + num = 10 + + pages = np.arange(0, num // 20 + 1) + data_list = [] + for page_num in pages: + + r = requests.get( + f"https://app.fdscanner.com/api2/unusualvolume?p=0&page_size=20&page={int(page_num)}", + headers={"User-Agent": get_user_agent()}, + ) + + if r.status_code != 200: + print("Error in fdscanner request") + return pd.DataFrame(), "request error" + + data_list.append(r.json()) + + ticker, expiry, option_strike, option_type, ask, bid, oi, vol, voi = ( + [], + [], + [], + [], + [], + [], + [], + [], + [], + ) + for data in data_list: + for entry in data["data"]: + ticker.append(entry["tk"]) + expiry.append(entry["expiry"]) + option_strike.append(float(entry["s"])) + option_type.append("Put" if entry["t"] == "P" else "Call") + ask.append(entry["a"]) + bid.append(entry["b"]) + oi.append(entry["oi"]) + vol.append(entry["v"]) + voi.append(entry["vol/oi"]) + + df = pd.DataFrame( + { + "Ticker": ticker, + "Exp": expiry, + "Strike": option_strike, + "Type": option_type, + "Vol/OI": voi, + "Vol": vol, + "OI": oi, + } + ) + + df = df.replace({"2021-", "2022-"}, "", regex=True) + + report = ( + "```" + + tabulate( + df, + headers=["T", "Exp", "ST", "C/P", "V/O", "Vol", "OI"], + tablefmt="fancy_grid", + showindex=False, + floatfmt=["", "", ".1f", "", ".1f", ".0f", ".0f", ".2f", ".2f"], + ) + + "```" + ) + embed = discord.Embed( + title="Unusual Options", + description=report, + colour=cfg.COLOR, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) + + except Exception as e: + embed = discord.Embed( + title="ERROR Unusual Options", + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) diff --git a/discordbot/stocks/options/vol.py b/discordbot/stocks/options/vol.py new file mode 100644 index 00000000000..acfb4109f20 --- /dev/null +++ b/discordbot/stocks/options/vol.py @@ -0,0 +1,113 @@ +import os +import discord +import matplotlib.pyplot as plt +import discordbot.config_discordbot as cfg + +from gamestonk_terminal import config_plot as cfp +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.options import yfinance_model + + +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"!stocks.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.90 * 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: + print(f"Image: {imagefile}") + title = " " + ticker.upper() + " Options: Volume" + 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)