From 2d14890c41ebbfe0e0eec88e27e940375383514e Mon Sep 17 00:00:00 2001 From: Didier Rodrigues Lopes Date: Thu, 23 Dec 2021 12:04:11 +0000 Subject: [PATCH] Discordbot screener (#1087) * improve screener on discordbot * improve screener code of discordbot * linter * line length --- discordbot/stocks/screener/financial.py | 63 ++------ discordbot/stocks/screener/historical.py | 20 +-- discordbot/stocks/screener/overview.py | 51 ++---- discordbot/stocks/screener/ownership.py | 59 ++----- discordbot/stocks/screener/performance.py | 32 ++-- discordbot/stocks/screener/presets.py | 126 --------------- discordbot/stocks/screener/presets_custom.py | 52 ++++++ discordbot/stocks/screener/presets_default.py | 42 +++++ discordbot/stocks/screener/screener_menu.py | 91 +++++++---- .../stocks/screener/screener_options.py | 152 ++++++++++++++++++ discordbot/stocks/screener/technical.py | 59 ++----- discordbot/stocks/screener/valuation.py | 62 ++----- 12 files changed, 404 insertions(+), 405 deletions(-) delete mode 100644 discordbot/stocks/screener/presets.py create mode 100644 discordbot/stocks/screener/presets_custom.py create mode 100644 discordbot/stocks/screener/presets_default.py create mode 100644 discordbot/stocks/screener/screener_options.py diff --git a/discordbot/stocks/screener/financial.py b/discordbot/stocks/screener/financial.py index 7dc2cfd2b07..4a8d6a56bac 100644 --- a/discordbot/stocks/screener/financial.py +++ b/discordbot/stocks/screener/financial.py @@ -2,18 +2,18 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so -async def financial_command( - ctx, preset="template", sort="", limit="25", ascend="False" -): +async def financial_command(ctx, preset="template", sort="", limit="5", ascend="False"): """Displays returned results from preset by financial metrics [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -36,39 +36,14 @@ async def financial_command( raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, - "overview", + "financial", limit, ascend, ) - preset_error = False - - d_cols_to_sort = { - "financial": [ - "Ticker", - "Market Cap", - "Dividend", - "ROA", - "ROE", - "ROI", - "Curr R", - "Quick R", - "LTDebt/Eq", - "Debt/Eq", - "Gross M", - "Oper M", - "Profit M", - "Earnings", - "Price", - "Change", - "Volume", - ], - } description = "" - if isinstance(df_screen, pd.DataFrame): if df_screen.empty: return [] @@ -76,7 +51,7 @@ async def financial_command( df_screen = df_screen.dropna(axis="columns", how="all") if sort: - if " ".join(sort) in d_cols_to_sort["financial"]: + if " ".join(sort) in so.d_cols_to_sort["financial"]: df_screen = df_screen.sort_values( by=[" ".join(sort)], ascending=ascend, @@ -85,7 +60,7 @@ async def financial_command( else: similar_cmd = difflib.get_close_matches( " ".join(sort), - d_cols_to_sort["financial"], + so.d_cols_to_sort["financial"], n=1, cutoff=0.7, ) @@ -98,7 +73,7 @@ async def financial_command( ) else: raise ValueError( - f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['financial'])}" + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['financial'])}" ) df_screen = df_screen.fillna("") @@ -140,19 +115,11 @@ async def financial_command( await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Financial Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Financial Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Financial Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/screener/historical.py b/discordbot/stocks/screener/historical.py index 0ffb03fdfd0..de3475b34c3 100644 --- a/discordbot/stocks/screener/historical.py +++ b/discordbot/stocks/screener/historical.py @@ -7,19 +7,19 @@ from matplotlib import pyplot as plt import yfinance as yf from pandas.plotting import register_matplotlib_converters from finvizfinance.screener import ticker -import discordbot.config_discordbot as cfg - -from discordbot.run_discordbot import gst_imgur from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.stocks.screener import finviz_view from gamestonk_terminal.stocks.screener import finviz_model from gamestonk_terminal.helper_funcs import plot_autoscale +import discordbot.config_discordbot as cfg +from discordbot.run_discordbot import gst_imgur +from discordbot.stocks.screener import screener_options as so + # pylint:disable=no-member -async def historical_command(ctx, signal="most_volatile", start=""): +async def historical_command(ctx, signal="", start=""): """Displays historical price comparison between similar companies [Yahoo Finance]""" try: @@ -27,9 +27,11 @@ async def historical_command(ctx, signal="most_volatile", start=""): if cfg.DEBUG: print(f"!stocks.scr.historical {signal} {start}") - register_matplotlib_converters() + # Check for argument + if signal == "" or signal not in list(so.d_signals_desc.keys): + raise Exception("Invalid preset selected!") - presets_path = os.path.join(cfg.GST_PATH, "stocks", "screener", "presets", "") + register_matplotlib_converters() screen = ticker.Ticker() @@ -39,7 +41,7 @@ async def historical_command(ctx, signal="most_volatile", start=""): else: preset_filter = configparser.RawConfigParser() preset_filter.optionxform = str # type: ignore - preset_filter.read(presets_path + signal + ".ini") + preset_filter.read(so.presets_path + signal + ".ini") d_general = preset_filter["General"] d_filters = { @@ -118,7 +120,7 @@ async def historical_command(ctx, signal="most_volatile", start=""): if signal: plt.title( - f"Screener Historical Price using {finviz_view.d_signals[signal]} signal" + f"Screener Historical Price using {finviz_model.d_signals[signal]} signal" ) else: plt.title(f"Screener Historical Price using {signal} preset") diff --git a/discordbot/stocks/screener/overview.py b/discordbot/stocks/screener/overview.py index 7e4823a054a..6b91cbfceca 100644 --- a/discordbot/stocks/screener/overview.py +++ b/discordbot/stocks/screener/overview.py @@ -2,16 +2,18 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so -async def overview_command(ctx, preset="template", sort="", limit="25", ascend="False"): +async def overview_command(ctx, preset="template", sort="", limit="5", ascend="False"): """Displays stocks with overview data such as Sector and Industry [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -34,29 +36,12 @@ async def overview_command(ctx, preset="template", sort="", limit="25", ascend=" raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, "overview", limit, ascend, ) - preset_error = False - - d_cols_to_sort = { - "overview": [ - "Ticker", - "Company", - "Sector", - "Industry", - "Country", - "Market Cap", - "P/E", - "Price", - "Change", - "Volume", - ], - } description = "" @@ -67,7 +52,7 @@ async def overview_command(ctx, preset="template", sort="", limit="25", ascend=" df_screen = df_screen.dropna(axis="columns", how="all") if sort: - if " ".join(sort) in d_cols_to_sort["overview"]: + if " ".join(sort) in so.d_cols_to_sort["overview"]: df_screen = df_screen.sort_values( by=[" ".join(sort)], ascending=ascend, @@ -76,7 +61,7 @@ async def overview_command(ctx, preset="template", sort="", limit="25", ascend=" else: similar_cmd = difflib.get_close_matches( " ".join(sort), - d_cols_to_sort["overview"], + so.d_cols_to_sort["overview"], n=1, cutoff=0.7, ) @@ -89,7 +74,7 @@ async def overview_command(ctx, preset="template", sort="", limit="25", ascend=" ) else: raise ValueError( - f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['overview'])}" + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['overview'])}" ) df_screen = df_screen.fillna("") @@ -131,19 +116,11 @@ async def overview_command(ctx, preset="template", sort="", limit="25", ascend=" await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Overview Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Overview Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Overview Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/screener/ownership.py b/discordbot/stocks/screener/ownership.py index 0efc117e9d6..ed1f7147a32 100644 --- a/discordbot/stocks/screener/ownership.py +++ b/discordbot/stocks/screener/ownership.py @@ -2,18 +2,18 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so -async def ownership_command( - ctx, preset="template", sort="", limit="25", ascend="False" -): +async def ownership_command(ctx, preset="template", sort="", limit="5", ascend="False"): """Displays stocks based on own share float and ownership data [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -36,33 +36,12 @@ async def ownership_command( raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, - "overview", + "ownership", limit, ascend, ) - preset_error = False - - d_cols_to_sort = { - "ownership": [ - "Ticker", - "Market Cap", - "Outstanding", - "Float", - "Insider Own", - "Insider Trans", - "Inst Own", - "Inst Trans", - "Float Short", - "Short Ratio", - "Avg Volume", - "Price", - "Change", - "Volume", - ], - } description = "" @@ -73,7 +52,7 @@ async def ownership_command( df_screen = df_screen.dropna(axis="columns", how="all") if sort: - if " ".join(sort) in d_cols_to_sort["ownership"]: + if " ".join(sort) in so.d_cols_to_sort["ownership"]: df_screen = df_screen.sort_values( by=[" ".join(sort)], ascending=ascend, @@ -82,7 +61,7 @@ async def ownership_command( else: similar_cmd = difflib.get_close_matches( " ".join(sort), - d_cols_to_sort["ownership"], + so.d_cols_to_sort["ownership"], n=1, cutoff=0.7, ) @@ -95,7 +74,7 @@ async def ownership_command( ) else: raise ValueError( - f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['ownership'])}" + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['ownership'])}" ) df_screen = df_screen.fillna("") @@ -137,19 +116,11 @@ async def ownership_command( await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Ownership Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Ownership Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Ownership Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/screener/performance.py b/discordbot/stocks/screener/performance.py index d81529f2b21..8c89ce8037a 100644 --- a/discordbot/stocks/screener/performance.py +++ b/discordbot/stocks/screener/performance.py @@ -2,18 +2,20 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so async def performance_command( - ctx, preset="template", sort="", limit="25", ascend="False" + ctx, preset="template", sort="", limit="5", ascend="False" ): """Displays stocks and sort by performance categories [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -36,14 +38,12 @@ async def performance_command( raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, - "overview", + "performance", limit, ascend, ) - preset_error = False d_cols_to_sort = { "performance": [ @@ -139,19 +139,11 @@ async def performance_command( await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Performance Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Performance Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Performance Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/screener/presets.py b/discordbot/stocks/screener/presets.py deleted file mode 100644 index 0f0eac590e1..00000000000 --- a/discordbot/stocks/screener/presets.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import discord - -import discordbot.config_discordbot as cfg -from discordbot.helpers import pagination - - -async def presets_command(ctx): - """Displays every preset""" - try: - - # Debug - if cfg.DEBUG: - print("!stocks.scr.presets") - - presets_path = os.path.join( - cfg.GST_PATH, "gamestonk_terminal", "stocks", "screener", "presets", "" - ) - - d_signals_desc = { - "top_gainers": "stocks with the highest %% price gain today", - "top_losers": "stocks with the highest %% price loss today", - "new_high": "stocks making 52-week high today", - "new_low": "stocks making 52-week low today", - "most_volatile": "stocks with the highest widest high/low trading range today", - "most_active": "stocks with the highest trading volume today", - "unusual_volume": "stocks with unusually high volume today - the highest relative volume ratio", - "overbought": "stock is becoming overvalued and may experience a pullback.", - "oversold": "oversold stocks may represent a buying opportunity for investors", - "downgrades": "stocks downgraded by analysts today", - "upgrades": "stocks upgraded by analysts today", - "earnings_before": "companies reporting earnings today, before market open", - "earnings_after": "companies reporting earnings today, after market close", - "recent_insider_buying": "stocks with recent insider buying activity", - "recent_insider_selling": "stocks with recent insider selling activity", - "major_news": "stocks with the highest news coverage today", - "horizontal_sr": "horizontal channel of price range between support and resistance trendlines", - "tl_resistance": "once a rising trendline is broken", - "tl_support": "once a falling trendline is broken", - "wedge_up": "upward trendline support and upward trendline resistance (reversal)", - "wedge_down": "downward trendline support and downward trendline resistance (reversal)", - "wedge": "upward trendline support, downward trendline resistance (contiunation)", - "triangle_ascending": "upward trendline support and horizontal trendline resistance", - "triangle_descending": "horizontal trendline support and downward trendline resistance", - "channel_up": "both support and resistance trendlines slope upward", - "channel_down": "both support and resistance trendlines slope downward", - "channel": "both support and resistance trendlines are horizontal", - "double_top": "stock with 'M' shape that indicates a bearish reversal in trend", - "double_bottom": "stock with 'W' shape that indicates a bullish reversal in trend", - "multiple_top": "same as double_top hitting more highs", - "multiple_bottom": "same as double_bottom hitting more lows", - "head_shoulders": "chart formation that predicts a bullish-to-bearish trend reversal", - "head_shoulders_inverse": "chart formation that predicts a bearish-to-bullish trend reversal", - } - - presets = [ - preset.split(".")[0] - for preset in os.listdir(presets_path) - if preset[-4:] == ".ini" - ] - - description = "***Custom Presets:***\n" - - for preset in presets: - with open( - presets_path + preset + ".ini", - encoding="utf8", - ) as f: - preset_line = "" - for line in f: - if line.strip() == "[General]": - break - preset_line += line.strip() - description += f"**{preset}:** *{preset_line.split('Description: ')[1].replace('#', '')}*\n" - - description += "\n\n***Default Presets:***\n" - for signame, sigdesc in d_signals_desc.items(): - description += f"**{signame}:** *{sigdesc}*\n" - - if len(description) <= 4000: - embed = discord.Embed( - title="Stocks: Screener Presets", - description=description, - 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(description) / 4000: - columns.append( - discord.Embed( - title="Stocks: Screener Presets", - description=description[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="ERROR Stocks: Screener Presets", - 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/screener/presets_custom.py b/discordbot/stocks/screener/presets_custom.py new file mode 100644 index 00000000000..190a30a6d1a --- /dev/null +++ b/discordbot/stocks/screener/presets_custom.py @@ -0,0 +1,52 @@ +import discord + +import discordbot.config_discordbot as cfg + +from discordbot.stocks.screener import screener_options as so + + +async def presets_custom_command(ctx): + """Displays every custom preset""" + try: + + # Debug + if cfg.DEBUG: + print("!stocks.scr.presets") + + description = "" + for preset in so.presets: + with open( + so.presets_path + preset + ".ini", + encoding="utf8", + ) as f: + preset_line = "" + for line in f: + if line.strip() == "[General]": + break + preset_line += line.strip() + description += f"**{preset}:** *{preset_line.split('Description: ')[1].replace('#', '')}*\n" + + embed = discord.Embed( + title="Stocks: Screener Custom Presets", + description=description, + 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: Screener Presets", + 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/screener/presets_default.py b/discordbot/stocks/screener/presets_default.py new file mode 100644 index 00000000000..3b5e1fc0794 --- /dev/null +++ b/discordbot/stocks/screener/presets_default.py @@ -0,0 +1,42 @@ +import discord + +import discordbot.config_discordbot as cfg + +from discordbot.stocks.screener import screener_options as so + + +async def presets_default_command(ctx): + """Displays default presets""" + try: + + # Debug + if cfg.DEBUG: + print("!stocks.scr.presets") + + description = "" + for signame, sigdesc in so.d_signals_desc.items(): + description += f"**{signame}:** *{sigdesc}*\n" + embed = discord.Embed( + title="Stocks: Screener Default Presets", + description=description, + 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: Screener Presets", + 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/screener/screener_menu.py b/discordbot/stocks/screener/screener_menu.py index c39c038dfdf..24437febf01 100644 --- a/discordbot/stocks/screener/screener_menu.py +++ b/discordbot/stocks/screener/screener_menu.py @@ -5,14 +5,19 @@ import discordbot.config_discordbot as cfg from discordbot.run_discordbot import gst_bot -from discordbot.stocks.screener.presets import presets_command from discordbot.stocks.screener.historical import historical_command from discordbot.stocks.screener.overview import overview_command +from discordbot.stocks.screener.presets_custom import presets_custom_command +from discordbot.stocks.screener.presets_default import presets_default_command from discordbot.stocks.screener.valuation import valuation_command from discordbot.stocks.screener.financial import financial_command from discordbot.stocks.screener.ownership import ownership_command from discordbot.stocks.screener.performance import performance_command from discordbot.stocks.screener.technical import technical_command +from discordbot.stocks.screener import screener_options as so + + +# pylint: disable=R0912 class ScreenerCommands(discord.ext.commands.Cog): @@ -21,10 +26,15 @@ class ScreenerCommands(discord.ext.commands.Cog): def __init__(self, bot: discord.ext.commands.Bot): self.bot = bot - @discord.ext.commands.command(name="stocks.scr.presets") - async def presets(self, ctx: discord.ext.commands.Context): + @discord.ext.commands.command(name="stocks.scr.presets_default") + async def presets_default(self, ctx: discord.ext.commands.Context): """Displays every available preset""" - await presets_command(ctx) + await presets_default_command(ctx) + + @discord.ext.commands.command(name="stocks.scr.presets_custom") + async def presets_custom(self, ctx: discord.ext.commands.Context): + """Displays every available preset""" + await presets_custom_command(ctx) @discord.ext.commands.command(name="stocks.scr.historical") async def historical( @@ -50,7 +60,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays stocks with overview data such as Sector and Industry [Finviz] @@ -74,7 +84,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays results from chosen preset focusing on valuation metrics [Finviz] @@ -98,7 +108,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays returned results from preset by financial metrics [Finviz] @@ -122,7 +132,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays stocks based on own share float and ownership data [Finviz] @@ -146,7 +156,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays stocks and sort by performance categories [Finviz] @@ -170,7 +180,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ctx: discord.ext.commands.Context, preset="template", sort="", - limit="25", + limit="5", ascend="False", ): """Displays stocks according to chosen preset, sorting by technical factors [Finviz] @@ -189,7 +199,7 @@ class ScreenerCommands(discord.ext.commands.Cog): await technical_command(ctx, preset, sort, limit, ascend) @discord.ext.commands.command(name="stocks.scr") - async def scr(self, ctx: discord.ext.commands.Context): + async def scr(self, ctx: discord.ext.commands.Context, preset=""): """Screener Context Menu Returns @@ -198,19 +208,36 @@ class ScreenerCommands(discord.ext.commands.Cog): The user can then select a reaction to trigger a command. """ + if preset != "" and preset not in so.all_presets: + raise Exception("Invalid preset selected!") + if cfg.DEBUG: print("!stocks.scr") - text = ( - "0️⃣ !stocks.scr.presets\n" - "1️⃣ !stocks.scr.historical \n" - "2️⃣ !stocks.scr.overview \n" - "3️⃣ !stocks.scr.valuation \n" - "4️⃣ !stocks.scr.financial \n" - "5️⃣ !stocks.scr.ownership \n" - "6️⃣ !stocks.scr.performance \n" - "7️⃣ !stocks.scr.technical " - ) + if preset: + text = ( + "0️⃣ !stocks.scr.presets_default\n" + "1️⃣ !stocks.scr.presets_custom\n" + "2️⃣ !stocks.scr.historical \n" + f"3️⃣ !stocks.scr.overview {preset} \n" + f"4️⃣ !stocks.scr.valuation {preset} \n" + f"5️⃣ !stocks.scr.financial {preset} \n" + f"6️⃣ !stocks.scr.ownership {preset} \n" + f"7️⃣ !stocks.scr.performance {preset} \n" + f"8️⃣ !stocks.scr.technical {preset} " + ) + else: + text = ( + "0️⃣ !stocks.scr.presets_default\n" + "1️⃣ !stocks.scr.presets_custom\n" + "2️⃣ !stocks.scr.historical \n" + "3️⃣ !stocks.scr.overview \n" + "4️⃣ !stocks.scr.valuation \n" + "5️⃣ !stocks.scr.financial \n" + "6️⃣ !stocks.scr.ownership \n" + "7️⃣ !stocks.scr.performance \n" + "8️⃣ !stocks.scr.technical " + ) title = "Screener Menu" embed = discord.Embed(title=title, description=text, colour=cfg.COLOR) @@ -220,7 +247,7 @@ class ScreenerCommands(discord.ext.commands.Cog): ) msg = await ctx.send(embed=embed) - emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"] + emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣"] for emoji in emoji_list: await msg.add_reaction(emoji) @@ -235,35 +262,39 @@ class ScreenerCommands(discord.ext.commands.Cog): if reaction.emoji == "0️⃣": if cfg.DEBUG: print("Reaction selected: 0") - await presets_command(ctx) + await presets_default_command(ctx) elif reaction.emoji == "1️⃣": if cfg.DEBUG: print("Reaction selected: 1") - await historical_command(ctx) + await presets_custom_command(ctx) elif reaction.emoji == "2️⃣": if cfg.DEBUG: print("Reaction selected: 2") - await overview_command(ctx) + await historical_command(ctx, preset) elif reaction.emoji == "3️⃣": if cfg.DEBUG: print("Reaction selected: 3") - await valuation_command(ctx) + await overview_command(ctx, preset) elif reaction.emoji == "4️⃣": if cfg.DEBUG: print("Reaction selected: 4") - await financial_command(ctx) + await valuation_command(ctx, preset) elif reaction.emoji == "5️⃣": if cfg.DEBUG: print("Reaction selected: 5") - await ownership_command(ctx) + await financial_command(ctx, preset) elif reaction.emoji == "6️⃣": if cfg.DEBUG: print("Reaction selected: 6") - await performance_command(ctx) + await ownership_command(ctx, preset) elif reaction.emoji == "7️⃣": if cfg.DEBUG: print("Reaction selected: 7") - await technical_command(ctx) + await performance_command(ctx, preset) + elif reaction.emoji == "8️⃣": + if cfg.DEBUG: + print("Reaction selected: 8") + await technical_command(ctx, preset) for emoji in emoji_list: await msg.remove_reaction(emoji, ctx.bot.user) diff --git a/discordbot/stocks/screener/screener_options.py b/discordbot/stocks/screener/screener_options.py new file mode 100644 index 00000000000..450eba73834 --- /dev/null +++ b/discordbot/stocks/screener/screener_options.py @@ -0,0 +1,152 @@ +import os + +import discordbot.config_discordbot as cfg + +presets_path = os.path.join( + cfg.GST_PATH, "gamestonk_terminal", "stocks", "screener", "presets", "" +) + +d_signals_desc = { + "top_gainers": "stocks with the highest %% price gain today", + "top_losers": "stocks with the highest %% price loss today", + "new_high": "stocks making 52-week high today", + "new_low": "stocks making 52-week low today", + "most_volatile": "stocks with the highest widest high/low trading range today", + "most_active": "stocks with the highest trading volume today", + "unusual_volume": "stocks with unusually high volume today - the highest relative volume ratio", + "overbought": "stock is becoming overvalued and may experience a pullback.", + "oversold": "oversold stocks may represent a buying opportunity for investors", + "downgrades": "stocks downgraded by analysts today", + "upgrades": "stocks upgraded by analysts today", + "earnings_before": "companies reporting earnings today, before market open", + "earnings_after": "companies reporting earnings today, after market close", + "recent_insider_buying": "stocks with recent insider buying activity", + "recent_insider_selling": "stocks with recent insider selling activity", + "major_news": "stocks with the highest news coverage today", + "horizontal_sr": "horizontal channel of price range between support and resistance trendlines", + "tl_resistance": "once a rising trendline is broken", + "tl_support": "once a falling trendline is broken", + "wedge_up": "upward trendline support and upward trendline resistance (reversal)", + "wedge_down": "downward trendline support and downward trendline resistance (reversal)", + "wedge": "upward trendline support, downward trendline resistance (contiunation)", + "triangle_ascending": "upward trendline support and horizontal trendline resistance", + "triangle_descending": "horizontal trendline support and downward trendline resistance", + "channel_up": "both support and resistance trendlines slope upward", + "channel_down": "both support and resistance trendlines slope downward", + "channel": "both support and resistance trendlines are horizontal", + "double_top": "stock with 'M' shape that indicates a bearish reversal in trend", + "double_bottom": "stock with 'W' shape that indicates a bullish reversal in trend", + "multiple_top": "same as double_top hitting more highs", + "multiple_bottom": "same as double_bottom hitting more lows", + "head_shoulders": "chart formation that predicts a bullish-to-bearish trend reversal", + "head_shoulders_inverse": "chart formation that predicts a bearish-to-bullish trend reversal", +} + +presets = [ + preset.split(".")[0] for preset in os.listdir(presets_path) if preset[-4:] == ".ini" +] + +all_presets = list(d_signals_desc.keys()) + presets + + +d_cols_to_sort = { + "overview": [ + "Ticker", + "Company", + "Sector", + "Industry", + "Country", + "Market Cap", + "P/E", + "Price", + "Change", + "Volume", + ], + "valuation": [ + "Ticker", + "Market Cap", + "P/E", + "Fwd P/E", + "PEG", + "P/S", + "P/B", + "P/C", + "P/FCF", + "EPS this Y", + "EPS next Y", + "EPS past 5Y", + "EPS next 5Y", + "Sales past 5Y", + "Price", + "Change", + "Volume", + ], + "financial": [ + "Ticker", + "Market Cap", + "Dividend", + "ROA", + "ROE", + "ROI", + "Curr R", + "Quick R", + "LTDebt/Eq", + "Debt/Eq", + "Gross M", + "Oper M", + "Profit M", + "Earnings", + "Price", + "Change", + "Volume", + ], + "ownership": [ + "Ticker", + "Market Cap", + "Outstanding", + "Float", + "Insider Own", + "Insider Trans", + "Inst Own", + "Inst Trans", + "Float Short", + "Short Ratio", + "Avg Volume", + "Price", + "Change", + "Volume", + ], + "performance": [ + "Ticker", + "Perf Week", + "Perf Month", + "Perf Quart", + "Perf Half", + "Perf Year", + "Perf YTD", + "Volatility W", + "Volatility M", + "Recom", + "Avg Volume", + "Rel Volume", + "Price", + "Change", + "Volume", + ], + "technical": [ + "Ticker", + "Beta", + "ATR", + "SMA20", + "SMA50", + "SMA200", + "52W High", + "52W Low", + "RSI", + "Price", + "Change", + "from Open", + "Gap", + "Volume", + ], +} diff --git a/discordbot/stocks/screener/technical.py b/discordbot/stocks/screener/technical.py index a3c08f6f9dc..78da2eff936 100644 --- a/discordbot/stocks/screener/technical.py +++ b/discordbot/stocks/screener/technical.py @@ -2,18 +2,18 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so -async def technical_command( - ctx, preset="template", sort="", limit="25", ascend="False" -): +async def technical_command(ctx, preset="template", sort="", limit="5", ascend="False"): """Displays stocks according to chosen preset, sorting by technical factors [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -36,33 +36,12 @@ async def technical_command( raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, - "overview", + "technical", limit, ascend, ) - preset_error = False - - d_cols_to_sort = { - "technical": [ - "Ticker", - "Beta", - "ATR", - "SMA20", - "SMA50", - "SMA200", - "52W High", - "52W Low", - "RSI", - "Price", - "Change", - "from Open", - "Gap", - "Volume", - ], - } description = "" @@ -73,7 +52,7 @@ async def technical_command( df_screen = df_screen.dropna(axis="columns", how="all") if sort: - if " ".join(sort) in d_cols_to_sort["technical"]: + if " ".join(sort) in so.d_cols_to_sort["technical"]: df_screen = df_screen.sort_values( by=[" ".join(sort)], ascending=ascend, @@ -82,7 +61,7 @@ async def technical_command( else: similar_cmd = difflib.get_close_matches( " ".join(sort), - d_cols_to_sort["technical"], + so.d_cols_to_sort["technical"], n=1, cutoff=0.7, ) @@ -95,7 +74,7 @@ async def technical_command( ) else: raise ValueError( - f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['technical'])}" + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['technical'])}" ) df_screen = df_screen.fillna("") @@ -137,19 +116,11 @@ async def technical_command( await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Technical Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Technical Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Technical Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL, diff --git a/discordbot/stocks/screener/valuation.py b/discordbot/stocks/screener/valuation.py index 143446a0b85..0366bea928f 100644 --- a/discordbot/stocks/screener/valuation.py +++ b/discordbot/stocks/screener/valuation.py @@ -2,18 +2,18 @@ import difflib import discord import pandas as pd +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data import discordbot.config_discordbot as cfg from discordbot.helpers import pagination - -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data +from discordbot.stocks.screener import screener_options as so -async def valuation_command( - ctx, preset="template", sort="", limit="25", ascend="False" -): +async def valuation_command(ctx, preset="template", sort="", limit="5", ascend="False"): """Displays results from chosen preset focusing on valuation metrics [Finviz]""" try: - preset_error = False + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") # Debug if cfg.DEBUG: @@ -36,36 +36,12 @@ async def valuation_command( raise Exception("ascend argument has to be true or false") # Output Data - preset_error = True df_screen = get_screener_data( preset, - "overview", + "valuation", limit, ascend, ) - preset_error = False - - d_cols_to_sort = { - "valuation": [ - "Ticker", - "Market Cap", - "P/E", - "Fwd P/E", - "PEG", - "P/S", - "P/B", - "P/C", - "P/FCF", - "EPS this Y", - "EPS next Y", - "EPS past 5Y", - "EPS next 5Y", - "Sales past 5Y", - "Price", - "Change", - "Volume", - ], - } description = "" @@ -76,7 +52,7 @@ async def valuation_command( df_screen = df_screen.dropna(axis="columns", how="all") if sort: - if " ".join(sort) in d_cols_to_sort["valuation"]: + if " ".join(sort) in so.d_cols_to_sort["valuation"]: df_screen = df_screen.sort_values( by=[" ".join(sort)], ascending=ascend, @@ -85,7 +61,7 @@ async def valuation_command( else: similar_cmd = difflib.get_close_matches( " ".join(sort), - d_cols_to_sort["valuation"], + so.d_cols_to_sort["valuation"], n=1, cutoff=0.7, ) @@ -98,7 +74,7 @@ async def valuation_command( ) else: raise ValueError( - f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['valuation'])}" + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['valuation'])}" ) df_screen = df_screen.fillna("") @@ -140,19 +116,11 @@ async def valuation_command( await pagination(columns, ctx) except Exception as e: - if not preset_error: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Valuation Screener", - colour=cfg.COLOR, - description=e, - ) - else: - embed = discord.Embed( - title="ERROR Stocks: [Finviz] Valuation Screener", - colour=cfg.COLOR, - description=f"Wrong preset parameter entered. Use the command '{cfg.COMMAND_PREFIX}stocks.scr.presets' " - "in order to see the available presets.", - ) + embed = discord.Embed( + title="ERROR Stocks: [Finviz] Valuation Screener", + colour=cfg.COLOR, + description=e, + ) embed.set_author( name=cfg.AUTHOR_NAME, icon_url=cfg.AUTHOR_ICON_URL,