diff --git a/discordbot/discordbot.py b/discordbot/discordbot.py index ef824d81eac..d2079f39483 100644 --- a/discordbot/discordbot.py +++ b/discordbot/discordbot.py @@ -34,6 +34,7 @@ gst_bot.load_extension("generic_commands") gst_bot.load_extension("economy.economy_menu") gst_bot.load_extension("stocks.dark_pool_shorts.dps_menu") gst_bot.load_extension("stocks.technical_analysis.ta_menu") +gst_bot.load_extension("stocks.due_diligence.dd_menu") # Get all command names all_cmds = gst_bot.all_commands.keys() diff --git a/discordbot/economy/economy_menu.py b/discordbot/economy/economy_menu.py index e5bea051119..7e7f073bf3c 100644 --- a/discordbot/economy/economy_menu.py +++ b/discordbot/economy/economy_menu.py @@ -177,11 +177,17 @@ class EconomyCommands(discord.ext.commands.Cog): await msg.remove_reaction(emoji, ctx.bot.user) except asyncio.TimeoutError: - text = text + "\n\nCOMMAND TIMEOUT." - embed = discord.Embed(title=title, description=text) - await msg.edit(embed=embed) 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 Economy Menu", + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + await ctx.send(embed=embed) def setup(bot: discord.ext.commands.Bot): diff --git a/discordbot/stocks/dark_pool_shorts/dps_menu.py b/discordbot/stocks/dark_pool_shorts/dps_menu.py index 2a7707de3af..47ce892cf0c 100644 --- a/discordbot/stocks/dark_pool_shorts/dps_menu.py +++ b/discordbot/stocks/dark_pool_shorts/dps_menu.py @@ -169,7 +169,7 @@ class DarkPoolShortsCommands(discord.ext.commands.Cog): "\n!stocks.dps " ) - title = "Dark Pool Shorts (DPS) Menu" + title = "Stocks: Dark Pool Shorts (DPS) Menu" embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) embed.set_author( name=cfg.AUTHOR_NAME, @@ -229,11 +229,17 @@ class DarkPoolShortsCommands(discord.ext.commands.Cog): await msg.remove_reaction(emoji, ctx.bot.user) except asyncio.TimeoutError: - text = text + "\n\nCommand timeout." - embed = discord.Embed(title=title, description=text) - await msg.edit(embed=embed) 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 Stocks: Dark Pool Shorts (DPS) Menu", + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + await ctx.send(embed=embed) def setup(bot: discord.ext.commands.Bot): diff --git a/discordbot/stocks/due_diligence/analyst.py b/discordbot/stocks/due_diligence/analyst.py new file mode 100644 index 00000000000..df5ca186c17 --- /dev/null +++ b/discordbot/stocks/due_diligence/analyst.py @@ -0,0 +1,46 @@ +import discord +import config_discordbot as cfg + +from gamestonk_terminal.stocks.due_diligence import finviz_model + + +async def analyst_command(ctx, ticker=""): + """Displays analyst recommendations [Finviz]""" + + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.dd.analyst {ticker}") + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + df = finviz_model.get_analyst_data(ticker) + + report = "```" + df.to_string() + "```" + embed = discord.Embed( + title="Stocks: [Finviz] Analyst Recommendations", + 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 Stocks: [Finviz] Analyst Recommendations", + 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/due_diligence/arktrades.py b/discordbot/stocks/due_diligence/arktrades.py new file mode 100644 index 00000000000..9d8efa72a75 --- /dev/null +++ b/discordbot/stocks/due_diligence/arktrades.py @@ -0,0 +1,95 @@ +import discord +import config_discordbot as cfg +from helpers import pagination +import pandas as pd + +from gamestonk_terminal.stocks.due_diligence import ark_model + + +async def arktrades_command(ctx, ticker="", num=""): + """Displays trades made by ark [cathiesark.com]""" + + try: + # Debug user input + if cfg.DEBUG: + print(f"!stocks.dd.arktrades{ticker}") + + if num == "": + pass + else: + if not num.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + num = float(num) + + if ticker == "": + raise Exception("A ticker is required") + + ark_holdings = ark_model.get_ark_trades_by_ticker(ticker) + + if ark_holdings.empty: + raise Exception( + "Issue getting data from cathiesark.com. Likely no trades found.\n" + ) + + ark_holdings = ark_holdings.drop(columns=["ticker"]) + ark_holdings["Total"] = ark_holdings["Total"] / 1_000_000 + ark_holdings.rename( + columns={"Close": "Close ($)", "Total": "Total ($1M)"}, inplace=True + ) + + ark_holdings.index = pd.Series(ark_holdings.index).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + + if num == "": + ark_holdings_str = ark_holdings.to_string() + else: + ark_holdings_str = ark_holdings.head(num).to_string() + + if len(ark_holdings_str) <= 4000: + embed = discord.Embed( + title=f"Stocks: [cathiesark.com] {ticker} Trades by Ark", + description="```" + ark_holdings_str + "```", + colour=cfg.COLOR, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed) + + else: + i = 0 + str_start = 0 + str_end = 4000 + columns = [] + while i <= len(ark_holdings_str) / 4000: + columns.append( + discord.Embed( + title=f"Stocks: [cathiesark.com] {ticker} Trades by Ark", + description="```" + ark_holdings_str[str_start:str_end] + "```", + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + str_end = str_start + str_start += 4000 + i += 1 + + await pagination(columns, ctx) + + except Exception as e: + embed = discord.Embed( + title=f"ERROR Stocks: [cathiesark.com] {ticker} Trades by Ark", + 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/due_diligence/customer.py b/discordbot/stocks/due_diligence/customer.py new file mode 100644 index 00000000000..17405f7955a --- /dev/null +++ b/discordbot/stocks/due_diligence/customer.py @@ -0,0 +1,51 @@ +import discord +import config_discordbot as cfg + +from gamestonk_terminal.stocks.due_diligence import csimarket_model + + +async def customer_command(ctx, ticker=""): + """Displays customers of the company [CSIMarket]""" + + try: + # Debug user input + if cfg.DEBUG: + print(f"!stocks.dd.customer {ticker}") + + if ticker == "": + raise Exception("A ticker is required") + + tickers = csimarket_model.get_customers(ticker) + + if tickers == "": + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + print(tickers) + + # Output data + embed = discord.Embed( + title="Stocks: [CSIMarket] Company Customers", + description=tickers, + 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 Stocks: [CSIMarket] Company Customers", + 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/due_diligence/dd_menu.py b/discordbot/stocks/due_diligence/dd_menu.py new file mode 100644 index 00000000000..c189c6bc3f8 --- /dev/null +++ b/discordbot/stocks/due_diligence/dd_menu.py @@ -0,0 +1,205 @@ +import discord +from discordbot import gst_bot +import config_discordbot as cfg +import asyncio + +from stocks.due_diligence.analyst import analyst_command +from stocks.due_diligence.pt import pt_command +from stocks.due_diligence.est import est_command +from stocks.due_diligence.sec import sec_command +from stocks.due_diligence.supplier import supplier_command +from stocks.due_diligence.customer import customer_command +from stocks.due_diligence.arktrades import arktrades_command + + +class DueDiligenceCommands(discord.ext.commands.Cog): + """Due Diligence menu.""" + + def __init__(self, bot: discord.ext.commands.Bot): + self.bot = bot + + @discord.ext.commands.command(name="stocks.dd.analyst") + async def analyst(self, ctx: discord.ext.commands.Context, ticker=""): + """Displays analyst recommendations [Finviz] + + Parameters + ----------- + ticker: str + ticker, -h or help + """ + await analyst_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.dd.pt") + async def pt(self, ctx: discord.ext.commands.Context, ticker="", raw="", start=""): + """Displays chart with price targets [Business Insiders] + + Parameters + ----------- + ticker: str + ticker, -h or help + raw: boolean + True or false + start: + date (in date format for start date) + """ + await pt_command(ctx, ticker, raw, start) + + @discord.ext.commands.command(name="stocks.dd.est") + async def est(self, ctx: discord.ext.commands.Context, ticker=""): + """Displays earning estimates [Business Insider] + + Parameters + ----------- + ticker: str + ticker, -h or help + """ + await est_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.dd.sec") + async def sec(self, ctx: discord.ext.commands.Context, ticker=""): + """Displays sec filings [Market Watch] + + Parameters + ----------- + ticker: str + ticker, -h or help + """ + await sec_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.dd.supplier") + async def supplier(self, ctx: discord.ext.commands.Context, ticker=""): + """Displays suppliers of the company [CSIMarket] + + Parameters + ----------- + ticker: str + ticker, -h or help + """ + await supplier_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.dd.customer") + async def customer(self, ctx: discord.ext.commands.Context, ticker=""): + """Displays customers of the company [CSIMarket] + + Parameters + ----------- + ticker: str + ticker, -h or help + """ + await customer_command(ctx, ticker) + + @discord.ext.commands.command(name="stocks.dd.arktrades") + async def arktrades(self, ctx: discord.ext.commands.Context, ticker="", num=""): + """Displays trades made by ark [cathiesark.com] + + Parameters + ----------- + ticker: str + ticker, -h or help + num: int + number of rows displayed + """ + await arktrades_command(ctx, ticker, num) + + @discord.ext.commands.command(name="stocks.dd") + async def economy(self, ctx: discord.ext.commands.Context, ticker=""): + """Economy Context Menu + + Returns + ------- + Sends a message to the discord user with the commands from the dd context. + The user can then select a reaction to trigger a command. + """ + + if cfg.DEBUG: + print("!stocks.dd") + + if ticker == "": + embed = discord.Embed( + title="ERROR Stocks: Due Diligence (DD) Menu", + colour=cfg.COLOR, + description="A stock ticker is required", + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + text = ( + f"0️⃣ !stocks.dd.analyst {ticker}\n" + f"1️⃣ !stocks.dd.pt {ticker} \n" + f"2️⃣ !stocks.dd.est {ticker}\n" + f"3️⃣ !stocks.dd.sec {ticker}\n" + f"4️⃣ !stocks.dd.supplier {ticker}\n" + f"5️⃣ !stocks.dd.customer {ticker}\n" + f"6️⃣ !stocks.dd.arktrades {ticker}" + ) + + title = "Stocks: Due Diligence (DD) Menu" + embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + msg = await ctx.send(embed=embed) + + emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣"] + + for emoji in emoji_list: + await msg.add_reaction(emoji) + + def check(reaction, user): + return user == ctx.message.author and str(reaction.emoji) in emoji_list + + try: + reaction, _ = await gst_bot.wait_for( + "reaction_add", timeout=cfg.MENU_TIMEOUT, check=check + ) + if reaction.emoji == "0️⃣": + if cfg.DEBUG: + print("Reaction selected: 0") + await analyst_command(ctx, ticker) + elif reaction.emoji == "1️⃣": + if cfg.DEBUG: + print("Reaction selected: 1") + await pt_command(ctx, ticker) + elif reaction.emoji == "2️⃣": + if cfg.DEBUG: + print("Reaction selected: 2") + await est_command(ctx, ticker) + elif reaction.emoji == "3️⃣": + if cfg.DEBUG: + print("Reaction selected: 3") + await sec_command(ctx, ticker) + elif reaction.emoji == "4️⃣": + if cfg.DEBUG: + print("Reaction selected: 4") + await supplier_command(ctx, ticker) + elif reaction.emoji == "5️⃣": + if cfg.DEBUG: + print("Reaction selected: 5") + await customer_command(ctx, ticker) + elif reaction.emoji == "6️⃣": + if cfg.DEBUG: + print("Reaction selected: 6") + await arktrades_command(ctx, ticker) + + 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 Stocks: Due Diligence (DD) Menu", + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + await ctx.send(embed=embed) + + +def setup(bot: discord.ext.commands.Bot): + gst_bot.add_cog(DueDiligenceCommands(bot)) diff --git a/discordbot/stocks/due_diligence/est.py b/discordbot/stocks/due_diligence/est.py new file mode 100644 index 00000000000..edecdb05914 --- /dev/null +++ b/discordbot/stocks/due_diligence/est.py @@ -0,0 +1,83 @@ +import discord +import config_discordbot as cfg +from helpers import pagination + +from gamestonk_terminal.stocks.due_diligence import business_insider_model + + +async def est_command(ctx, ticker=""): + """Displays earning estimates [Business Insider]""" + + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.dd.est {ticker}") + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ( + df_year_estimates, + df_quarter_earnings, + df_quarter_revenues, + ) = business_insider_model.get_estimates(ticker) + + if ( + df_quarter_revenues.empty + and df_year_estimates.empty + and df_quarter_earnings.empty + ): + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + print(df_year_estimates.to_string()) + print(df_quarter_earnings.to_string()) + print(df_quarter_revenues.to_string()) + + # Output data + cols = [] + initial_text = ( + "´´´Page 0: Overview\nPage 1: Year Estimates\n" + "Page 2: Quarter Earnings\nPage 3: Quarter Revenues´´´" + ) + text = "´´´" + df_year_estimates.to_string() + "´´´" + cols.append(text) + text = "´´´" + df_quarter_earnings.to_string() + "´´´" + cols.append(text) + text = "´´´" + df_quarter_revenues.to_string() + "´´´" + cols.append(text) + columns = [] + columns.append( + discord.Embed( + title="Stocks: [Business Insider] Earning Estimates", + description=initial_text, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for text in cols: + columns.append( + discord.Embed(description=text, colour=cfg.COLOR,).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + + await pagination(columns, ctx) + except Exception as e: + embed = discord.Embed( + title="ERROR Stocks: [Business Insider] Earning Estimates", + 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/due_diligence/pt.py b/discordbot/stocks/due_diligence/pt.py new file mode 100644 index 00000000000..247c62f2aab --- /dev/null +++ b/discordbot/stocks/due_diligence/pt.py @@ -0,0 +1,104 @@ +import discord +import config_discordbot as cfg +from discordbot import gst_imgur +from matplotlib import pyplot as plt +from datetime import datetime, timedelta +import os +import helpers + +from gamestonk_terminal.stocks.due_diligence import business_insider_model + + +async def pt_command(ctx, ticker="", raw="", start=""): + """Displays price targets [Business Insider]""" + + try: + + # Debug + if cfg.DEBUG: + print(f"!stocks.dd.pt {ticker}") + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if raw == "false" or raw == "False" or raw == "FALSE" or raw == "": + raw = False + elif raw == "true" or raw == "True" or raw == "TRUE": + raw = True + else: + raise Exception("raw argument has to be true or false") + + df_analyst_data = business_insider_model.get_price_target_from_analysts(ticker) + stock = helpers.load(ticker, start) + + if df_analyst_data.empty or stock.empty: + raise Exception("Enter valid ticker") + + # Output Data + + if raw: + df_analyst_data.sort_index(ascending=False) + report = "´´´" + df_analyst_data.to_string() + "´´´" + embed = discord.Embed( + title="Stocks: [Business Insider] Price Targets", + description=report, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ctx.send(embed=embed) + else: + if start: + df_analyst_data = df_analyst_data[start:] + + plt.plot(stock.index, stock["Adj Close"].values, lw=3) + + plt.plot(df_analyst_data.groupby(by=["Date"]).mean()) + + plt.scatter( + df_analyst_data.index, df_analyst_data["Price Target"], c="r", s=40 + ) + + plt.legend(["Closing Price", "Average Price Target", "Price Target"]) + + plt.title(f"{ticker} (Time Series) and Price Target") + plt.xlim(stock.index[0], stock.index[-1]) + plt.xlabel("Time") + plt.ylabel("Share Price") + plt.grid(b=True, which="major", color="#666666", linestyle="-") + + plt.savefig("ta_pt.png") + uploaded_image = gst_imgur.upload_image("ta_pt.png", title="something") + image_link = uploaded_image.link + if cfg.DEBUG: + print(f"Image URL: {image_link}") + title = "Stocks: [Business Insider] Price Targets " + ticker + embed = discord.Embed(title=title, colour=cfg.COLOR) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + embed.set_image(url=image_link) + os.remove("ta_pt.png") + + await ctx.send(embed=embed) + + except Exception as e: + embed = discord.Embed( + title="ERROR Stocks: [Business Insider] Price Targets", + 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/due_diligence/sec.py b/discordbot/stocks/due_diligence/sec.py new file mode 100644 index 00000000000..30882963a25 --- /dev/null +++ b/discordbot/stocks/due_diligence/sec.py @@ -0,0 +1,52 @@ +import discord +import config_discordbot as cfg + +from gamestonk_terminal.stocks.due_diligence import marketwatch_model + + +async def sec_command(ctx, ticker=""): + """Displays sec filings [Market Watch]""" + + try: + # Debug user input + if cfg.DEBUG: + print(f"!stocks.dd.sec {ticker}") + + if ticker == "": + raise Exception("A ticker is required") + + df_financials = marketwatch_model.get_sec_filings(ticker) + + if df_financials.empty: + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + print(df_financials.to_string()) + + # Output data + report = "```" + df_financials.to_string() + "```" + embed = discord.Embed( + title="Stocks: [Market Watch] SEC Filings", + 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 Stocks: [Market Watch] SEC Filings", + 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/due_diligence/supplier.py b/discordbot/stocks/due_diligence/supplier.py new file mode 100644 index 00000000000..5e9222969e6 --- /dev/null +++ b/discordbot/stocks/due_diligence/supplier.py @@ -0,0 +1,51 @@ +import discord +import config_discordbot as cfg + +from gamestonk_terminal.stocks.due_diligence import csimarket_model + + +async def supplier_command(ctx, ticker=""): + """Displays suppliers of the company [CSIMarket]""" + + try: + # Debug user input + if cfg.DEBUG: + print(f"!stocks.dd.supplier {ticker}") + + if ticker == "": + raise Exception("A ticker is required") + + tickers = csimarket_model.get_suppliers(ticker) + + if tickers == "": + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + print(tickers) + + # Output data + embed = discord.Embed( + title="Stocks: [CSIMarket] Company Suppliers", + description=tickers, + 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 Stocks: [CSIMarket] Company Suppliers", + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await ctx.send(embed=embed)