Discordbot: Adding screener preset (#1045)

* Screener menu

Adding the screener commands to the discord bot.

* Fix linting

* More linting

* Even more linting

* Fixing errors

* Linting fix

* Removing code leftover from testing

* Fixing everything hopefully

* Fixing things

* Removing signal keyword

* Fixing linting

* Transpose command

Transposes the commands and adds preset command

* Improving error message output
This commit is contained in:
northern-64bit
2021-12-17 05:48:35 +01:00
committed by GitHub
parent 0c205cbeae
commit 9d86ffa5ee
12 changed files with 1526 additions and 2 deletions

View File

@@ -4,7 +4,7 @@ import difflib
import discord
import discord_components
import pyimgur
import discordbot.config_discordbot as cfg
import config_discordbot as cfg
activity = discord.Game(
@@ -18,7 +18,7 @@ discord_components.DiscordComponents(gst_bot)
if cfg.IMGUR_CLIENT_ID == "REPLACE_ME" or cfg.DISCORD_BOT_TOKEN == "REPLACE_ME":
print(
f"Update IMGUR_CLIENT_ID or DISCORD_BOT_TOKEN or both in { os.path.join('discordbot', 'config_discordot') } \n"
f"Update IMGUR_CLIENT_ID or DISCORD_BOT_TOKEN or both in { os.path.join('discordbot', 'config_discordbot') } \n"
)
sys.exit()
@@ -36,6 +36,7 @@ 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")
gst_bot.load_extension("stocks.government.gov_menu")
gst_bot.load_extension("stocks.screener.screener_menu")
# Get all command names
all_cmds = gst_bot.all_commands.keys()

View File

@@ -3,5 +3,6 @@ from . import (
dark_pool_shorts,
due_diligence,
government,
screener,
technical_analysis,
)

View File

View File

@@ -0,0 +1,161 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def financial_command(
ctx, preset="template", sort="", limit="25", ascend="False"
):
"""Displays returned results from preset by financial metrics [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.financial {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = {
"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 []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["financial"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["financial"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['financial'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Financial Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Financial Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,160 @@
import os
import random
from datetime import datetime, timedelta
import configparser
import discord
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
async def historical_command(ctx, signal="most_volatile", start=""):
"""Displays historical price comparison between similar companies [Yahoo Finance]"""
try:
# Debug user input
if cfg.DEBUG:
print(f"!stocks.scr.historical {signal} {start}")
register_matplotlib_converters()
presets_path = os.path.join(cfg.GST_PATH, "stocks", "screener", "presets", "")
screen = ticker.Ticker()
if signal in list(finviz_model.d_signals.keys()):
screen.set_filter(signal=finviz_model.d_signals[signal])
else:
preset_filter = configparser.RawConfigParser()
preset_filter.optionxform = str # type: ignore
preset_filter.read(presets_path + signal + ".ini")
d_general = preset_filter["General"]
d_filters = {
**preset_filter["Descriptive"],
**preset_filter["Fundamental"],
**preset_filter["Technical"],
}
d_filters = {k: v for k, v in d_filters.items() if v}
if d_general["Signal"]:
screen.set_filter(filters_dict=d_filters, signal=d_general["Signal"])
else:
screen.set_filter(filters_dict=d_filters)
if start == "":
start = datetime.now() - timedelta(days=365)
else:
start = datetime.strptime(start, cfg.DATE_FORMAT)
# Output Data
l_min = []
l_leg = []
l_stocks = screen.ScreenerView(verbose=0)
if len(l_stocks) > 10:
description = (
"\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed."
"\nThe selected list will be: "
)
random.shuffle(l_stocks)
l_stocks = sorted(l_stocks[:10])
description = description + (", ".join(l_stocks))
print(description)
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
while l_stocks:
l_parsed_stocks = []
for symbol in l_stocks:
try:
df_similar_stock = yf.download(
symbol,
start=datetime.strftime(start, "%Y-%m-%d"),
progress=False,
threads=False,
)
if not df_similar_stock.empty:
plt.plot(
df_similar_stock.index,
df_similar_stock["Adj Close"].values,
)
l_min.append(df_similar_stock.index[0])
l_leg.append(symbol)
l_parsed_stocks.append(symbol)
except Exception as e:
error = (
f"{e}\nDisregard previous error, which is due to API Rate limits from Yahoo Finance. "
f"Because we like '{symbol}', and we won't leave without getting data from it."
)
embed = discord.Embed(
title="ERROR Stocks: [Yahoo Finance] Historical Screener",
colour=cfg.COLOR,
description=error,
)
embed.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)
for parsed_stock in l_parsed_stocks:
l_stocks.remove(parsed_stock)
if signal:
plt.title(
f"Screener Historical Price using {finviz_view.d_signals[signal]} signal"
)
else:
plt.title(f"Screener Historical Price using {signal} preset")
plt.xlabel("Time")
plt.ylabel("Share Price ($)")
plt.legend(l_leg)
plt.grid(b=True, which="major", color="#666666", linestyle="-")
plt.minorticks_on()
plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
# ensures that the historical data starts from same datapoint
plt.xlim([max(l_min), df_similar_stock.index[-1]])
plt.savefig("scr_historical.png")
uploaded_image = gst_imgur.upload_image("scr_historical.png", title="something")
image_link = uploaded_image.link
if cfg.DEBUG:
print(f"Image URL: {image_link}")
title = "Stocks: [Yahoo Finance] Historical Screener"
embed = discord.Embed(title=title, description=description, colour=cfg.COLOR)
embed.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
embed.set_image(url=image_link)
os.remove("scr_historical.png")
await ctx.send(embed=embed)
except Exception as e:
embed = discord.Embed(
title="ERROR Stocks: [Yahoo Finance] Historical Screener",
colour=cfg.COLOR,
description=e,
)
embed.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,152 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def overview_command(ctx, preset="template", sort="", limit="25", ascend="False"):
"""Displays stocks with overview data such as Sector and Industry [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.overview {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = ""
if isinstance(df_screen, pd.DataFrame):
if df_screen.empty:
return []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["overview"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["overview"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['overview'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Overview Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Overview Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,158 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def ownership_command(
ctx, preset="template", sort="", limit="25", ascend="False"
):
"""Displays stocks based on own share float and ownership data [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.ownership {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = {
"ownership": [
"Ticker",
"Market Cap",
"Outstanding",
"Float",
"Insider Own",
"Insider Trans",
"Inst Own",
"Inst Trans",
"Float Short",
"Short Ratio",
"Avg Volume",
"Price",
"Change",
"Volume",
],
}
description = ""
if isinstance(df_screen, pd.DataFrame):
if df_screen.empty:
return []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["ownership"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["ownership"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['ownership'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Ownership Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Ownership Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,160 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def performance_command(
ctx, preset="template", sort="", limit="25", ascend="False"
):
"""Displays stocks and sort by performance categories [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.performance {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = {
"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",
],
}
description = ""
if isinstance(df_screen, pd.DataFrame):
if df_screen.empty:
return []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["performance"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["performance"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
"Wrong sort column provided! Provide one of these:"
f"{', '.join(d_cols_to_sort['performance'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Performance Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Performance Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,126 @@
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)

View File

@@ -0,0 +1,286 @@
import asyncio
import discord
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.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
class ScreenerCommands(discord.ext.commands.Cog):
"""Screener menu"""
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):
"""Displays every available preset"""
await presets_command(ctx)
@discord.ext.commands.command(name="stocks.scr.historical")
async def historical(
self,
ctx: discord.ext.commands.Context,
signal="most_volatile",
start="",
):
"""Displays trades made by the congress/senate/house [quiverquant.com]
Parameters
-----------
signal: str
Signal. Default: most_volatile
start:
date (in date format for start date)
"""
await historical_command(ctx, signal, start)
@discord.ext.commands.command(name="stocks.scr.overview")
async def overview(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays stocks with overview data such as Sector and Industry [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await overview_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr.valuation")
async def valuation(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays results from chosen preset focusing on valuation metrics [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await valuation_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr.financial")
async def financial(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays returned results from preset by financial metrics [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await financial_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr.ownership")
async def ownership(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays stocks based on own share float and ownership data [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await ownership_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr.performance")
async def performance(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays stocks and sort by performance categories [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await performance_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr.technical")
async def technical(
self,
ctx: discord.ext.commands.Context,
preset="template",
sort="",
limit="25",
ascend="False",
):
"""Displays stocks according to chosen preset, sorting by technical factors [Finviz]
Parameters
-----------
preset: str
screener preset
sort: str
column to sort by
limit: int
number of stocks to display
ascend: boolean
whether it's sorted by ascending order or not. Default: False
"""
await technical_command(ctx, preset, sort, limit, ascend)
@discord.ext.commands.command(name="stocks.scr")
async def scr(self, ctx: discord.ext.commands.Context):
"""Screener Context Menu
Returns
-------
Sends a message to the discord user with the commands from the screener context.
The user can then select a reaction to trigger a command.
"""
if cfg.DEBUG:
print("!stocks.scr")
text = (
"0️⃣ !stocks.scr.presets\n"
"1️⃣ !stocks.scr.historical <SIGNAL> <START>\n"
"2️⃣ !stocks.scr.overview <PRESET> <SORT> <LIMIT> <ASCEND>\n"
"3️⃣ !stocks.scr.valuation <PRESET> <SORT> <LIMIT> <ASCEND>\n"
"4️⃣ !stocks.scr.financial <PRESET> <SORT> <LIMIT> <ASCEND>\n"
"5️⃣ !stocks.scr.ownership <PRESET> <SORT> <LIMIT> <ASCEND>\n"
"6️⃣ !stocks.scr.performance <PRESET> <SORT> <LIMIT> <ASCEND>\n"
"7️⃣ !stocks.scr.technical <PRESET> <SORT> <LIMIT> <ASCEND>"
)
title = "Screener 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️⃣", "7️⃣"]
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 presets_command(ctx)
elif reaction.emoji == "1️⃣":
if cfg.DEBUG:
print("Reaction selected: 1")
await historical_command(ctx)
elif reaction.emoji == "2️⃣":
if cfg.DEBUG:
print("Reaction selected: 2")
await overview_command(ctx)
elif reaction.emoji == "3️⃣":
if cfg.DEBUG:
print("Reaction selected: 3")
await valuation_command(ctx)
elif reaction.emoji == "4️⃣":
if cfg.DEBUG:
print("Reaction selected: 4")
await financial_command(ctx)
elif reaction.emoji == "5️⃣":
if cfg.DEBUG:
print("Reaction selected: 5")
await ownership_command(ctx)
elif reaction.emoji == "6️⃣":
if cfg.DEBUG:
print("Reaction selected: 6")
await performance_command(ctx)
elif reaction.emoji == "7️⃣":
if cfg.DEBUG:
print("Reaction selected: 7")
await technical_command(ctx)
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 Screener 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(ScreenerCommands(bot))

View File

@@ -0,0 +1,158 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def technical_command(
ctx, preset="template", sort="", limit="25", ascend="False"
):
"""Displays stocks according to chosen preset, sorting by technical factors [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.technical {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = {
"technical": [
"Ticker",
"Beta",
"ATR",
"SMA20",
"SMA50",
"SMA200",
"52W High",
"52W Low",
"RSI",
"Price",
"Change",
"from Open",
"Gap",
"Volume",
],
}
description = ""
if isinstance(df_screen, pd.DataFrame):
if df_screen.empty:
return []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["technical"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["technical"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['technical'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Technical Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Technical Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)

View File

@@ -0,0 +1,161 @@
import difflib
import discord
import pandas as pd
import discordbot.config_discordbot as cfg
from discordbot.helpers import pagination
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
async def valuation_command(
ctx, preset="template", sort="", limit="25", ascend="False"
):
"""Displays results from chosen preset focusing on valuation metrics [Finviz]"""
try:
preset_error = False
# Debug
if cfg.DEBUG:
print(f"!stocks.scr.valuation {preset} {sort} {limit} {ascend}")
# Check for argument
if not limit.lstrip("-").isnumeric():
raise Exception("Number has to be an integer")
limit = int(limit)
if limit < 0:
raise Exception("Number has to be above 0")
if ascend.lower() == "false":
ascend = False
elif ascend.lower() == "true":
ascend = True
else:
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 = {
"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 = ""
if isinstance(df_screen, pd.DataFrame):
if df_screen.empty:
return []
df_screen = df_screen.dropna(axis="columns", how="all")
if sort:
if " ".join(sort) in d_cols_to_sort["valuation"]:
df_screen = df_screen.sort_values(
by=[" ".join(sort)],
ascending=ascend,
na_position="last",
)
else:
similar_cmd = difflib.get_close_matches(
" ".join(sort),
d_cols_to_sort["valuation"],
n=1,
cutoff=0.7,
)
if similar_cmd:
description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n"
df_screen = df_screen.sort_values(
by=[similar_cmd[0]],
ascending=ascend,
na_position="last",
)
else:
raise ValueError(
f"Wrong sort column provided! Provide one of these: {', '.join(d_cols_to_sort['valuation'])}"
)
df_screen = df_screen.fillna("")
future_column_name = df_screen["Ticker"]
df_screen = df_screen.head(n=limit).transpose()
df_screen.columns = future_column_name
df_screen.drop("Ticker")
columns = []
initial_str = description + "Page 0: Overview"
i = 1
for column in df_screen.columns.values:
initial_str = initial_str + "\nPage " + str(i) + ": " + column
i += 1
columns.append(
discord.Embed(
title="Stocks: [Finviz] Valuation Screener",
description=initial_str,
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
for column in df_screen.columns.values:
columns.append(
discord.Embed(
title="Stocks: [Finviz] Valuation Screener",
description="```"
+ df_screen[column].fillna("").to_string()
+ "```",
colour=cfg.COLOR,
).set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
)
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.set_author(
name=cfg.AUTHOR_NAME,
icon_url=cfg.AUTHOR_ICON_URL,
)
await ctx.send(embed=embed)