mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-08 18:32:21 +00:00
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:
@@ -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()
|
||||
|
||||
@@ -3,5 +3,6 @@ from . import (
|
||||
dark_pool_shorts,
|
||||
due_diligence,
|
||||
government,
|
||||
screener,
|
||||
technical_analysis,
|
||||
)
|
||||
|
||||
0
discordbot/stocks/screener/__init__.py
Normal file
0
discordbot/stocks/screener/__init__.py
Normal file
161
discordbot/stocks/screener/financial.py
Normal file
161
discordbot/stocks/screener/financial.py
Normal 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)
|
||||
160
discordbot/stocks/screener/historical.py
Normal file
160
discordbot/stocks/screener/historical.py
Normal 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)
|
||||
152
discordbot/stocks/screener/overview.py
Normal file
152
discordbot/stocks/screener/overview.py
Normal 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)
|
||||
158
discordbot/stocks/screener/ownership.py
Normal file
158
discordbot/stocks/screener/ownership.py
Normal 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)
|
||||
160
discordbot/stocks/screener/performance.py
Normal file
160
discordbot/stocks/screener/performance.py
Normal 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)
|
||||
126
discordbot/stocks/screener/presets.py
Normal file
126
discordbot/stocks/screener/presets.py
Normal 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)
|
||||
286
discordbot/stocks/screener/screener_menu.py
Normal file
286
discordbot/stocks/screener/screener_menu.py
Normal 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))
|
||||
158
discordbot/stocks/screener/technical.py
Normal file
158
discordbot/stocks/screener/technical.py
Normal 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)
|
||||
161
discordbot/stocks/screener/valuation.py
Normal file
161
discordbot/stocks/screener/valuation.py
Normal 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)
|
||||
Reference in New Issue
Block a user