mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 20:42:26 +00:00
* First commit * Renamed to bot * change from discordbot to bots imports * refractor cmds + autocomplete helpers + updated dps plots/candle + quote cmd * isort fix * Update helpers.py * Added first refactor * Added commands and refactor * Added description handler * Through grains * ta + image border helper * Added indices * merged * Finished econ * options refactor * dark_pool_shorts, candle, quote done * pagination/dropdown logic to helpers + fixes * dd/disc/insider done + add image logic to helpers * linting * removed plt style * Almost done with Gov * fixing conflicts * Finished gov (excpect ones mentioned) * screener done * Test bug refactor * Store changes to switch * Finished refactor * Fixed error * Delete ::40 * Fixed issue * Fixed black * finished * fixes Co-authored-by: teh_coderer <me@tehcoderer.com> Co-authored-by: didierlopes.eth <dro.lopes@campus.fct.unl.pt>
272 lines
7.7 KiB
Python
272 lines
7.7 KiB
Python
import os
|
|
import uuid
|
|
|
|
import df2img
|
|
import disnake
|
|
import pandas as pd
|
|
import yfinance as yf
|
|
from numpy.core.fromnumeric import transpose
|
|
from PIL import Image
|
|
|
|
import bots.config_discordbot as cfg
|
|
|
|
presets_custom = [
|
|
"potential_reversals",
|
|
"golden_cross_penny",
|
|
"rosenwald_gtfo",
|
|
"golden_cross",
|
|
"bull_runs_over_10pct",
|
|
"recent_growth_and_support",
|
|
"heavy_inst_ins",
|
|
"short_squeeze_scan",
|
|
"under_15dol_stocks",
|
|
"top_performers_healthcare",
|
|
"oversold_under_3dol",
|
|
"value_stocks",
|
|
"cheap_dividend",
|
|
"death_cross",
|
|
"top_performers_tech",
|
|
"unusual_volume",
|
|
"cheap_oversold",
|
|
"undervalue",
|
|
"high_vol_and_low_debt",
|
|
"simplistic_momentum_scanner_under_7dol",
|
|
"5pct_above_low",
|
|
"growth_stocks",
|
|
"cheap_bottom_dividend",
|
|
"analyst_strong_buy",
|
|
"oversold",
|
|
"rosenwald",
|
|
"weak_support_and_top_performers",
|
|
"channel_up_and_low_debt_and_sma_50and200",
|
|
"template",
|
|
"modified_neff",
|
|
"buffett_like",
|
|
"oversold_under_5dol",
|
|
"sexy_year",
|
|
"news_scanner",
|
|
"top_performers_all",
|
|
"stocks_strong_support_levels",
|
|
"continued_momentum_scan",
|
|
"modified_dreman",
|
|
"break_out_stocks",
|
|
]
|
|
signals = [
|
|
"top_gainers",
|
|
"top_losers",
|
|
"new_high",
|
|
"new_low",
|
|
"most_volatile",
|
|
"most_active",
|
|
"unusual_volume",
|
|
"overbought",
|
|
"oversold",
|
|
"downgrades",
|
|
"upgrades",
|
|
"earnings_before",
|
|
"earnings_after",
|
|
"recent_insider_buying",
|
|
"recent_insider_selling",
|
|
"major_news",
|
|
"horizontal_sr",
|
|
"tl_resistance",
|
|
"tl_support",
|
|
"wedge_up",
|
|
"wedge_down",
|
|
"wedge",
|
|
"triangle_ascending",
|
|
"triangle_descending",
|
|
"channel_up",
|
|
"channel_down",
|
|
"channel",
|
|
"double_top",
|
|
"double_bottom",
|
|
"multiple_top",
|
|
"multiple_bottom",
|
|
"head_shoulders",
|
|
"head_shoulders_inverse",
|
|
]
|
|
|
|
|
|
def load(ticker, start_date):
|
|
df_stock_candidate = yf.download(ticker, start=start_date, progress=False)
|
|
df_stock_candidate.index.name = "date"
|
|
return df_stock_candidate
|
|
|
|
|
|
def quote(ticker):
|
|
ticker = yf.Ticker(ticker)
|
|
quote_df = pd.DataFrame(
|
|
[
|
|
{
|
|
"Symbol": ticker.info["symbol"],
|
|
"Name": ticker.info["shortName"],
|
|
"Price": ticker.info["regularMarketPrice"],
|
|
"Open": ticker.info["regularMarketOpen"],
|
|
"High": ticker.info["dayHigh"],
|
|
"Low": ticker.info["dayLow"],
|
|
"Previous Close": ticker.info["previousClose"],
|
|
"Volume": ticker.info["volume"],
|
|
"52 Week High": ticker.info["fiftyTwoWeekHigh"],
|
|
"52 Week Low": ticker.info["fiftyTwoWeekLow"],
|
|
}
|
|
]
|
|
)
|
|
quote_df["Change"] = quote_df["Price"] - quote_df["Previous Close"]
|
|
quote_df["Change %"] = quote_df.apply(
|
|
lambda x: f'{((x["Change"] / x["Previous Close"]) * 100):.2f}%',
|
|
axis="columns",
|
|
)
|
|
for c in [
|
|
"Price",
|
|
"Open",
|
|
"High",
|
|
"Low",
|
|
"Previous Close",
|
|
"52 Week High",
|
|
"52 Week Low",
|
|
"Change",
|
|
]:
|
|
quote_df[c] = quote_df[c].apply(lambda x: f"{x:.2f}")
|
|
quote_df["Volume"] = quote_df["Volume"].apply(lambda x: f"{x:,}")
|
|
|
|
quote_df = quote_df.set_index("Symbol")
|
|
quote_data = transpose(quote_df)
|
|
return quote_data
|
|
|
|
|
|
def uuid_get():
|
|
rand = str(uuid.uuid1()).replace("-", "")
|
|
return rand
|
|
|
|
|
|
def autocrop_image(image, border=0):
|
|
bbox = image.getbbox()
|
|
image = image.crop(bbox)
|
|
(width, height) = image.size
|
|
width += border * 2
|
|
height += border * 2
|
|
cropped_image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
|
cropped_image.paste(image, (border, border))
|
|
return cropped_image
|
|
|
|
|
|
def ticker_autocomp(inter, ticker: str): # pylint: disable=W0613
|
|
if not ticker:
|
|
return ["Start Typing", "for a", "stock ticker"]
|
|
print(f"ticker_autocomp [ticker]: {ticker}")
|
|
tlow = ticker.lower()
|
|
col_list = ["Name"]
|
|
df = pd.read_csv("files/tickers.csv", usecols=col_list)
|
|
df = df["Name"]
|
|
return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24]
|
|
|
|
|
|
def expiry_autocomp(inter, ticker: str): # pylint: disable=W0613
|
|
data = inter.filled_options["ticker"]
|
|
yf_ticker = yf.Ticker(data)
|
|
dates = list(yf_ticker.options)
|
|
return dates[:24]
|
|
|
|
|
|
def presets_custom_autocomp(inter, preset: str): # pylint: disable=W0613
|
|
df = presets_custom
|
|
if not preset:
|
|
return df[:24]
|
|
plow = preset.lower()
|
|
print(f"preset_custom_autocomp [preset]: {preset}")
|
|
return [preset for preset in df if preset.lower().startswith(plow)][:24]
|
|
|
|
|
|
def signals_autocomp(inter, signal: str): # pylint: disable=W0613
|
|
df = signals
|
|
if not signal:
|
|
return df[:24]
|
|
print(f"signal_autocomp [signal]: {signal}")
|
|
slow = signal.lower()
|
|
return [signal for signal in df if signal.lower().startswith(slow)][:24]
|
|
|
|
|
|
def save_image(file, fig):
|
|
imagefile = f"{file.replace('.png', '')}_{uuid_get()}.png"
|
|
df2img.save_dataframe(fig=fig, filename=imagefile)
|
|
image = Image.open(imagefile)
|
|
image = autocrop_image(image, 0)
|
|
image.save(imagefile, "PNG", quality=100)
|
|
return imagefile
|
|
|
|
|
|
def image_border(file, **kwargs):
|
|
imagefile = f"{file.replace('.png', '')}_{uuid_get()}.png"
|
|
if "fig" in kwargs:
|
|
fig = kwargs["fig"]
|
|
fig.write_image(imagefile)
|
|
img = Image.open(file)
|
|
im_bg = Image.open(cfg.IMG_BG)
|
|
h = img.height + 240
|
|
w = img.width + 520
|
|
|
|
# Paste fig onto background img and autocrop background
|
|
img = img.resize((w, h), Image.ANTIALIAS)
|
|
x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0])
|
|
y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1])
|
|
x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0])
|
|
y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1])
|
|
img = img.convert("RGB")
|
|
im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2))
|
|
im_bg.save(file, "PNG", quality=100)
|
|
image = Image.open(file)
|
|
image = autocrop_image(image, 0)
|
|
image.save(imagefile, "PNG", quality=100)
|
|
os.remove(file)
|
|
return imagefile
|
|
|
|
|
|
class ShowView:
|
|
async def run_discord(self, func, inter, *args, **kwargs):
|
|
data = func(*args, **kwargs)
|
|
|
|
if "view" in data:
|
|
await inter.send(
|
|
embed=data["embed"][0],
|
|
view=data["view"](data["embed"], data["choices"]),
|
|
)
|
|
|
|
else:
|
|
title = data.get("title", "")
|
|
embed = disnake.Embed(
|
|
title=title, colour=cfg.COLOR, description=data.get("description", "")
|
|
)
|
|
embed.set_author(
|
|
name=cfg.AUTHOR_NAME,
|
|
icon_url=cfg.AUTHOR_ICON_URL,
|
|
)
|
|
if "imagefile" in data:
|
|
image = disnake.File(data["imagefile"])
|
|
embed.set_image(url=f"attachment://{data['imagefile']}")
|
|
os.remove(data["imagefile"])
|
|
await inter.send(embed=embed, file=image)
|
|
else:
|
|
await inter.send(embed=embed)
|
|
|
|
async def discord(self, func, inter, name, *args, **kwargs):
|
|
await inter.response.defer()
|
|
cfg.logger.info(name)
|
|
if os.environ.get("DEBUG_MODE") == "true":
|
|
await self.run_discord(func, inter, *args, **kwargs)
|
|
else:
|
|
try:
|
|
await self.run_discord(func, inter, *args, **kwargs)
|
|
except Exception as e:
|
|
embed = disnake.Embed(
|
|
title=name,
|
|
colour=cfg.COLOR,
|
|
description=e,
|
|
)
|
|
embed.set_author(
|
|
name=cfg.AUTHOR_NAME,
|
|
icon_url=cfg.AUTHOR_ICON_URL,
|
|
)
|
|
|
|
await inter.send(embed=embed, delete_after=30.0)
|