Refactor Bot (#1326)
* 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>
0
discordbot/README.md → bots/README.md
vendored
@@ -1,9 +1,9 @@
|
||||
# pylint: disable=W0612
|
||||
import disnake
|
||||
from disnake.ext import commands
|
||||
from menus.menu import Menu
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.menus.menu import Menu
|
||||
|
||||
|
||||
class CmdsCommands(commands.Cog):
|
||||
@@ -1,32 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
from disnake.ext import commands
|
||||
|
||||
from discordbot.config_discordbot import logger
|
||||
from discordbot.stocks.due_diligence.analyst import analyst_command
|
||||
from discordbot.stocks.due_diligence.arktrades import arktrades_command
|
||||
from discordbot.stocks.due_diligence.customer import customer_command
|
||||
from discordbot.stocks.due_diligence.est import est_command
|
||||
from discordbot.stocks.due_diligence.pt import pt_command
|
||||
from discordbot.stocks.due_diligence.sec import sec_command
|
||||
from discordbot.stocks.due_diligence.supplier import supplier_command
|
||||
|
||||
|
||||
def default_completion(inter: disnake.AppCmdInter) -> list[str]:
|
||||
return ["Start Typing", "If you want", "to", "Live"]
|
||||
|
||||
|
||||
def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str):
|
||||
if not ticker:
|
||||
return default_completion(inter)
|
||||
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]
|
||||
from bots.helpers import ShowView, ticker_autocomp
|
||||
from bots.stocks.due_diligence.analyst import analyst_command
|
||||
from bots.stocks.due_diligence.arktrades import arktrades_command
|
||||
from bots.stocks.due_diligence.customer import customer_command
|
||||
from bots.stocks.due_diligence.est import est_command
|
||||
from bots.stocks.due_diligence.pt import pt_command
|
||||
from bots.stocks.due_diligence.sec import sec_command
|
||||
from bots.stocks.due_diligence.supplier import supplier_command
|
||||
|
||||
|
||||
class DueDiligenceCommands(commands.Cog):
|
||||
@@ -37,7 +21,7 @@ class DueDiligenceCommands(commands.Cog):
|
||||
|
||||
@commands.slash_command(name="dd-analyst")
|
||||
async def analyst(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays analyst recommendations [Finviz]
|
||||
@@ -46,13 +30,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-analyst")
|
||||
await analyst_command(ctx, ticker)
|
||||
await ShowView().discord(analyst_command, inter, "dd-analyst", ticker)
|
||||
|
||||
@commands.slash_command(name="dd-pt")
|
||||
async def pt(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
raw: bool = False,
|
||||
start="",
|
||||
@@ -65,13 +47,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
raw: True or false
|
||||
start: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-pt")
|
||||
await pt_command(ctx, ticker, raw, start)
|
||||
await ShowView().discord(pt_command, inter, "dd-pt", ticker, raw, start)
|
||||
|
||||
@commands.slash_command(name="dd-est")
|
||||
async def est(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays earning estimates [Business Insider]
|
||||
@@ -80,13 +60,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-est")
|
||||
await est_command(ctx, ticker)
|
||||
await ShowView().discord(est_command, inter, "dd-est", ticker)
|
||||
|
||||
@commands.slash_command(name="dd-sec")
|
||||
async def sec(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays sec filings [Market Watch]
|
||||
@@ -95,13 +73,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-sec")
|
||||
await sec_command(ctx, ticker)
|
||||
await ShowView().discord(sec_command, inter, "dd-sec", ticker)
|
||||
|
||||
@commands.slash_command(name="dd-supplier")
|
||||
async def supplier(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays suppliers of the company [CSIMarket]
|
||||
@@ -110,13 +86,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-supplier")
|
||||
await supplier_command(ctx, ticker)
|
||||
await ShowView().discord(supplier_command, inter, "dd-supplier", ticker)
|
||||
|
||||
@commands.slash_command(name="dd-customer")
|
||||
async def customer(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays customers of the company [CSIMarket]
|
||||
@@ -125,13 +99,11 @@ class DueDiligenceCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-customer")
|
||||
await customer_command(ctx, ticker)
|
||||
await ShowView().discord(customer_command, inter, "dd-customer", ticker)
|
||||
|
||||
@commands.slash_command(name="dd-arktrades")
|
||||
async def arktrades(
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
num: int = 10,
|
||||
):
|
||||
@@ -142,9 +114,7 @@ class DueDiligenceCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
num: number of rows displayed
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("dd-arktrades")
|
||||
await arktrades_command(ctx, ticker, num)
|
||||
await ShowView().discord(arktrades_command, inter, "dd-arktrades", ticker, num)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
156
bots/cmds/dps_cmds.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import disnake
|
||||
from disnake.ext import commands
|
||||
|
||||
from bots.helpers import ShowView, ticker_autocomp
|
||||
from bots.stocks.dark_pool_shorts.dpotc import dpotc_command
|
||||
from bots.stocks.dark_pool_shorts.ftd import ftd_command
|
||||
from bots.stocks.dark_pool_shorts.hsi import hsi_command
|
||||
from bots.stocks.dark_pool_shorts.pos import pos_command
|
||||
from bots.stocks.dark_pool_shorts.psi import psi_command
|
||||
from bots.stocks.dark_pool_shorts.shorted import shorted_command
|
||||
from bots.stocks.dark_pool_shorts.sidtc import sidtc_command
|
||||
from bots.stocks.dark_pool_shorts.spos import spos_command
|
||||
|
||||
pos_choices = {
|
||||
"Short Vol (1M)": "sv",
|
||||
"Short Vol %": "sv_pct",
|
||||
"Net Short Vol (1M)": "nsv",
|
||||
"Net Short Vol ($100M)": "nsv_dollar",
|
||||
"DP Position (1M)": "dpp",
|
||||
"DP Position ($1B)": "dpp_dollar",
|
||||
}
|
||||
|
||||
|
||||
class DarkPoolShortsCommands(commands.Cog):
|
||||
"""Dark Pool Shorts menu"""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.bot.help_command.cog = self
|
||||
|
||||
def cog_unload(self):
|
||||
self.bot.help_command = None
|
||||
|
||||
@commands.slash_command(name="dps-shorted")
|
||||
async def shorted(self, inter: disnake.AppCmdInter, num: int = 10):
|
||||
"""Show most shorted stocks [Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
num: Number of the most shorted stocks to retrieve
|
||||
"""
|
||||
await ShowView().discord(shorted_command, inter, "dps-shorted", num)
|
||||
|
||||
@commands.slash_command(name="dps-hsi")
|
||||
async def hsi(self, inter: disnake.AppCmdInter, num: int = 10):
|
||||
"""Show top high short interest stocks of over 20% ratio [shortinterest.com]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
num: Number of top stocks to print
|
||||
"""
|
||||
await ShowView().discord(hsi_command, inter, "dps-hsi", num)
|
||||
|
||||
@commands.slash_command(name="dps-pos")
|
||||
async def pos(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
sort: str = commands.Param(choices=pos_choices),
|
||||
num: int = 10,
|
||||
):
|
||||
"""Dark pool short position [Stockgrid]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
sort: Field for which to sort.
|
||||
num: Number of top tickers to show
|
||||
"""
|
||||
await ShowView().discord(pos_command, inter, "dps-pos", sort, num)
|
||||
|
||||
@commands.slash_command(name="dps-sidtc")
|
||||
async def sidtc(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
sort: str = commands.Param(
|
||||
choices={
|
||||
"Float Short %": "float",
|
||||
"Days to Cover": "dtc",
|
||||
"Short Interest": "si",
|
||||
}
|
||||
),
|
||||
num: int = 10,
|
||||
):
|
||||
"""Short interest and days to cover [Stockgrid]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
sort: Field for which to sort. Possible are: `float`, `dtc`, `si`.
|
||||
num: Number of top tickers to show
|
||||
"""
|
||||
await ShowView().discord(sidtc_command, inter, "dps-sidtc", sort, num)
|
||||
|
||||
@commands.slash_command(name="dps-ftd")
|
||||
async def ftd(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
start="",
|
||||
end="",
|
||||
):
|
||||
"""Fails-to-deliver data [SEC]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: Stock Ticker
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ShowView().discord(ftd_command, inter, "dps-ftd", ticker, start, end)
|
||||
|
||||
@commands.slash_command(name="dps-dpotc")
|
||||
async def dpotc(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Dark pools (ATS) vs OTC data [FINRA]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ShowView().discord(dpotc_command, inter, "dps-dpotc", ticker)
|
||||
|
||||
@commands.slash_command(name="dps-spos")
|
||||
async def spos(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Net short vs position [Stockgrid]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ShowView().discord(spos_command, inter, "dps-spos", ticker)
|
||||
|
||||
@commands.slash_command(name="dps-psi")
|
||||
async def psi(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Price vs short interest volume [Stockgrid]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ShowView().discord(psi_command, inter, "dps-psi", ticker)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(DarkPoolShortsCommands(bot))
|
||||
149
bots/cmds/economy_cmds.py
Normal file
@@ -0,0 +1,149 @@
|
||||
import disnake
|
||||
from disnake.ext import commands
|
||||
|
||||
from bots.economy.currencies import currencies_command
|
||||
from bots.economy.energy import energy_command
|
||||
from bots.economy.feargreed import feargreed_command
|
||||
from bots.economy.futures import futures_command
|
||||
from bots.economy.glbonds import glbonds_command
|
||||
from bots.economy.grains import grains_command
|
||||
from bots.economy.indices import indices_command
|
||||
from bots.economy.meats import meats_command
|
||||
from bots.economy.metals import metals_command
|
||||
from bots.economy.overview import overview_command
|
||||
from bots.economy.performance import performance_command
|
||||
from bots.economy.softs import softs_command
|
||||
from bots.economy.usbonds import usbonds_command
|
||||
from bots.economy.valuation import valuation_command
|
||||
from bots.helpers import ShowView
|
||||
|
||||
group = [
|
||||
"basic_materials",
|
||||
"capitalization",
|
||||
"communication_services",
|
||||
"consumer_cyclical",
|
||||
"consumer_defensive",
|
||||
"country",
|
||||
"energy",
|
||||
"financial",
|
||||
"healthcare",
|
||||
"industry",
|
||||
"industrials",
|
||||
"real_estate",
|
||||
"sector",
|
||||
"technology",
|
||||
"utilities",
|
||||
]
|
||||
|
||||
fgind = {
|
||||
"Junk Bond Demand": "jbd",
|
||||
"Market Volatility": "mv",
|
||||
"Put and Call Options": "pco",
|
||||
"Market Momentum": "mm",
|
||||
"Stock Price Strength": "sps",
|
||||
"Stock Price Breadth": "spb",
|
||||
"Safe Heaven Demand": "shd",
|
||||
}
|
||||
|
||||
|
||||
class EconomyCommands(commands.Cog):
|
||||
"""Economy Commands menu"""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.slash_command(name="econ-feargreed")
|
||||
async def feargreed(self, inter: disnake.AppCmdInter):
|
||||
"""Feargreed command [CNN]"""
|
||||
await ShowView().discord(feargreed_command, inter, "econ-feargreed")
|
||||
|
||||
@commands.slash_command(name="econ-overview")
|
||||
async def overview(self, inter: disnake.AppCmdInter):
|
||||
"""Market data overview [Wall St. Journal]"""
|
||||
await ShowView().discord(overview_command, inter, "econ-overview")
|
||||
|
||||
@commands.slash_command(name="econ-indices")
|
||||
async def indices(self, inter: disnake.AppCmdInter):
|
||||
"""US indices overview [Wall St. Journal]"""
|
||||
await ShowView().discord(indices_command, inter, "econ-indices")
|
||||
|
||||
@commands.slash_command(name="econ-futures")
|
||||
async def futures(self, inter: disnake.AppCmdInter):
|
||||
"""Futures and commodities overview [Wall St. Journal]"""
|
||||
await ShowView().discord(futures_command, inter, "econ-futures")
|
||||
|
||||
@commands.slash_command(name="econ-usbonds")
|
||||
async def usbonds(self, inter: disnake.AppCmdInter):
|
||||
"""US bonds overview [Wall St. Journal]"""
|
||||
await ShowView().discord(usbonds_command, inter, "econ-usbonds")
|
||||
|
||||
@commands.slash_command(name="econ-glbonds")
|
||||
async def glbonds(self, inter: disnake.AppCmdInter):
|
||||
"""Global bonds overview [Wall St. Journal]"""
|
||||
await ShowView().discord(glbonds_command, inter, "econ-glbonds")
|
||||
|
||||
@commands.slash_command(name="econ-energy")
|
||||
async def energy(self, inter: disnake.AppCmdInter):
|
||||
"""Displays energy futures data [Finviz]"""
|
||||
await ShowView().discord(energy_command, inter, "econ-energy")
|
||||
|
||||
@commands.slash_command(name="econ-metals")
|
||||
async def metals(self, inter: disnake.AppCmdInter):
|
||||
"""Displays metals futures data [Finviz]"""
|
||||
await ShowView().discord(metals_command, inter, "econ-metals")
|
||||
|
||||
@commands.slash_command(name="econ-meats")
|
||||
async def meats(self, inter: disnake.AppCmdInter):
|
||||
"""Displays meats futures data [Finviz]"""
|
||||
await ShowView().discord(meats_command, inter, "econ-meats")
|
||||
|
||||
@commands.slash_command(name="econ-grains")
|
||||
async def grains(self, inter: disnake.AppCmdInter):
|
||||
"""Displays grains futures data [Finviz]"""
|
||||
await ShowView().discord(grains_command, inter, "econ-grains")
|
||||
|
||||
@commands.slash_command(name="econ-softs")
|
||||
async def softs(self, inter: disnake.AppCmdInter):
|
||||
"""Displays softs futures data [Finviz]"""
|
||||
await ShowView().discord(softs_command, inter, "econ-softs")
|
||||
|
||||
@commands.slash_command(name="econ-currencies")
|
||||
async def currencies(self, inter: disnake.AppCmdInter):
|
||||
"""Currencies overview [Wall St. Journal]"""
|
||||
await ShowView().discord(currencies_command, inter, "econ-currencies")
|
||||
|
||||
@commands.slash_command(name="econ-valuation")
|
||||
async def valuation(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
economy_group: str = commands.Param(choices=group),
|
||||
):
|
||||
"""Valuation of sectors, industry, country [Finviz]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
economy_group: Economy Group
|
||||
"""
|
||||
await ShowView().discord(
|
||||
valuation_command, inter, "econ-valuation", economy_group
|
||||
)
|
||||
|
||||
@commands.slash_command(name="econ-performance")
|
||||
async def performance(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
economy_group: str = commands.Param(choices=group),
|
||||
):
|
||||
"""Performance of sectors, industry, country [Finviz]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
economy_group: Economy Group
|
||||
"""
|
||||
await ShowView().discord(
|
||||
performance_command, inter, "econ-performance", economy_group
|
||||
)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(EconomyCommands(bot))
|
||||
@@ -1,35 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
from disnake.ext import commands
|
||||
|
||||
from discordbot.config_discordbot import logger
|
||||
from discordbot.stocks.government.contracts import contracts_command
|
||||
from discordbot.stocks.government.gtrades import gtrades_command
|
||||
from discordbot.stocks.government.histcont import histcont_command
|
||||
from discordbot.stocks.government.lastcontracts import lastcontracts_command
|
||||
from discordbot.stocks.government.lasttrades import lasttrades_command
|
||||
from discordbot.stocks.government.lobbying import lobbying_command
|
||||
from discordbot.stocks.government.qtrcontracts import qtrcontracts_command
|
||||
from discordbot.stocks.government.topbuys import topbuys_command
|
||||
from discordbot.stocks.government.toplobbying import toplobbying_command
|
||||
from discordbot.stocks.government.topsells import topsells_command
|
||||
|
||||
|
||||
def default_completion(inter: disnake.AppCmdInter) -> list[str]:
|
||||
return ["Start Typing", "for a", "stock ticker"]
|
||||
|
||||
|
||||
def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str):
|
||||
if not ticker:
|
||||
return default_completion(inter)
|
||||
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]
|
||||
from bots.helpers import ShowView, ticker_autocomp
|
||||
from bots.stocks.government.contracts import contracts_command
|
||||
from bots.stocks.government.gtrades import gtrades_command
|
||||
from bots.stocks.government.histcont import histcont_command
|
||||
from bots.stocks.government.lastcontracts import lastcontracts_command
|
||||
from bots.stocks.government.lasttrades import lasttrades_command
|
||||
from bots.stocks.government.lobbying import lobbying_command
|
||||
from bots.stocks.government.qtrcontracts import qtrcontracts_command
|
||||
from bots.stocks.government.topbuys import topbuys_command
|
||||
from bots.stocks.government.toplobbying import toplobbying_command
|
||||
from bots.stocks.government.topsells import topsells_command
|
||||
|
||||
|
||||
class GovernmentCommands(commands.Cog):
|
||||
@@ -45,7 +29,7 @@ class GovernmentCommands(commands.Cog):
|
||||
@commands.slash_command(name="gov-lasttrades")
|
||||
async def lasttrades(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
gov_type: str = commands.Param(choices=["congress", "senate", "house"]),
|
||||
past_days: int = 5,
|
||||
representative="",
|
||||
@@ -58,14 +42,19 @@ class GovernmentCommands(commands.Cog):
|
||||
past_days: Positive number of past transaction days
|
||||
representative: Enter name of a representative
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-lasttrades")
|
||||
await lasttrades_command(ctx, gov_type, past_days, representative)
|
||||
await ShowView().discord(
|
||||
lasttrades_command,
|
||||
inter,
|
||||
"gov-lasttrades",
|
||||
gov_type,
|
||||
past_days,
|
||||
representative,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-topbuys")
|
||||
async def topbuys(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
gov_type: str = commands.Param(choices=["congress", "senate", "house"]),
|
||||
past_transactions_months: int = 5,
|
||||
num: int = 20,
|
||||
@@ -80,14 +69,20 @@ class GovernmentCommands(commands.Cog):
|
||||
num: Number of most sold stocks to retrieve
|
||||
raw: If raw data should be outputted
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-topbuys")
|
||||
await topbuys_command(ctx, gov_type, past_transactions_months, num, raw)
|
||||
await ShowView().discord(
|
||||
topbuys_command,
|
||||
inter,
|
||||
"gov-topbuys",
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
num,
|
||||
raw,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-topsells")
|
||||
async def topsells(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
gov_type: str = commands.Param(choices=["congress", "senate", "house"]),
|
||||
past_transactions_months: int = 5,
|
||||
num: int = 10,
|
||||
@@ -102,13 +97,19 @@ class GovernmentCommands(commands.Cog):
|
||||
num: Number of most sold stocks to retrieve
|
||||
raw: If raw data should be outputted
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-topsells")
|
||||
await topsells_command(ctx, gov_type, past_transactions_months, num, raw)
|
||||
await ShowView().discord(
|
||||
topsells_command,
|
||||
inter,
|
||||
"gov-topsells",
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
num,
|
||||
raw,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-lastcontracts")
|
||||
async def lastcontracts(
|
||||
self, ctx: disnake.AppCmdInter, past_transactions_days: int = 2, num: int = 20
|
||||
self, inter: disnake.AppCmdInter, past_transactions_days: int = 2, num: int = 20
|
||||
):
|
||||
"""Displays last government contracts [quiverquant.com]
|
||||
|
||||
@@ -117,14 +118,18 @@ class GovernmentCommands(commands.Cog):
|
||||
past_transactions_days: Positive number of past transaction days
|
||||
num: Number of contracts
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-lastcontracts")
|
||||
await lastcontracts_command(ctx, past_transactions_days, num)
|
||||
await ShowView().discord(
|
||||
lastcontracts_command,
|
||||
inter,
|
||||
"gov-lastcontracts",
|
||||
past_transactions_days,
|
||||
num,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-qtrcontracts")
|
||||
async def qtrcontracts(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
num: int = 20,
|
||||
analysis: str = commands.Param(choices=["total", "upmom", "downmom"]),
|
||||
):
|
||||
@@ -135,13 +140,13 @@ class GovernmentCommands(commands.Cog):
|
||||
analysis: Possible arguments: total, upmom & downmom
|
||||
num: Number of contracts
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-qtrcontracts")
|
||||
await qtrcontracts_command(ctx, num, analysis)
|
||||
await ShowView().discord(
|
||||
qtrcontracts_command, inter, "gov-qtrcontracts", num, analysis
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-toplobbying")
|
||||
async def toplobbying(
|
||||
self, ctx: disnake.AppCmdInter, num: int = 10, raw: bool = False
|
||||
self, inter: disnake.AppCmdInter, num: int = 10, raw: bool = False
|
||||
):
|
||||
"""Displays top lobbying firms [quiverquant.com]
|
||||
|
||||
@@ -150,14 +155,14 @@ class GovernmentCommands(commands.Cog):
|
||||
num: Number to show
|
||||
raw: If raw data should be outputted
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-toplobbying")
|
||||
await toplobbying_command(ctx, num, raw)
|
||||
await ShowView().discord(
|
||||
toplobbying_command, inter, "gov-toplobbying", num, raw
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-gtrades")
|
||||
async def gtrades(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
gov_type: str = commands.Param(choices=["congress", "senate", "house"]),
|
||||
past_transactions_months: int = 10,
|
||||
@@ -172,14 +177,20 @@ class GovernmentCommands(commands.Cog):
|
||||
past_transactions_months: Positive number of past transaction months
|
||||
raw: If raw data should be outputted
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-gtrades")
|
||||
await gtrades_command(ctx, ticker, gov_type, past_transactions_months, raw)
|
||||
await ShowView().discord(
|
||||
gtrades_command,
|
||||
inter,
|
||||
"gov-gtrades",
|
||||
ticker,
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
raw,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-contracts")
|
||||
async def contracts(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
past_transaction_days: int = 10,
|
||||
raw: bool = False,
|
||||
@@ -192,14 +203,19 @@ class GovernmentCommands(commands.Cog):
|
||||
past_transaction_days: Number of past transaction months
|
||||
raw: If raw data should be outputted
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-contracts")
|
||||
await contracts_command(ctx, ticker, past_transaction_days, raw)
|
||||
await ShowView().discord(
|
||||
contracts_command,
|
||||
inter,
|
||||
"gov-contracts",
|
||||
ticker,
|
||||
past_transaction_days,
|
||||
raw,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="gov-histcont")
|
||||
async def histcont(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays historical quarterly-contracts [quiverquant.com]
|
||||
@@ -208,14 +224,12 @@ class GovernmentCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-histcont")
|
||||
await histcont_command(ctx, ticker)
|
||||
await ShowView().discord(histcont_command, inter, "gov-histcont", ticker)
|
||||
|
||||
@commands.slash_command(name="gov-lobbying")
|
||||
async def lobbying(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
num: int = 10,
|
||||
):
|
||||
@@ -226,9 +240,7 @@ class GovernmentCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
num: Number of events
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("gov-lobbying")
|
||||
await lobbying_command(ctx, ticker, num)
|
||||
await ShowView().discord(lobbying_command, inter, "gov-lobbying", ticker, num)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
@@ -1,74 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pickle
|
||||
import time
|
||||
|
||||
import disnake
|
||||
import disnake.ext.commands as commands
|
||||
import pandas as pd
|
||||
|
||||
from discordbot.config_discordbot import logger
|
||||
from discordbot.stocks.candle import candle_command
|
||||
from discordbot.stocks.disc.ford import ford_command
|
||||
from discordbot.stocks.insider.lins import lins_command
|
||||
from discordbot.stocks.options.cc_hist import cc_hist_command
|
||||
from discordbot.stocks.options.hist import hist_command
|
||||
from discordbot.stocks.options.iv import iv_command
|
||||
from discordbot.stocks.options.oi import oi_command
|
||||
from discordbot.stocks.options.opt_chain import chain_command
|
||||
from discordbot.stocks.options.overview import overview_command
|
||||
from discordbot.stocks.options.unu import unu_command
|
||||
from discordbot.stocks.options.vol import vol_command
|
||||
from discordbot.stocks.options.vsurf import vsurf_command
|
||||
from gamestonk_terminal.stocks.options import yfinance_model
|
||||
from bots.helpers import ShowView, expiry_autocomp, ticker_autocomp
|
||||
from bots.stocks.candle import candle_command
|
||||
from bots.stocks.disc.ford import ford_command
|
||||
from bots.stocks.insider.lins import lins_command
|
||||
from bots.stocks.options.cc_hist import cc_hist_command
|
||||
from bots.stocks.options.hist import hist_command
|
||||
from bots.stocks.options.iv import iv_command
|
||||
from bots.stocks.options.oi import oi_command
|
||||
from bots.stocks.options.opt_chain import chain_command
|
||||
from bots.stocks.options.overview import overview_command
|
||||
from bots.stocks.options.unu import unu_command
|
||||
from bots.stocks.options.vol import vol_command
|
||||
from bots.stocks.options.vsurf import vsurf_command
|
||||
from bots.stocks.quote import quote_command
|
||||
|
||||
startTime = time.time()
|
||||
data = []
|
||||
|
||||
|
||||
class Ticker:
|
||||
def __init__(self, ticker):
|
||||
super().__init__()
|
||||
global tickerr
|
||||
self._ticker = ticker
|
||||
tickerr = ticker
|
||||
|
||||
@property
|
||||
def ticker(self):
|
||||
return self._ticker
|
||||
|
||||
def __repr__(self) -> str:
|
||||
data.append(f"{self._ticker}")
|
||||
file = open("ticker", "wb")
|
||||
pickle.dump(data, file)
|
||||
file.close()
|
||||
return f"{self._ticker}"
|
||||
|
||||
|
||||
def default_completion(inter: disnake.AppCmdInter) -> list[str]:
|
||||
return ["Start Typing", "for a", "stock ticker"]
|
||||
|
||||
|
||||
def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str):
|
||||
if not ticker:
|
||||
return default_completion(inter)
|
||||
if ticker:
|
||||
ticker = str(Ticker(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: disnake.AppCmdInter, tickerr: str):
|
||||
file = open("ticker", "rb")
|
||||
data = pickle.load(file)
|
||||
file.close()
|
||||
print(data)
|
||||
dates = yfinance_model.option_expirations(data[-1])
|
||||
return [dates for dates in dates][:24]
|
||||
vsurf_choices = {
|
||||
"Volatility": "IV",
|
||||
"Open Interest": "OI",
|
||||
"Last Price": "LP",
|
||||
}
|
||||
|
||||
|
||||
class SlashCommands(commands.Cog):
|
||||
@@ -96,9 +50,9 @@ class SlashCommands(commands.Cog):
|
||||
min_sp: Minimum Strike Price
|
||||
max_sp: Maximum Strike Price
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-chain")
|
||||
await chain_command(inter, ticker, expiry, opt_type, min_sp, max_sp)
|
||||
await ShowView().discord(
|
||||
chain_command, inter, "opt-chain", ticker, expiry, opt_type, min_sp, max_sp
|
||||
)
|
||||
|
||||
@commands.slash_command(name="opt-oi")
|
||||
async def open_interest(
|
||||
@@ -118,9 +72,9 @@ class SlashCommands(commands.Cog):
|
||||
min_sp: Minimum Strike Price
|
||||
max_sp: Maximum Strike Price
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-oi")
|
||||
await oi_command(inter, ticker, expiry, min_sp, max_sp)
|
||||
await ShowView().discord(
|
||||
oi_command, inter, "opt-oi", ticker, expiry, min_sp, max_sp
|
||||
)
|
||||
|
||||
@commands.slash_command(name="opt-iv")
|
||||
async def iv(
|
||||
@@ -134,9 +88,21 @@ class SlashCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-iv")
|
||||
await iv_command(inter, ticker)
|
||||
await ShowView().discord(iv_command, inter, "opt-iv", ticker)
|
||||
|
||||
@commands.slash_command(name="q")
|
||||
async def quote(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays ticker quote [yFinance]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ShowView().discord(quote_command, inter, "quote", ticker)
|
||||
|
||||
@commands.slash_command(name="disc-ford")
|
||||
async def ford(self, inter: disnake.AppCmdInter):
|
||||
@@ -146,16 +112,12 @@ class SlashCommands(commands.Cog):
|
||||
-----------
|
||||
num: Number of stocks to display
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("disc-ford")
|
||||
await ford_command(inter)
|
||||
await ShowView().discord(ford_command, inter, "disc-ford")
|
||||
|
||||
@commands.slash_command(name="opt-unu")
|
||||
async def unu(self, inter: disnake.AppCmdInter):
|
||||
"""Unusual Options"""
|
||||
await inter.response.defer()
|
||||
logger.info("disc-ford")
|
||||
await unu_command(inter)
|
||||
await ShowView().discord(unu_command, inter, "disc-ford")
|
||||
|
||||
@commands.slash_command(name="ins-last")
|
||||
async def lins(
|
||||
@@ -171,9 +133,7 @@ class SlashCommands(commands.Cog):
|
||||
ticker : Stock Ticker
|
||||
num : Number of latest insider activity to display
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("ins-last")
|
||||
await lins_command(inter, ticker, num)
|
||||
await lins_command(inter, "ins-last", ticker, num)
|
||||
|
||||
@commands.slash_command(name="cc")
|
||||
async def cc(
|
||||
@@ -195,9 +155,9 @@ class SlashCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("cc")
|
||||
await candle_command(inter, ticker, interval, past_days, start, end)
|
||||
await ShowView().discord(
|
||||
candle_command, inter, "cc", ticker, interval, past_days, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="btc")
|
||||
async def btc(
|
||||
@@ -217,9 +177,9 @@ class SlashCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("btc")
|
||||
await candle_command(inter, "btc-usd", interval, past_days, start, end)
|
||||
await ShowView().discord(
|
||||
candle_command, inter, "btc", "btc-usd", interval, past_days, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="eth")
|
||||
async def eth(
|
||||
@@ -239,9 +199,9 @@ class SlashCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("eth")
|
||||
await candle_command(inter, "eth-usd", interval, past_days, start, end)
|
||||
await ShowView().discord(
|
||||
candle_command, inter, "eth", "eth-usd", interval, past_days, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="sol")
|
||||
async def sol(
|
||||
@@ -261,9 +221,9 @@ class SlashCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("sol")
|
||||
await candle_command(inter, "sol-usd", interval, past_days, start, end)
|
||||
await ShowView().discord(
|
||||
candle_command, inter, "sol", "sol-usd", interval, past_days, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="opt-overview")
|
||||
async def overview(
|
||||
@@ -283,9 +243,9 @@ class SlashCommands(commands.Cog):
|
||||
min_sp: Minimum Strike Price
|
||||
max_sp: Maximum Strike Price
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-overview")
|
||||
await overview_command(inter, ticker, expiry, min_sp, max_sp)
|
||||
await ShowView().discord(
|
||||
overview_command, inter, "opt-overview", ticker, expiry, min_sp, max_sp
|
||||
)
|
||||
|
||||
@commands.slash_command(name="opt-vol")
|
||||
async def volume(
|
||||
@@ -301,16 +261,14 @@ class SlashCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
expiry: Expiration Date
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-vol")
|
||||
await vol_command(inter, ticker, expiry)
|
||||
await ShowView().discord(vol_command, inter, "opt-vol", ticker, expiry)
|
||||
|
||||
@commands.slash_command(name="opt-vsurf")
|
||||
async def vsurf(
|
||||
self,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
z: str = commands.Param(choices=["Volatility", "Open Interest", "Last Price"]),
|
||||
z: str = commands.Param(choices=vsurf_choices),
|
||||
):
|
||||
"""Display Volatility Surface
|
||||
|
||||
@@ -319,17 +277,7 @@ class SlashCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
z: The variable for the Z axis
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-vsurf")
|
||||
|
||||
if z == "Volatility":
|
||||
z = "IV"
|
||||
if z == "Open Interest":
|
||||
z = "OI"
|
||||
if z == "Last Price":
|
||||
z = "LP"
|
||||
|
||||
await vsurf_command(inter, ticker, z)
|
||||
await ShowView().discord(vsurf_command, inter, "opt-vsurf", ticker, z)
|
||||
|
||||
@commands.slash_command(name="opt-hist")
|
||||
async def history(
|
||||
@@ -358,12 +306,12 @@ class SlashCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
expiry: Expiration Date
|
||||
strike: Options Strike Price
|
||||
type: Calls or Puts
|
||||
opt_type: Calls or Puts
|
||||
greek: Greek variable to plot
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-hist")
|
||||
await hist_command(inter, ticker, expiry, strike, opt_type, greek)
|
||||
await ShowView().discord(
|
||||
hist_command, inter, "opt-hist", ticker, expiry, strike, opt_type, greek
|
||||
)
|
||||
|
||||
@commands.slash_command(name="opt-cc-hist")
|
||||
async def cc_history(
|
||||
@@ -381,14 +329,12 @@ class SlashCommands(commands.Cog):
|
||||
ticker: Stock Ticker
|
||||
expiry: Expiration Date
|
||||
strike: Options Strike Price
|
||||
type: Calls or Puts
|
||||
opt_type: Calls or Puts
|
||||
"""
|
||||
await inter.response.defer()
|
||||
logger.info("opt-cc-hist")
|
||||
await cc_hist_command(inter, ticker, expiry, strike, opt_type)
|
||||
await ShowView().discord(
|
||||
cc_hist_command, inter, "opt-cc-hist", ticker, expiry, strike, opt_type
|
||||
)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(SlashCommands(bot))
|
||||
executionTime = time.time() - startTime
|
||||
print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n")
|
||||
@@ -1,96 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
from disnake.ext import commands
|
||||
|
||||
from discordbot.config_discordbot import logger
|
||||
from discordbot.stocks.screener.financial import financial_command
|
||||
from discordbot.stocks.screener.historical import historical_command
|
||||
from discordbot.stocks.screener.overview import overview_command
|
||||
from discordbot.stocks.screener.ownership import ownership_command
|
||||
from discordbot.stocks.screener.performance import performance_command
|
||||
from discordbot.stocks.screener.presets_custom import presets_custom_command
|
||||
from discordbot.stocks.screener.presets_default import presets_default_command
|
||||
from discordbot.stocks.screener.technical import technical_command
|
||||
from discordbot.stocks.screener.valuation import valuation_command
|
||||
from bots.helpers import ShowView, presets_custom_autocomp, signals_autocomp
|
||||
from bots.stocks.screener.financial import financial_command
|
||||
from bots.stocks.screener.historical import historical_command
|
||||
from bots.stocks.screener.overview import overview_command
|
||||
from bots.stocks.screener.ownership import ownership_command
|
||||
from bots.stocks.screener.performance import performance_command
|
||||
from bots.stocks.screener.presets_custom import presets_custom_command
|
||||
from bots.stocks.screener.presets_default import presets_default_command
|
||||
from bots.stocks.screener.technical import technical_command
|
||||
from bots.stocks.screener.valuation import valuation_command
|
||||
|
||||
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",
|
||||
]
|
||||
sort = {
|
||||
"overview": [
|
||||
"Ticker",
|
||||
@@ -195,39 +118,6 @@ sort = {
|
||||
# pylint: disable=R0912
|
||||
|
||||
|
||||
def default_completion(inter: disnake.AppCmdInter) -> list[str]:
|
||||
return ["Start Typing", "for a", "stock ticker"]
|
||||
|
||||
|
||||
def presets_custom_autocomp(inter: disnake.AppCmdInter, preset: str):
|
||||
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: disnake.AppCmdInter, signal: str):
|
||||
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 ticker_autocomp(inter: disnake.AppCmdInter, ticker: str):
|
||||
if not ticker:
|
||||
return default_completion(inter)
|
||||
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]
|
||||
|
||||
|
||||
class ScreenerCommands(commands.Cog):
|
||||
"""Screener menu"""
|
||||
|
||||
@@ -235,21 +125,19 @@ class ScreenerCommands(commands.Cog):
|
||||
self.bot = bot
|
||||
|
||||
@commands.slash_command(name="scr-presets_default")
|
||||
async def presets_default(self, ctx: disnake.AppCmdInter):
|
||||
async def presets_default(self, inter: disnake.AppCmdInter):
|
||||
"""Displays every available preset"""
|
||||
logger.info("scr-presets_default")
|
||||
await presets_default_command(ctx)
|
||||
await ShowView().discord(presets_default_command, inter, "scr-presets_default")
|
||||
|
||||
@commands.slash_command(name="scr-presets_custom")
|
||||
async def presets_custom(self, ctx: disnake.AppCmdInter):
|
||||
async def presets_custom(self, inter: disnake.AppCmdInter):
|
||||
"""Displays every available preset"""
|
||||
logger.info("scr-presets_custom")
|
||||
await presets_custom_command(ctx)
|
||||
await ShowView().discord(presets_custom_command, inter, "scr-presets_custom")
|
||||
|
||||
@commands.slash_command(name="scr-historical")
|
||||
async def historical(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
signal: str = commands.Param(autocomplete=signals_autocomp),
|
||||
start="",
|
||||
):
|
||||
@@ -260,14 +148,14 @@ class ScreenerCommands(commands.Cog):
|
||||
signal: Signal. Default: most_volatile
|
||||
start: Starting date in YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-historical")
|
||||
await historical_command(ctx, signal, start)
|
||||
await ShowView().discord(
|
||||
historical_command, inter, "scr-historical", signal, start
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-overview")
|
||||
async def overview(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["overview"]),
|
||||
limit: int = 5,
|
||||
@@ -282,14 +170,14 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-overview")
|
||||
await overview_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
overview_command, inter, "scr-overview", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-valuation")
|
||||
async def valuation(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["valuation"]),
|
||||
limit: int = 5,
|
||||
@@ -304,14 +192,14 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-valuation")
|
||||
await valuation_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
valuation_command, inter, "scr-valuation", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-financial")
|
||||
async def financial(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["financial"]),
|
||||
limit: int = 5,
|
||||
@@ -326,14 +214,14 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-financial")
|
||||
await financial_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
financial_command, inter, "scr-financial", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-ownership")
|
||||
async def ownership(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["ownership"]),
|
||||
limit: int = 5,
|
||||
@@ -348,14 +236,14 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-ownership")
|
||||
await ownership_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
ownership_command, inter, "scr-ownership", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-performance")
|
||||
async def performance(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["performance"]),
|
||||
limit: int = 5,
|
||||
@@ -370,14 +258,14 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-performance")
|
||||
await performance_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
performance_command, inter, "scr-performance", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
@commands.slash_command(name="scr-technical")
|
||||
async def technical(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
preset: str = commands.Param(autocomplete=presets_custom_autocomp),
|
||||
sort: str = commands.Param(choices=sort["technical"]),
|
||||
limit: int = 5,
|
||||
@@ -392,9 +280,9 @@ class ScreenerCommands(commands.Cog):
|
||||
limit: number of stocks to display
|
||||
ascend: whether it's sorted by ascending order or not. Default: False
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("scr-technical")
|
||||
await technical_command(ctx, preset, sort, limit, ascend)
|
||||
await ShowView().discord(
|
||||
technical_command, inter, "scr-technical", preset, sort, limit, ascend
|
||||
)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
@@ -2,33 +2,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
from disnake.ext import commands
|
||||
|
||||
from discordbot.config_discordbot import logger
|
||||
from discordbot.stocks.technical_analysis.ad import ad_command
|
||||
from discordbot.stocks.technical_analysis.adosc import adosc_command
|
||||
from discordbot.stocks.technical_analysis.adx import adx_command
|
||||
from discordbot.stocks.technical_analysis.aroon import aroon_command
|
||||
from discordbot.stocks.technical_analysis.bbands import bbands_command
|
||||
from discordbot.stocks.technical_analysis.cci import cci_command
|
||||
from discordbot.stocks.technical_analysis.cg import cg_command
|
||||
from discordbot.stocks.technical_analysis.donchian import donchian_command
|
||||
from discordbot.stocks.technical_analysis.ema import ema_command
|
||||
from discordbot.stocks.technical_analysis.fib import fib_command
|
||||
from discordbot.stocks.technical_analysis.fisher import fisher_command
|
||||
from discordbot.stocks.technical_analysis.hma import hma_command
|
||||
from discordbot.stocks.technical_analysis.kc import kc_command
|
||||
from discordbot.stocks.technical_analysis.macd import macd_command
|
||||
from discordbot.stocks.technical_analysis.obv import obv_command
|
||||
from discordbot.stocks.technical_analysis.recom import recom_command
|
||||
from discordbot.stocks.technical_analysis.rsi import rsi_command
|
||||
from discordbot.stocks.technical_analysis.sma import sma_command
|
||||
from discordbot.stocks.technical_analysis.stoch import stoch_command
|
||||
from discordbot.stocks.technical_analysis.summary import summary_command
|
||||
from discordbot.stocks.technical_analysis.view import view_command
|
||||
from discordbot.stocks.technical_analysis.wma import wma_command
|
||||
from discordbot.stocks.technical_analysis.zlma import zlma_command
|
||||
from bots.helpers import ShowView, ticker_autocomp
|
||||
from bots.stocks.technical_analysis.ad import ad_command
|
||||
from bots.stocks.technical_analysis.adosc import adosc_command
|
||||
from bots.stocks.technical_analysis.adx import adx_command
|
||||
from bots.stocks.technical_analysis.aroon import aroon_command
|
||||
from bots.stocks.technical_analysis.bbands import bbands_command
|
||||
from bots.stocks.technical_analysis.cci import cci_command
|
||||
from bots.stocks.technical_analysis.cg import cg_command
|
||||
from bots.stocks.technical_analysis.donchian import donchian_command
|
||||
from bots.stocks.technical_analysis.ema import ema_command
|
||||
from bots.stocks.technical_analysis.fib import fib_command
|
||||
from bots.stocks.technical_analysis.fisher import fisher_command
|
||||
from bots.stocks.technical_analysis.hma import hma_command
|
||||
from bots.stocks.technical_analysis.kc import kc_command
|
||||
from bots.stocks.technical_analysis.macd import macd_command
|
||||
from bots.stocks.technical_analysis.obv import obv_command
|
||||
from bots.stocks.technical_analysis.recom import recom_command
|
||||
from bots.stocks.technical_analysis.rsi import rsi_command
|
||||
from bots.stocks.technical_analysis.sma import sma_command
|
||||
from bots.stocks.technical_analysis.stoch import stoch_command
|
||||
from bots.stocks.technical_analysis.summary import summary_command
|
||||
from bots.stocks.technical_analysis.view import view_command
|
||||
from bots.stocks.technical_analysis.wma import wma_command
|
||||
from bots.stocks.technical_analysis.zlma import zlma_command
|
||||
|
||||
possible_ma = [
|
||||
"dema",
|
||||
@@ -51,21 +50,6 @@ possible_ma = [
|
||||
]
|
||||
|
||||
|
||||
def default_completion(inter: disnake.AppCmdInter) -> list[str]:
|
||||
return ["Start Typing", "for a", "stock ticker"]
|
||||
|
||||
|
||||
def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str):
|
||||
if not ticker:
|
||||
return default_completion(inter)
|
||||
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]
|
||||
|
||||
|
||||
class TechnicalAnalysisCommands(commands.Cog):
|
||||
"""Technical Analysis menu."""
|
||||
|
||||
@@ -75,7 +59,7 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
@commands.slash_command(name="ta-ema")
|
||||
async def ema(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
window="",
|
||||
offset: int = 0,
|
||||
@@ -92,14 +76,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-ema")
|
||||
await ema_command(ctx, ticker, window, offset, start, end)
|
||||
await ShowView().discord(
|
||||
ema_command, inter, "ta-ema", ticker, window, offset, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-sma")
|
||||
async def sma(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
window="",
|
||||
offset: int = 0,
|
||||
@@ -116,14 +100,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-sma")
|
||||
await sma_command(ctx, ticker, window, offset, start, end)
|
||||
await ShowView().discord(
|
||||
sma_command, inter, "ta-sma", ticker, window, offset, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-wma")
|
||||
async def wma(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
window="",
|
||||
offset: int = 0,
|
||||
@@ -140,14 +124,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-wma")
|
||||
await wma_command(ctx, ticker, window, offset, start, end)
|
||||
await ShowView().discord(
|
||||
wma_command, inter, "ta-wma", ticker, window, offset, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-hma")
|
||||
async def hma(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
window="",
|
||||
offset: int = 0,
|
||||
@@ -164,14 +148,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-hma")
|
||||
await hma_command(ctx, ticker, window, offset, start, end)
|
||||
await ShowView().discord(
|
||||
hma_command, inter, "ta-hma", ticker, window, offset, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-zlma")
|
||||
async def zlma(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
window="",
|
||||
offset: int = 0,
|
||||
@@ -188,14 +172,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-zlma")
|
||||
await zlma_command(ctx, ticker, window, offset, start, end)
|
||||
await ShowView().discord(
|
||||
zlma_command, inter, "ta-zlma", ticker, window, offset, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-cci")
|
||||
async def cci(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="14",
|
||||
scalar="0.015",
|
||||
@@ -212,14 +196,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-cci")
|
||||
await cci_command(ctx, ticker, length, scalar, start, end)
|
||||
await ShowView().discord(
|
||||
cci_command, inter, "ta-cci", ticker, length, scalar, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-macd")
|
||||
async def macd(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
fast="12",
|
||||
slow="26",
|
||||
@@ -238,14 +222,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-macd")
|
||||
await macd_command(ctx, ticker, fast, slow, signal, start, end)
|
||||
await ShowView().discord(
|
||||
macd_command, inter, "ta-macd", ticker, fast, slow, signal, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-rsi")
|
||||
async def rsi(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="14",
|
||||
scalar="100",
|
||||
@@ -264,14 +248,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-rsi")
|
||||
await rsi_command(ctx, ticker, length, scalar, drift, start, end)
|
||||
await ShowView().discord(
|
||||
rsi_command, inter, "ta-rsi", ticker, length, scalar, drift, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-stoch")
|
||||
async def stoch(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
fast_k="14",
|
||||
slow_d="3",
|
||||
@@ -290,14 +274,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-stoch")
|
||||
await stoch_command(ctx, ticker, fast_k, slow_d, slow_k, start, end)
|
||||
await ShowView().discord(
|
||||
stoch_command, inter, "ta-stoch", ticker, fast_k, slow_d, slow_k, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-fisher")
|
||||
async def fisher(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="14",
|
||||
start="",
|
||||
@@ -312,14 +296,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-fisher")
|
||||
await fisher_command(ctx, ticker, length, start, end)
|
||||
await ShowView().discord(
|
||||
fisher_command, inter, "ta-fisher", ticker, length, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-cg")
|
||||
async def cg(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="14",
|
||||
start="",
|
||||
@@ -334,14 +318,12 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-cg")
|
||||
await cg_command(ctx, ticker, length, start, end)
|
||||
await ShowView().discord(cg_command, inter, "ta-cg", ticker, length, start, end)
|
||||
|
||||
@commands.slash_command(name="ta-adx")
|
||||
async def adx(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="14",
|
||||
scalar="100",
|
||||
@@ -360,14 +342,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-adx")
|
||||
await adx_command(ctx, ticker, length, scalar, drift, start, end)
|
||||
await ShowView().discord(
|
||||
adx_command, inter, "ta-adx", ticker, length, scalar, drift, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-aroon")
|
||||
async def aroon(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="25",
|
||||
scalar="100",
|
||||
@@ -384,14 +366,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-aroon")
|
||||
await aroon_command(ctx, ticker, length, scalar, start, end)
|
||||
await ShowView().discord(
|
||||
aroon_command, inter, "ta-aroon", ticker, length, scalar, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-bbands")
|
||||
async def bbands(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="5",
|
||||
std="2",
|
||||
@@ -410,15 +392,15 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-bbands")
|
||||
ma_mode = str(ma_mode)
|
||||
await bbands_command(ctx, ticker, length, std, ma_mode, start, end)
|
||||
await ShowView().discord(
|
||||
bbands_command, inter, "ta-bbands", ticker, length, std, ma_mode, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-donchian")
|
||||
async def donchian(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
upper_length="25",
|
||||
lower_length="100",
|
||||
@@ -435,15 +417,22 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-donchian")
|
||||
await donchian_command(ctx, ticker, upper_length, lower_length, start, end)
|
||||
await ShowView().discord(
|
||||
donchian_command,
|
||||
inter,
|
||||
"ta-donchian",
|
||||
ticker,
|
||||
upper_length,
|
||||
lower_length,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
@commands.slash_command(name="ta-kc")
|
||||
async def kc(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
length="20",
|
||||
scalar="2",
|
||||
@@ -464,15 +453,24 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-kc")
|
||||
ma_mode = str(ma_mode)
|
||||
await kc_command(ctx, ticker, length, scalar, ma_mode, offset, start, end)
|
||||
await ShowView().discord(
|
||||
kc_command,
|
||||
inter,
|
||||
"ta-kc",
|
||||
ticker,
|
||||
length,
|
||||
scalar,
|
||||
ma_mode,
|
||||
offset,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-ad")
|
||||
async def ad(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
is_open="False",
|
||||
start="",
|
||||
@@ -487,14 +485,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-ad")
|
||||
await ad_command(ctx, ticker, is_open, start, end)
|
||||
await ShowView().discord(
|
||||
ad_command, inter, "ta-ad", ticker, is_open, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-adosc")
|
||||
async def adosc(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
is_open="False",
|
||||
fast="3",
|
||||
@@ -513,14 +511,14 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-adosc")
|
||||
await adosc_command(ctx, ticker, is_open, fast, slow, start, end)
|
||||
await ShowView().discord(
|
||||
adosc_command, inter, "ta-adosc", ticker, is_open, fast, slow, start, end
|
||||
)
|
||||
|
||||
@commands.slash_command(name="ta-obv")
|
||||
async def obv(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
start="",
|
||||
end="",
|
||||
@@ -533,14 +531,12 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD
|
||||
end: YYYY-MM-DD
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-obv")
|
||||
await obv_command(ctx, ticker, start, end)
|
||||
await ShowView().discord(obv_command, inter, "ta-obv", ticker, start, end)
|
||||
|
||||
@commands.slash_command(name="ta-fib")
|
||||
async def fib(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
start="",
|
||||
end="",
|
||||
@@ -553,14 +549,12 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
start: YYYY-MM-DD format
|
||||
end: YYYY-MM-DD format
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-fib")
|
||||
await fib_command(ctx, ticker, start, end)
|
||||
await ShowView().discord(fib_command, inter, "ta-fib", ticker, start, end)
|
||||
|
||||
@commands.slash_command(name="ta-view")
|
||||
async def view(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays image from Finviz [Finviz]
|
||||
@@ -569,14 +563,12 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-view")
|
||||
await view_command(ctx, ticker)
|
||||
await ShowView().discord(view_command, inter, "ta-view", ticker)
|
||||
|
||||
@commands.slash_command(name="ta-summary")
|
||||
async def summary(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays text of a given stocks ta summary [FinBrain API]
|
||||
@@ -585,14 +577,12 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-summary")
|
||||
await summary_command(ctx, ticker)
|
||||
await ShowView().discord(summary_command, inter, "ta-summary", ticker)
|
||||
|
||||
@commands.slash_command(name="ta-recom")
|
||||
async def recom(
|
||||
self,
|
||||
ctx: disnake.AppCmdInter,
|
||||
inter: disnake.AppCmdInter,
|
||||
ticker: str = commands.Param(autocomplete=ticker_autocomp),
|
||||
):
|
||||
"""Displays text of a given stocks recommendation based on ta [Tradingview API]
|
||||
@@ -601,9 +591,7 @@ class TechnicalAnalysisCommands(commands.Cog):
|
||||
-----------
|
||||
ticker: Stock Ticker
|
||||
"""
|
||||
await ctx.response.defer()
|
||||
logger.info("ta-recom")
|
||||
await recom_command(ctx, ticker)
|
||||
await ShowView().discord(recom_command, inter, "ta-recom", ticker)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
import disnake
|
||||
import pyimgur
|
||||
|
||||
@@ -31,7 +32,7 @@ DISCORD_BOT_TOKEN = os.getenv("GT_DISCORD_BOT_TOKEN") or "REPLACE_ME"
|
||||
IMGUR_CLIENT_ID = os.getenv("GT_IMGUR_CLIENT_ID") or "REPLACE_ME"
|
||||
|
||||
# Settings
|
||||
SLASH_TESTING_SERVERS: List[int] = [] # Test server IDs
|
||||
SLASH_TESTING_SERVERS: List[int] = [936333632317046844] # Test server IDs
|
||||
COMMAND_PREFIX = "!"
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
COLOR = disnake.Color.from_rgb(255, 0, 0)
|
||||
67
bots/economy/currencies.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def currencies_command():
|
||||
"""Currencies overview [Wall St. Journal]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-currencies")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.global_currencies()
|
||||
df = pd.DataFrame.from_dict(df)
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Last"] = pd.to_numeric(df["Last"].astype(float))
|
||||
df["Chng"] = pd.to_numeric(df["Chng"].astype(float))
|
||||
df["%Chng"] = pd.to_numeric(df["%Chng"].astype(float))
|
||||
|
||||
formats = {"Last": "{:.2f}", "Chng": "{:.2f}", "%Chng": "{:.2f}%"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index(" ", inplace=True)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
df = df[
|
||||
[
|
||||
"Last",
|
||||
"Chng",
|
||||
"%Chng",
|
||||
]
|
||||
]
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-currencies.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] Currencies",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
70
bots/economy/energy.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def energy_command():
|
||||
"""Displays energy futures data [Finviz]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-energy")
|
||||
|
||||
# Retrieve data
|
||||
d_futures = finviz_model.get_futures()
|
||||
df = pd.DataFrame(d_futures["Energy"])
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
formats = {"last": "${:.2f}", "prevClose": "${:.2f}"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.sort_values(by="ticker", ascending=False)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index("label", inplace=True)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"prevClose",
|
||||
"last",
|
||||
"change",
|
||||
]
|
||||
]
|
||||
df.index.names = [""]
|
||||
df = df.rename(
|
||||
columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"}
|
||||
)
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 2],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-energy.png", fig)
|
||||
return {
|
||||
"title": "[Finviz] Energy Futures",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
57
bots/economy/feargreed.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from gamestonk_terminal.economy import cnn_model, cnn_view
|
||||
|
||||
|
||||
def feargreed_command(indicator=""):
|
||||
"""CNN Fear and Greed Index [CNN]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-futures")
|
||||
|
||||
# Check for argument
|
||||
possible_indicators = ("", "jbd", "mv", "pco", "mm", "sps", "spb", "shd")
|
||||
|
||||
if indicator not in possible_indicators:
|
||||
raise Exception(
|
||||
f"Select a valid indicator from {', '.join(possible_indicators)}" # nosec
|
||||
)
|
||||
|
||||
# Retrieve data
|
||||
fig = plt.figure(figsize=[1, 1], dpi=10)
|
||||
|
||||
report, _ = cnn_model.get_feargreed_report(indicator, fig)
|
||||
cnn_view.fear_and_greed_index(indicator=indicator, export="png")
|
||||
plt.close("all")
|
||||
|
||||
# Output data
|
||||
now = datetime.datetime.now()
|
||||
image_path = os.path.join(
|
||||
cfg.GST_PATH,
|
||||
"exports",
|
||||
"economy",
|
||||
f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png",
|
||||
)
|
||||
|
||||
i = 0
|
||||
while not os.path.exists(image_path) and i < 10:
|
||||
now -= datetime.timedelta(seconds=1)
|
||||
image_path = os.path.join(
|
||||
cfg.GST_PATH,
|
||||
"exports",
|
||||
"economy",
|
||||
f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png",
|
||||
)
|
||||
i += 1
|
||||
|
||||
return {
|
||||
"title": "Economy: [CNN] Fear Geed Index",
|
||||
"imagefile": image_path,
|
||||
"description": report,
|
||||
}
|
||||
63
bots/economy/futures.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def futures_command():
|
||||
"""Futures and commodities overview [Wall St. Journal]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-futures")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.top_commodities()
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Price"] = pd.to_numeric(df["Price"].astype(float))
|
||||
df["Chg"] = pd.to_numeric(df["Chg"].astype(float))
|
||||
df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float))
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index(" ", inplace=True)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"Price",
|
||||
"Chg",
|
||||
"%Chg",
|
||||
]
|
||||
]
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-futures.png", fig)
|
||||
return {"title": "Economy: [WSJ] Futures/Commodities", "imagefile": imagefile}
|
||||
69
bots/economy/glbonds.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def glbonds_command():
|
||||
"""Global bonds overview [Wall St. Journal]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-glbonds")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.global_bonds()
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float))
|
||||
df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float))
|
||||
df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float))
|
||||
|
||||
formats = {
|
||||
"Rate (%)": "{:.2f}%",
|
||||
"Yld (%)": "{:.2f}%",
|
||||
"Yld Chg (%)": "{:.2f}%",
|
||||
}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
df = df.fillna("")
|
||||
df = df.replace(("Government Bond", "10 Year"), ("Gov .Bond", "10yr"), regex=True)
|
||||
df.set_index(" ", inplace=True)
|
||||
df = df.set_axis(
|
||||
[
|
||||
"Rate",
|
||||
"Yld",
|
||||
"Yld Chg",
|
||||
],
|
||||
axis="columns",
|
||||
)
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-glbonds.png", fig)
|
||||
|
||||
return {"title": "Economy: [WSJ] Global Bonds", "imagefile": imagefile}
|
||||
70
bots/economy/grains.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def grains_command():
|
||||
"""Displays grains futures data [Finviz]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-grains")
|
||||
|
||||
# Retrieve data
|
||||
d_futures = finviz_model.get_futures()
|
||||
df = pd.DataFrame(d_futures["Grains"])
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
formats = {"last": "${:.2f}", "prevClose": "${:.2f}"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.fillna("")
|
||||
df = df.set_index("label")
|
||||
df = df.sort_values(by="ticker", ascending=False)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"prevClose",
|
||||
"last",
|
||||
"change",
|
||||
]
|
||||
]
|
||||
df.index.names = [""]
|
||||
df = df.rename(
|
||||
columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"}
|
||||
)
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[6, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-grains.png", fig)
|
||||
|
||||
return {
|
||||
"title": "Economy: [Finviz] Grains Futures",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
67
bots/economy/indices.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def indices_command():
|
||||
"""US indices overview [Wall St. Journal]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-indices")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.us_indices()
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Price"] = pd.to_numeric(df["Price"].astype(float))
|
||||
df["Chg"] = pd.to_numeric(df["Chg"].astype(float))
|
||||
df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float))
|
||||
|
||||
formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"}
|
||||
for col, value in formats.items():
|
||||
# pylint: disable=W0640
|
||||
df[col] = df[col].map(lambda x: value.format(x))
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"Price",
|
||||
"Chg",
|
||||
"%Chg",
|
||||
]
|
||||
]
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index(" ", inplace=True)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align=["left", "center"],
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-indices.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] US Indices",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
69
bots/economy/meats.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def meats_command():
|
||||
"""Displays meats futures data [Finviz]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-meats")
|
||||
|
||||
# Retrieve data
|
||||
d_futures = finviz_model.get_futures()
|
||||
df = pd.DataFrame(d_futures["Meats"])
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df = df.fillna("")
|
||||
df = df.set_index("label")
|
||||
df = df.sort_values(by="ticker", ascending=False)
|
||||
|
||||
formats = {"last": "${:.2f}", "prevClose": "${:.2f}"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"prevClose",
|
||||
"last",
|
||||
"change",
|
||||
]
|
||||
]
|
||||
df.index.names = [""]
|
||||
df = df.rename(
|
||||
columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"}
|
||||
)
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[6, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-meats.png", fig)
|
||||
return {
|
||||
"title": "Economy: [Finviz] Meats Futures",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
69
bots/economy/metals.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def metals_command():
|
||||
"""Displays metals futures data [Finviz]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-metals")
|
||||
|
||||
# Retrieve data
|
||||
d_futures = finviz_model.get_futures()
|
||||
df = pd.DataFrame(d_futures["Metals"])
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
formats = {"last": "${:.2f}", "prevClose": "${:.2f}"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df)
|
||||
|
||||
df = df.sort_values(by="ticker", ascending=False)
|
||||
df = df.fillna("")
|
||||
df.set_index("label", inplace=True)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"prevClose",
|
||||
"last",
|
||||
"change",
|
||||
]
|
||||
]
|
||||
|
||||
df.index.names = [""]
|
||||
df = df.rename(
|
||||
columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"}
|
||||
)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[6, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-metals.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] Metals Futures",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
63
bots/economy/overview.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def overview_command():
|
||||
"""Market data overview [Wall St. Journal]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-overview")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.market_overview()
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Price"] = pd.to_numeric(df["Price"].astype(float))
|
||||
df["Chg"] = pd.to_numeric(df["Chg"].astype(float))
|
||||
df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float))
|
||||
|
||||
formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"}
|
||||
for col, v in formats.items():
|
||||
df[col] = df[col].map(lambda x: v.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df[
|
||||
[
|
||||
"Price",
|
||||
"Chg",
|
||||
"%Chg",
|
||||
]
|
||||
]
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index(" ", inplace=True)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-indices.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] Market Overview",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
120
bots/economy/performance.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import df2img
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def performance_command(economy_group="sector"):
|
||||
"""Performance of sectors, industry, country [Finviz]"""
|
||||
|
||||
d_economy_group = {
|
||||
"sector": "Sector",
|
||||
"industry": "Industry",
|
||||
"basic_materials": "Industry (Basic Materials)",
|
||||
"communication_services": "Industry (Communication Services)",
|
||||
"consumer_cyclical": "Industry (Consumer Cyclical)",
|
||||
"consumer_defensive": "Industry (Consumer Defensive)",
|
||||
"energy": "Industry (Energy)",
|
||||
"financial": "Industry (Financial)",
|
||||
"healthcare": "Industry (Healthcare)",
|
||||
"industrials": "Industry (Industrials)",
|
||||
"real_estate": "Industry (Real Estate)",
|
||||
"technology": "Industry (Technology)",
|
||||
"utilities": "Industry (Utilities)",
|
||||
"country": "Country (U.S. listed stocks only)",
|
||||
"capitalization": "Capitalization",
|
||||
}
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-performance %s", economy_group)
|
||||
|
||||
# Select default group
|
||||
if not economy_group:
|
||||
if cfg.DEBUG:
|
||||
logger.debug("Use default economy_group: 'sector'")
|
||||
economy_group = "sector"
|
||||
|
||||
# Check for argument
|
||||
possible_groups = list(d_economy_group.keys())
|
||||
|
||||
if economy_group not in possible_groups:
|
||||
possible_group_list = ", ".join(possible_groups)
|
||||
raise Exception(f"Select a valid group from {possible_group_list}") # nosec
|
||||
|
||||
group = d_economy_group[economy_group]
|
||||
|
||||
# Retrieve data
|
||||
df_group = finviz_model.get_valuation_performance_data(group, "performance")
|
||||
|
||||
# Check for argument
|
||||
if df_group.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
# Output data
|
||||
df = pd.DataFrame(df_group)
|
||||
df = df.replace(np.nan, 0)
|
||||
|
||||
df["Volume"] = df["Volume"] / 1_000_000
|
||||
df["Avg Volume"] = df["Avg Volume"] / 1_000_000
|
||||
|
||||
formats = {
|
||||
"Perf Month": "{:.2f}",
|
||||
"Perf Quart": "{:.2f}",
|
||||
"Perf Half": "{:.2f}",
|
||||
"Perf Year": "{:.2f}",
|
||||
"Perf YTD": "{:.2f}",
|
||||
"Avg Volume": "{:.0f}M",
|
||||
"Change": "{:.2f}",
|
||||
"Volume": "{:.0f}M",
|
||||
}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.set_axis(
|
||||
[
|
||||
"Name",
|
||||
"Week",
|
||||
"Month",
|
||||
"3Month",
|
||||
"6Month",
|
||||
"1Year",
|
||||
"YTD",
|
||||
"Recom",
|
||||
"Avg Vol.",
|
||||
"RelVolume",
|
||||
"Change",
|
||||
"Volume",
|
||||
],
|
||||
axis="columns",
|
||||
)
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index("Name", inplace=True)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(1500, (40 + (50 * dindex))),
|
||||
col_width=[10, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3.5],
|
||||
tbl_cells=dict(
|
||||
align=["left", "center"],
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-performance.png", fig)
|
||||
return {
|
||||
"title": f"Economy: [WSJ] Performance {group}",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
71
bots/economy/softs.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def softs_command():
|
||||
"""Displays softs futures data [Finviz]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-softs")
|
||||
|
||||
# Retrieve data
|
||||
d_futures = finviz_model.get_futures()
|
||||
df = pd.DataFrame(d_futures["Softs"])
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
formats = {"last": "${:.2f}", "prevClose": "${:.2f}"}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df)
|
||||
|
||||
df = df.sort_values(by="ticker", ascending=False)
|
||||
df = df.fillna("")
|
||||
df.set_index("label", inplace=True)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"prevClose",
|
||||
"last",
|
||||
"change",
|
||||
]
|
||||
]
|
||||
|
||||
df.index.names = [""]
|
||||
df = df.rename(
|
||||
columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"}
|
||||
)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[6, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-currencies.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] Currencies",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
69
bots/economy/usbonds.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import df2img
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import wsj_model
|
||||
|
||||
|
||||
def usbonds_command():
|
||||
"""US bonds overview [Wall St. Journal]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-usbonds")
|
||||
|
||||
# Retrieve data
|
||||
df = wsj_model.us_bonds()
|
||||
|
||||
# Check for argument
|
||||
if df.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float))
|
||||
df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float))
|
||||
df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float))
|
||||
|
||||
formats = {
|
||||
"Rate (%)": "{:.2f}%",
|
||||
"Yld (%)": "{:.2f}%",
|
||||
"Yld Chg (%)": "{:.2f}%",
|
||||
}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index(" ", inplace=True)
|
||||
|
||||
df = df.set_axis(
|
||||
[
|
||||
"Rate",
|
||||
"Yld",
|
||||
"Yld Chg",
|
||||
],
|
||||
axis="columns",
|
||||
)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[8, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
|
||||
imagefile = save_image("econ-usbonds.png", fig)
|
||||
return {
|
||||
"title": "Economy: [WSJ] US Bonds",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
126
bots/economy/valuation.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import df2img
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.economy import finviz_model
|
||||
|
||||
|
||||
def valuation_command(economy_group="sector"):
|
||||
"""Valuation of sectors, industry, country [Finviz]"""
|
||||
|
||||
d_economy_group = {
|
||||
"sector": "Sector",
|
||||
"industry": "Industry",
|
||||
"basic_materials": "Industry (Basic Materials)",
|
||||
"communication_services": "Industry (Communication Services)",
|
||||
"consumer_cyclical": "Industry (Consumer Cyclical)",
|
||||
"consumer_defensive": "Industry (Consumer Defensive)",
|
||||
"energy": "Industry (Energy)",
|
||||
"financial": "Industry (Financial)",
|
||||
"healthcare": "Industry (Healthcare)",
|
||||
"industrials": "Industry (Industrials)",
|
||||
"real_estate": "Industry (Real Estate)",
|
||||
"technology": "Industry (Technology)",
|
||||
"utilities": "Industry (Utilities)",
|
||||
"country": "Country (U.S. listed stocks only)",
|
||||
"capitalization": "Capitalization",
|
||||
}
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("econ-valuation %s", economy_group)
|
||||
|
||||
# Select default group
|
||||
if economy_group == "":
|
||||
if cfg.DEBUG:
|
||||
logger.debug("Use default economy_group: 'sector'")
|
||||
economy_group = "sector"
|
||||
|
||||
# Check for argument
|
||||
possible_groups = list(d_economy_group.keys())
|
||||
|
||||
if economy_group not in possible_groups:
|
||||
raise Exception(
|
||||
f"Select a valid group from {', '.join(possible_groups)}" # nosec
|
||||
)
|
||||
|
||||
group = d_economy_group[economy_group]
|
||||
|
||||
# Retrieve data
|
||||
df_group = finviz_model.get_valuation_performance_data(group, "valuation")
|
||||
|
||||
# Check for argument
|
||||
if df_group.empty:
|
||||
raise Exception("No available data found")
|
||||
|
||||
# Output data
|
||||
df = pd.DataFrame(df_group)
|
||||
df = df.replace(np.nan, 0)
|
||||
|
||||
df = df.set_axis(
|
||||
[
|
||||
"Name",
|
||||
"MarketCap",
|
||||
"P/E",
|
||||
"FwdP/E",
|
||||
"PEG",
|
||||
"P/S",
|
||||
"P/B",
|
||||
"P/C",
|
||||
"P/FCF",
|
||||
"EPSpast5Y",
|
||||
"EPSnext5Y",
|
||||
"Salespast5Y",
|
||||
"Change",
|
||||
"Volume",
|
||||
],
|
||||
axis="columns",
|
||||
)
|
||||
|
||||
df["P/E"] = pd.to_numeric(df["P/E"].astype(float))
|
||||
df["FwdP/E"] = pd.to_numeric(df["FwdP/E"].astype(float))
|
||||
df["EPSpast5Y"] = pd.to_numeric(df["EPSpast5Y"].astype(float))
|
||||
df["EPSnext5Y"] = pd.to_numeric(df["EPSnext5Y"].astype(float))
|
||||
df["Salespast5Y"] = pd.to_numeric(df["Salespast5Y"].astype(float))
|
||||
df["Volume"] = pd.to_numeric(df["Volume"].astype(float))
|
||||
df["Volume"] = df["Volume"] / 1_000_000
|
||||
|
||||
formats = {
|
||||
"P/E": "{:.2f}",
|
||||
"FwdP/E": "{:.2f}",
|
||||
"EPSpast5Y": "{:.2f}",
|
||||
"EPSnext5Y": "{:.2f}",
|
||||
"Salespast5Y": "{:.2f}",
|
||||
"Change": "{:.2f}",
|
||||
"Volume": "{:.0f}M",
|
||||
}
|
||||
for col, value in formats.items():
|
||||
df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640
|
||||
|
||||
df = df.fillna("")
|
||||
df.set_index("Name", inplace=True)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(1600, (40 + (50 * dindex))),
|
||||
col_width=[12, 5, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4],
|
||||
tbl_cells=dict(
|
||||
align=["left", "center"],
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("econ-valuation.png", fig)
|
||||
return {
|
||||
"title": "Economy: [Finviz] Valuation",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
0
discordbot/files/bg.png → bots/files/bg.png
vendored
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
@@ -1,5 +1,5 @@
|
||||
import discord
|
||||
import discordbot.config_discordbot as cfg
|
||||
import bots.config_discordbot as cfg
|
||||
|
||||
|
||||
class GenericCommands(discord.ext.commands.Cog):
|
||||
271
bots/helpers.py
Normal file
@@ -0,0 +1,271 @@
|
||||
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)
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
@@ -1,11 +1,11 @@
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import config_discordbot as cfg
|
||||
|
||||
import disnake
|
||||
from disnake.ext import commands
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
|
||||
bot = commands.Bot()
|
||||
startTime = time.time()
|
||||
|
||||
@@ -40,7 +40,7 @@ class Menu(disnake.ui.View):
|
||||
|
||||
def set_link_button(self) -> None:
|
||||
if not hasattr(self, "link_button"):
|
||||
self.link_button = disnake.ui.Button(
|
||||
self.link_button: disnake.ui.Button = disnake.ui.Button(
|
||||
style=disnake.ButtonStyle.url,
|
||||
url="https://github.com/GamestonkTerminal/GamestonkTerminal",
|
||||
label="Site",
|
||||
209
bots/stocks/candle.py
Normal file
@@ -0,0 +1,209 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import overlap_model
|
||||
|
||||
|
||||
def candle_command(
|
||||
ticker: str = "",
|
||||
interval: int = 15,
|
||||
past_days: int = 1,
|
||||
start="",
|
||||
end="",
|
||||
):
|
||||
"""Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: str
|
||||
Ticker name
|
||||
interval: int
|
||||
chart mins or daily
|
||||
past_days: int
|
||||
Display the past * days. Default: 1(Not for Daily)
|
||||
start: str
|
||||
start date format YYYY-MM-DD
|
||||
end: str
|
||||
end date format YYYY-MM-DD
|
||||
"""
|
||||
|
||||
logger.info("candle %s %s %s %s", ticker, interval, start, end)
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
futures = "=F" or "^"
|
||||
crypto = "-USD"
|
||||
if interval == 1440:
|
||||
df_stock_candidate = yf.download(
|
||||
ticker,
|
||||
start=start,
|
||||
end=end,
|
||||
progress=False,
|
||||
)
|
||||
|
||||
# Check that loading a stock was not successful
|
||||
if df_stock_candidate.empty:
|
||||
raise Exception(f"No data found for {ticker.upper()}")
|
||||
|
||||
df_stock_candidate.index.name = "date"
|
||||
else:
|
||||
s_int = str(interval) + "m"
|
||||
d_granularity = {
|
||||
"1m": past_days,
|
||||
"5m": past_days,
|
||||
"15m": past_days,
|
||||
"30m": past_days,
|
||||
"60m": past_days,
|
||||
}
|
||||
s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int])
|
||||
s_date_start = s_start_dt.strftime("%Y-%m-%d")
|
||||
|
||||
df_stock_candidate = yf.download(
|
||||
ticker,
|
||||
start=s_date_start if s_start_dt > start else start.strftime("%Y-%m-%d"),
|
||||
progress=False,
|
||||
interval=s_int,
|
||||
prepost=True,
|
||||
)
|
||||
|
||||
# Check that loading a stock was not successful
|
||||
if df_stock_candidate.empty:
|
||||
raise Exception(f"No data found for {ticker.upper()}.")
|
||||
|
||||
df_stock_candidate.index = df_stock_candidate.index.tz_localize(None)
|
||||
df_stock_candidate.index.name = "date"
|
||||
df_stock = df_stock_candidate
|
||||
price_df = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
df_vwap = overlap_model.vwap(price_df, 0)
|
||||
|
||||
plt_title = [f"{ticker.upper()} Intraday {interval}min", "Volume"]
|
||||
title = f"Intraday {interval}min Chart for {ticker.upper()}"
|
||||
if interval == 1440:
|
||||
plt_title = [f"{ticker.upper()} Daily", "Volume"]
|
||||
title = f"Daily Chart for {ticker.upper()}"
|
||||
|
||||
fig = make_subplots(
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.09,
|
||||
subplot_titles=plt_title,
|
||||
specs=[[{"secondary_y": True}]],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Candlestick(
|
||||
x=df_stock.index,
|
||||
open=df_stock.Open,
|
||||
high=df_stock.High,
|
||||
low=df_stock.Low,
|
||||
close=df_stock.Close,
|
||||
name="OHLC",
|
||||
showlegend=False,
|
||||
),
|
||||
secondary_y=True,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df_stock.index,
|
||||
y=df_stock.Volume,
|
||||
name="Volume",
|
||||
yaxis="y2",
|
||||
marker_color="#d81aea",
|
||||
opacity=0.4,
|
||||
showlegend=False,
|
||||
),
|
||||
secondary_y=False,
|
||||
)
|
||||
if interval != 1440:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="VWAP",
|
||||
x=df_stock.index,
|
||||
y=df_vwap["VWAP_D"],
|
||||
opacity=0.65,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
showlegend=True,
|
||||
),
|
||||
secondary_y=True,
|
||||
)
|
||||
fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across")
|
||||
fig.update_yaxes(showspikes=True, spikethickness=2)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=40, b=20),
|
||||
template=cfg.PLT_CANDLE_STYLE_TEMPLATE,
|
||||
yaxis2_title="Price ($)",
|
||||
yaxis_title="Volume",
|
||||
yaxis=dict(
|
||||
showgrid=False,
|
||||
fixedrange=False,
|
||||
side="left",
|
||||
titlefont=dict(color="#d81aea"),
|
||||
tickfont=dict(color="#d81aea"),
|
||||
nticks=20,
|
||||
),
|
||||
yaxis2=dict(
|
||||
side="right",
|
||||
fixedrange=False,
|
||||
anchor="x",
|
||||
layer="above traces",
|
||||
overlaying="y",
|
||||
nticks=20,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
showspikes=True,
|
||||
),
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
dragmode="pan",
|
||||
hovermode="x unified",
|
||||
spikedistance=1000,
|
||||
hoverdistance=100,
|
||||
)
|
||||
if futures in ticker.upper():
|
||||
fig.update_xaxes(
|
||||
rangebreaks=[
|
||||
dict(bounds=["sat", "sun"]),
|
||||
dict(bounds=[17, 17.50], pattern="hour"),
|
||||
],
|
||||
)
|
||||
elif crypto not in ticker.upper() and interval != 1440:
|
||||
fig.update_xaxes(
|
||||
rangebreaks=[
|
||||
dict(bounds=["sat", "mon"]),
|
||||
dict(bounds=[20, 9.50], pattern="hour"),
|
||||
],
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "candle.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/candle_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/candle_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
208
bots/stocks/dark_pool_shorts/dpotc.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import finra_model
|
||||
|
||||
|
||||
def dpotc_command(ticker: str = ""):
|
||||
"""Dark pools (ATS) vs OTC data [FINRA]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-dpotc %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
ticker = ticker.upper()
|
||||
|
||||
stock = yf.download(ticker, progress=False)
|
||||
if stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve data
|
||||
ats, otc = finra_model.getTickerFINRAdata(ticker)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(ats.to_string())
|
||||
logger.debug(otc.to_string())
|
||||
|
||||
# Output data
|
||||
title = f"Stocks: [FINRA] Dark Pools (ATS) vs OTC {ticker}"
|
||||
|
||||
if ats.empty and otc.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.07,
|
||||
row_width=[0.4, 0.6],
|
||||
specs=[[{"secondary_y": True}], [{"secondary_y": False}]],
|
||||
)
|
||||
|
||||
if not ats.empty and not otc.empty:
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=ats.index,
|
||||
y=(ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"]),
|
||||
name="ATS",
|
||||
opacity=0.8,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
secondary_y=False,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=otc.index,
|
||||
y=otc["totalWeeklyShareQuantity"],
|
||||
name="OTC",
|
||||
opacity=0.8,
|
||||
yaxis="y2",
|
||||
offset=0.0001,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
|
||||
elif not ats.empty:
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=ats.index,
|
||||
y=(ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"]),
|
||||
name="ATS",
|
||||
opacity=0.8,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
secondary_y=False,
|
||||
)
|
||||
|
||||
elif not otc.empty:
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=otc.index,
|
||||
y=otc["totalWeeklyShareQuantity"],
|
||||
name="OTC",
|
||||
opacity=0.8,
|
||||
yaxis="y2",
|
||||
secondary_y=False,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
|
||||
if not ats.empty:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="ATS",
|
||||
x=ats.index,
|
||||
y=ats["totalWeeklyShareQuantity"] / ats["totalWeeklyTradeCount"],
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
yaxis="y2",
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
|
||||
if not otc.empty:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="OTC",
|
||||
x=otc.index,
|
||||
y=otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"],
|
||||
line=dict(color="#d81aea", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
else:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="OTC",
|
||||
x=otc.index,
|
||||
y=otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"],
|
||||
line=dict(color="#d81aea", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
|
||||
fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across")
|
||||
fig.update_yaxes(showspikes=True, spikethickness=2)
|
||||
fig.update_layout(
|
||||
margin=dict(l=20, r=0, t=10, b=20),
|
||||
template=cfg.PLT_CANDLE_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"Dark Pools (ATS) vs OTC (Non-ATS) Data for {ticker}",
|
||||
title_x=0.5,
|
||||
yaxis3_title="Shares per Trade",
|
||||
yaxis_title="Total Weekly Shares",
|
||||
xaxis2_title="Weeks",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
side="left",
|
||||
nticks=20,
|
||||
),
|
||||
yaxis2=dict(
|
||||
fixedrange=False,
|
||||
showgrid=False,
|
||||
overlaying="y",
|
||||
anchor="x",
|
||||
layer="above traces",
|
||||
),
|
||||
yaxis3=dict(
|
||||
fixedrange=False,
|
||||
nticks=10,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
showspikes=True,
|
||||
nticks=20,
|
||||
),
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
barmode="group",
|
||||
bargap=0.5,
|
||||
bargroupgap=0,
|
||||
dragmode="pan",
|
||||
hovermode="x unified",
|
||||
spikedistance=1000,
|
||||
hoverdistance=100,
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "dps_dpotc.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/dpotc_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/dpotc_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
122
bots/stocks/dark_pool_shorts/ftd.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import sec_model
|
||||
|
||||
|
||||
def ftd_command(ticker: str = "", start="", end=""):
|
||||
"""Fails-to-deliver data [SEC]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-ftd %s %s %s", ticker, start, end)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
ticker = ticker.upper()
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
# Retrieve data
|
||||
ftds_data = sec_model.get_fails_to_deliver(ticker, start, end, 0)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(ftds_data.to_string())
|
||||
|
||||
stock = helpers.load(ticker, start)
|
||||
stock_ftd = stock[stock.index > start]
|
||||
stock_ftd = stock_ftd[stock_ftd.index < end]
|
||||
|
||||
# Output data
|
||||
fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]])
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=stock_ftd.index,
|
||||
y=stock_ftd["Adj Close"],
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
secondary_y=False,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
name="FTDs",
|
||||
x=ftds_data["SETTLEMENT DATE"],
|
||||
y=ftds_data["QUANTITY (FAILS)"] / 1000,
|
||||
opacity=1,
|
||||
),
|
||||
secondary_y=True,
|
||||
)
|
||||
# Set y-axes titles
|
||||
fig.update_yaxes(title_text="<b>Shares</b> [K]", secondary_y=True)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=20, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"{ticker}",
|
||||
title_x=0.5,
|
||||
yaxis_title="<b>Stock Price</b> ($)",
|
||||
yaxis=dict(
|
||||
side="right",
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis2=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
yaxis2=dict(
|
||||
side="left",
|
||||
position=0.15,
|
||||
fixedrange=False,
|
||||
),
|
||||
hovermode="x unified",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "dps_ftd.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/ftds_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ftds_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: [SEC] Failure-to-deliver {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
80
bots/stocks/dark_pool_shorts/hsi.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import shortinterest_model
|
||||
|
||||
|
||||
def hsi_command(num: int = 10):
|
||||
"""Show top high short interest stocks of over 20% ratio [shortinterest.com]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-hsi %s", num)
|
||||
|
||||
# Check for argument
|
||||
if num < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Retrieve data
|
||||
df = shortinterest_model.get_high_short_interest()
|
||||
df = df.iloc[1:].head(n=num)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
title = "Stocks: [highshortinterest.com] Top High Short Interest"
|
||||
|
||||
future_column_name = df["Ticker"]
|
||||
df = df.transpose()
|
||||
df.columns = future_column_name
|
||||
df.drop("Ticker")
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df.columns.values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
for column in df.columns.values:
|
||||
description = "```" + df[column].fillna("").to_string() + "```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
113
bots/stocks/dark_pool_shorts/pos.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model
|
||||
|
||||
|
||||
def pos_command(sort="dpp_dollar", num: int = 10):
|
||||
"""Dark pool short position [Stockgrid]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-pos %s %s", sort, num)
|
||||
|
||||
# Check for argument
|
||||
possible_sorts = ("sv", "sv_pct", "nsv", "nsv_dollar", "dpp", "dpp_dollar")
|
||||
|
||||
if sort not in possible_sorts:
|
||||
raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}")
|
||||
|
||||
if num < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Retrieve data
|
||||
df = stockgrid_model.get_dark_pool_short_positions(sort, False)
|
||||
df = df.iloc[:num]
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
title = "Stocks: [Stockgrid] Dark Pool Short Position"
|
||||
|
||||
dp_date = df["Date"].values[0]
|
||||
df = df.drop(columns=["Date"])
|
||||
df["Net Short Volume $"] = df["Net Short Volume $"] / 100_000_000
|
||||
df["Short Volume"] = df["Short Volume"] / 1_000_000
|
||||
df["Net Short Volume"] = df["Net Short Volume"] / 1_000_000
|
||||
df["Short Volume %"] = df["Short Volume %"] * 100
|
||||
df["Dark Pools Position $"] = df["Dark Pools Position $"] / (1_000_000_000)
|
||||
df["Dark Pools Position"] = df["Dark Pools Position"] / 1_000_000
|
||||
df.columns = [
|
||||
"Ticker",
|
||||
"Short Vol. (1M)",
|
||||
"Short Vol. %",
|
||||
"Net Short Vol. (1M)",
|
||||
"Net Short Vol. ($100M)",
|
||||
"DP Position (1M)",
|
||||
"DP Position ($1B)",
|
||||
]
|
||||
future_column_name = df["Ticker"]
|
||||
df = df.transpose()
|
||||
df.columns = future_column_name
|
||||
df.drop("Ticker")
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df.columns.values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
for column in df.columns.values:
|
||||
description = (
|
||||
"```The following data corresponds to the date: "
|
||||
+ dp_date
|
||||
+ "\n\n"
|
||||
+ df[column].fillna("").to_string()
|
||||
+ "```",
|
||||
)
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
157
bots/stocks/dark_pool_shorts/psi.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model
|
||||
|
||||
|
||||
def psi_command(ticker: str = ""):
|
||||
"""Price vs short interest volume [Stockgrid]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-psi %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
ticker = ticker.upper()
|
||||
|
||||
stock = yf.download(ticker, progress=False)
|
||||
if stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve data
|
||||
df, prices = stockgrid_model.get_short_interest_volume(ticker)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.06,
|
||||
row_width=[0.3, 0.6],
|
||||
specs=[[{"secondary_y": True}], [{"secondary_y": False}]],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df["date"].values,
|
||||
y=prices[len(prices) - len(df) :],
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
secondary_y=False,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df["date"],
|
||||
y=df["total_volume"] / 1_000_000,
|
||||
name="Total Volume",
|
||||
yaxis="y2",
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
secondary_y=True,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df["date"],
|
||||
y=df["short_volume"] / 1_000_000,
|
||||
name="Short Volume",
|
||||
yaxis="y2",
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
secondary_y=True,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="Short Vol. %",
|
||||
x=df["date"].values,
|
||||
y=100 * df["short_volume%"],
|
||||
line=dict(width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
secondary_y=False,
|
||||
)
|
||||
fig.update_traces(hovertemplate="%{y:.2f}")
|
||||
fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across")
|
||||
fig.update_yaxes(showspikes=True, spikethickness=2)
|
||||
fig.update_layout(
|
||||
margin=dict(l=10, r=0, t=40, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"Price vs Short Volume Interest for {ticker}",
|
||||
title_x=0.45,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis2_title="FINRA Volume [M]",
|
||||
yaxis3_title="Short Vol. %",
|
||||
yaxis=dict(
|
||||
side="right",
|
||||
fixedrange=False,
|
||||
titlefont=dict(color="#fdc708"),
|
||||
tickfont=dict(color="#fdc708"),
|
||||
nticks=20,
|
||||
),
|
||||
yaxis2=dict(
|
||||
side="left",
|
||||
fixedrange=False,
|
||||
anchor="x",
|
||||
overlaying="y",
|
||||
titlefont=dict(color="#d81aea"),
|
||||
tickfont=dict(color="#d81aea"),
|
||||
nticks=20,
|
||||
),
|
||||
yaxis3=dict(
|
||||
fixedrange=False,
|
||||
titlefont=dict(color="#9467bd"),
|
||||
tickfont=dict(color="#9467bd"),
|
||||
nticks=20,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
hovermode="x unified",
|
||||
spikedistance=1000,
|
||||
hoverdistance=100,
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "dps_psi.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/psi_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/psi_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: [Stockgrid] Price vs Short Interest Volume {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
81
bots/stocks/dark_pool_shorts/shorted.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import yahoofinance_model
|
||||
|
||||
|
||||
def shorted_command(num: int = 10):
|
||||
"""Show most shorted stocks [Yahoo Finance]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-shorted %s", num)
|
||||
|
||||
# Check for argument
|
||||
if num < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Retrieve data
|
||||
df = yahoofinance_model.get_most_shorted().head(num)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
title = "Stocks: [Yahoo Finance] Most Shorted"
|
||||
df.dropna(how="all", axis=1, inplace=True)
|
||||
df = df.replace(float("NaN"), "")
|
||||
future_column_name = df["Symbol"]
|
||||
df = df.transpose()
|
||||
df.columns = future_column_name
|
||||
df.drop("Symbol")
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df.columns.values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
for column in df.columns.values:
|
||||
description = "```" + df[column].fillna("").to_string() + "```"
|
||||
embeds.append(
|
||||
disnake.Embed(description=description, colour=cfg.COLOR,).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
104
bots/stocks/dark_pool_shorts/sidtc.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model
|
||||
|
||||
|
||||
def sidtc_command(sort="float", num: int = 10):
|
||||
"""Short interest and days to cover [Stockgrid]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-sidtc %s %s", sort, num)
|
||||
|
||||
# Check for argument
|
||||
possible_sorts = ("float", "dtc", "si")
|
||||
|
||||
if sort not in possible_sorts:
|
||||
raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}")
|
||||
|
||||
if num < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Retrieve data
|
||||
df = stockgrid_model.get_short_interest_days_to_cover(sort)
|
||||
df = df.iloc[:num]
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
title = "Stocks: [Stockgrid] Short Interest and Days to Cover"
|
||||
dp_date = df["Date"].values[0]
|
||||
df = df.drop(columns=["Date"])
|
||||
df["Short Interest"] = df["Short Interest"] / 1_000_000
|
||||
df.head()
|
||||
df.columns = [
|
||||
"Ticker",
|
||||
"Float Short %",
|
||||
"Days to Cover",
|
||||
"Short Interest (1M)",
|
||||
]
|
||||
future_column_name = df["Ticker"]
|
||||
df = df.transpose()
|
||||
df.columns = future_column_name
|
||||
df.drop("Ticker")
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df.columns.values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title="Dark Pool Shorts", description=initial_str, colour=cfg.COLOR
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
|
||||
for column in df.columns.values:
|
||||
description = (
|
||||
"```The following data corresponds to the date: "
|
||||
+ dp_date
|
||||
+ "\n\n"
|
||||
+ df[column].fillna("").to_string()
|
||||
+ "```",
|
||||
)
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
124
bots/stocks/dark_pool_shorts/spos.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model
|
||||
|
||||
|
||||
def spos_command(ticker: str = ""):
|
||||
"""Net short vs position [Stockgrid]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dps-spos %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
ticker = ticker.upper()
|
||||
|
||||
stock = yf.download(ticker, progress=False)
|
||||
if stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve data
|
||||
df = stockgrid_model.get_net_short_position(ticker)
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df.to_string())
|
||||
|
||||
# Output data
|
||||
title = f"Stocks: [Stockgrid] Net Short vs Position {ticker}"
|
||||
|
||||
fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]])
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="Position ($)",
|
||||
x=df["dates"].values,
|
||||
y=df["dollar_dp_position"] * 1_000,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
secondary_y=True,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
name="Net Short Vol. ($)",
|
||||
x=df["dates"],
|
||||
y=df["dollar_net_volume"],
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
secondary_y=False,
|
||||
)
|
||||
# Set y-axes titles
|
||||
fig.update_xaxes(dtick="M1", tickformat="%b %d\n%Y")
|
||||
fig.update_yaxes(title_text="<b>Position</b> ($)", secondary_y=True)
|
||||
fig.update_traces(hovertemplate="%{y:.2s}")
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=10, t=40, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"Net Short Vol. vs Position for {ticker}",
|
||||
title_x=0.5,
|
||||
yaxis_title="<b>Net Short Vol.</b> ($)",
|
||||
yaxis=dict(
|
||||
side="left",
|
||||
showgrid=False,
|
||||
fixedrange=False,
|
||||
layer="above traces",
|
||||
titlefont=dict(color="#d81aea"),
|
||||
tickfont=dict(color="#d81aea"),
|
||||
nticks=10,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis2=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
yaxis2=dict(
|
||||
side="right",
|
||||
position=0.15,
|
||||
fixedrange=False,
|
||||
titlefont=dict(color="#fdc708"),
|
||||
tickfont=dict(color="#fdc708"),
|
||||
nticks=10,
|
||||
),
|
||||
hovermode="x unified",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "dps_spos.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/spos_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/spos_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
56
bots/stocks/disc/ford.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import df2img
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.stocks.discovery import fidelity_model
|
||||
|
||||
|
||||
def ford_command():
|
||||
"""Display Orders by Fidelity Customers. [Source: Fidelity]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("disc-ford")
|
||||
order_header, df_orders = fidelity_model.get_orders()
|
||||
|
||||
df_orders = df_orders.head(n=30).iloc[:, :-1]
|
||||
df_orders = df_orders.applymap(str)
|
||||
|
||||
font_color = (
|
||||
["white"] * 2
|
||||
+ [
|
||||
[
|
||||
"#ad0000" if boolv else "#0d5700"
|
||||
for boolv in df_orders["Price Change"].str.contains("-")
|
||||
]
|
||||
]
|
||||
+ [["white"] * 3]
|
||||
)
|
||||
|
||||
df_orders.set_index("Symbol", inplace=True)
|
||||
df_orders = df_orders.apply(lambda x: x.str.slice(0, 20))
|
||||
|
||||
dindex = len(df_orders.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df_orders,
|
||||
fig_size=(1500, (40 + (40 * dindex))),
|
||||
col_width=[1, 3, 2.2, 3, 2, 2],
|
||||
tbl_cells=dict(
|
||||
align=["left", "center", "left", "left", "center"],
|
||||
font=dict(color=font_color),
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("disc-ford.png", fig)
|
||||
|
||||
return {
|
||||
"title": "Fidelity Customer Orders",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
55
bots/stocks/due_diligence/analyst.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import df2img
|
||||
import numpy as np
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.stocks.due_diligence import finviz_model
|
||||
|
||||
|
||||
def analyst_command(ticker=""):
|
||||
"""Displays analyst recommendations [Finviz]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-analyst %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if not ticker:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
df = finviz_model.get_analyst_data(ticker)
|
||||
df = df.replace(np.nan, 0)
|
||||
df.index.names = ["Date"]
|
||||
df = df.rename(
|
||||
columns={
|
||||
"category": "Category",
|
||||
"analyst": "Analyst",
|
||||
"rating": "Rating",
|
||||
"target": "Target",
|
||||
"target_from": "Target From",
|
||||
"target_to": "Target To",
|
||||
}
|
||||
)
|
||||
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(1500, (40 + (40 * dindex))),
|
||||
col_width=[5, 5, 8, 14, 5, 5, 5],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("dd-analyst.png", fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: [Finviz] Analyst Recommendations {ticker.upper()}",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
117
bots/stocks/due_diligence/arktrades.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import os
|
||||
|
||||
import df2img
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import gst_imgur, logger
|
||||
from bots.helpers import save_image
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.due_diligence import ark_model
|
||||
|
||||
|
||||
def arktrades_command(ticker: str = "", num: int = 10):
|
||||
"""Displays trades made by ark [cathiesark.com]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-arktrades %s", ticker)
|
||||
|
||||
if ticker:
|
||||
ark_holdings = ark_model.get_ark_trades_by_ticker(ticker)
|
||||
|
||||
if ark_holdings.empty:
|
||||
raise Exception(
|
||||
"Issue getting data from cathiesark.com. Likely no trades found.\n"
|
||||
)
|
||||
|
||||
ark_holdings["Total"] = ark_holdings["Total"] / 1_000_000
|
||||
ark_holdings.rename(columns={"direction": "B/S", "weight": "F %"}, inplace=True)
|
||||
ark_holdings = ark_holdings.drop(
|
||||
columns=["ticker", "everything.profile.companyName"]
|
||||
)
|
||||
|
||||
ark_holdings.index = pd.Series(ark_holdings.index).apply(
|
||||
lambda x: x.strftime("%Y-%m-%d")
|
||||
)
|
||||
|
||||
df = ark_holdings.head(num)
|
||||
df = df.fillna(0)
|
||||
dindex = len(df.head(num).index)
|
||||
formats = {"Close": "{:.2f}", "Total": "{:.2f}"}
|
||||
for col, f in formats.items():
|
||||
df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640
|
||||
|
||||
title = f"Stocks: [cathiesark.com] {ticker.upper()} Trades by Ark"
|
||||
|
||||
embeds: list = []
|
||||
|
||||
i, i2, end = 0, 0, 20
|
||||
df_pg = []
|
||||
embeds_img = []
|
||||
dindex = len(df.index)
|
||||
while i < dindex:
|
||||
df_pg = df.iloc[i:end]
|
||||
df_pg.append(df_pg)
|
||||
fig = df2img.plot_dataframe(
|
||||
df_pg,
|
||||
fig_size=(900, (40 + (40 * 20))),
|
||||
col_width=[5, 10, 4, 4, 3, 4, 5],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image(f"dd-arktrades{i}.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link = uploaded_image.link
|
||||
embeds_img.append(
|
||||
f"{image_link}",
|
||||
)
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
)
|
||||
i2 += 1
|
||||
i += 20
|
||||
end += 20
|
||||
os.remove(imagefile)
|
||||
|
||||
# Author/Footer
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
url=cfg.AUTHOR_URL,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
embeds[i].set_footer(
|
||||
text=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
i = 0
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_image(url=embeds_img[i])
|
||||
|
||||
i += 1
|
||||
embeds[0].set_footer(text=f"Page 1 of {len(embeds)}")
|
||||
choices = [
|
||||
disnake.SelectOption(label="Home", value="0", emoji="🟢"),
|
||||
]
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
"embeds_img": embeds_img,
|
||||
}
|
||||
30
bots/stocks/due_diligence/customer.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from gamestonk_terminal.stocks.due_diligence import csimarket_model
|
||||
|
||||
|
||||
def customer_command(ticker: str = ""):
|
||||
"""Displays customers of the company [CSIMarket]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-customer %s", ticker)
|
||||
|
||||
if not ticker:
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
tickers = csimarket_model.get_customers(ticker)
|
||||
|
||||
if not tickers:
|
||||
raise Exception("Enter a valid ticker")
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(tickers)
|
||||
|
||||
# Output data
|
||||
|
||||
return {
|
||||
"title": f"Stocks: [CSIMarket] {ticker} Customers",
|
||||
"description": tickers,
|
||||
}
|
||||
151
bots/stocks/due_diligence/est.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import os
|
||||
|
||||
import df2img
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import gst_imgur, logger
|
||||
from bots.helpers import save_image
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.due_diligence import business_insider_model
|
||||
|
||||
|
||||
def est_command(ticker: str = ""):
|
||||
"""Displays earning estimates [Business Insider]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-est %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
(
|
||||
df_year_estimates,
|
||||
df_quarter_earnings,
|
||||
df_quarter_revenues,
|
||||
) = business_insider_model.get_estimates(ticker)
|
||||
|
||||
if (
|
||||
df_quarter_revenues.empty
|
||||
and df_year_estimates.empty
|
||||
and df_quarter_earnings.empty
|
||||
):
|
||||
raise Exception("Enter a valid ticker")
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df_year_estimates.to_string())
|
||||
logger.debug(df_quarter_earnings.to_string())
|
||||
logger.debug(df_quarter_revenues.to_string())
|
||||
|
||||
dindex = len(df_year_estimates.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df_year_estimates,
|
||||
fig_size=(1200, (40 + (60 * dindex))),
|
||||
col_width=[12, 4, 4, 4, 4],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("estimates.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
link_estimates = uploaded_image.link
|
||||
|
||||
os.remove(imagefile)
|
||||
|
||||
fig = df2img.plot_dataframe(
|
||||
df_quarter_earnings,
|
||||
fig_size=(1200, (40 + (40 * 20))),
|
||||
col_width=[5, 5, 4, 4, 5, 4],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("earnings.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
link_earnings = uploaded_image.link
|
||||
os.remove(imagefile)
|
||||
|
||||
fig = df2img.plot_dataframe(
|
||||
df_quarter_revenues,
|
||||
fig_size=(1200, (40 + (40 * 20))),
|
||||
col_width=[5, 5, 4, 4, 5, 4],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("revenues.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
link_revenues = uploaded_image.link
|
||||
os.remove(imagefile)
|
||||
|
||||
embeds = [
|
||||
disnake.Embed(
|
||||
title=f"**{ticker.upper()} Year Estimates**",
|
||||
color=cfg.COLOR,
|
||||
),
|
||||
disnake.Embed(
|
||||
title=f"**{ticker.upper()} Quarter Earnings**",
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
disnake.Embed(
|
||||
title=f"**{ticker.upper()} Quarter Revenues**",
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
]
|
||||
embeds[0].set_image(url=link_estimates)
|
||||
embeds[1].set_image(url=link_earnings)
|
||||
embeds[2].set_image(url=link_revenues)
|
||||
titles = [
|
||||
f"**{ticker.upper()} Year Estimates**",
|
||||
f"**{ticker.upper()} Quarter Earnings**",
|
||||
f"**{ticker.upper()} Quarter Revenues**",
|
||||
]
|
||||
embeds_img = [
|
||||
f"{link_estimates}",
|
||||
f"{link_earnings}",
|
||||
f"{link_revenues}",
|
||||
]
|
||||
# Output data
|
||||
choices = [
|
||||
disnake.SelectOption(
|
||||
label=f"{ticker.upper()} Year Estimates", value="0", emoji="🟢"
|
||||
),
|
||||
disnake.SelectOption(
|
||||
label=f"{ticker.upper()} Quarter Earnings", value="1", emoji="🟢"
|
||||
),
|
||||
disnake.SelectOption(
|
||||
label=f"{ticker.upper()} Quarter Revenues", value="2", emoji="🟢"
|
||||
),
|
||||
]
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"titles": titles,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
"embeds_img": embeds_img,
|
||||
}
|
||||
75
bots/stocks/due_diligence/pt.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
import bots.helpers
|
||||
from bots.config_discordbot import logger
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.due_diligence import business_insider_model
|
||||
|
||||
|
||||
def pt_command(ticker: str = "", raw: bool = False, start=""):
|
||||
"""Displays price targets [Business Insider]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-pt %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if raw not in [True, False]:
|
||||
raise Exception("raw argument has to be true or false")
|
||||
|
||||
df_analyst_data = business_insider_model.get_price_target_from_analysts(ticker)
|
||||
stock = bots.helpers.load(ticker, start)
|
||||
title = f"Stocks: [Business Insider] Price Targets {ticker}"
|
||||
if df_analyst_data.empty or stock.empty:
|
||||
raise Exception("Enter valid ticker")
|
||||
|
||||
# Output Data
|
||||
|
||||
if raw:
|
||||
df_analyst_data.sort_index(ascending=False)
|
||||
report = "```" + df_analyst_data.to_string() + "```"
|
||||
|
||||
output = {
|
||||
"title": title,
|
||||
"description": report,
|
||||
}
|
||||
else:
|
||||
plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
if start:
|
||||
df_analyst_data = df_analyst_data[start:]
|
||||
|
||||
plt.plot(stock.index, stock["Adj Close"].values, lw=3)
|
||||
|
||||
plt.plot(df_analyst_data.groupby(by=["Date"]).mean())
|
||||
|
||||
plt.scatter(df_analyst_data.index, df_analyst_data["Price Target"], c="r", s=40)
|
||||
|
||||
plt.legend(["Closing Price", "Average Price Target", "Price Target"])
|
||||
|
||||
plt.title(f"{ticker.upper} (Time Series) and Price Target")
|
||||
plt.xlim(stock.index[0], stock.index[-1])
|
||||
plt.xlabel("Time")
|
||||
plt.ylabel("Share Price")
|
||||
plt.grid(b=True, which="major", color="#666666", linestyle="-")
|
||||
plt.savefig("ta_pt.png")
|
||||
|
||||
imagefile = bots.helpers.image_border("ta_pt.png")
|
||||
|
||||
output = {
|
||||
"title": title,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
|
||||
return output
|
||||
33
bots/stocks/due_diligence/sec.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from gamestonk_terminal.stocks.due_diligence import marketwatch_model
|
||||
|
||||
|
||||
def sec_command(ticker=""):
|
||||
"""Displays sec filings [Market Watch]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-sec %s", ticker)
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
df_financials = marketwatch_model.get_sec_filings(ticker)
|
||||
|
||||
if df_financials.empty:
|
||||
raise Exception("Enter a valid ticker")
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(df_financials.to_string())
|
||||
|
||||
df = df_financials
|
||||
df.loc[:, "Link"] = "[Link Source](" + df.loc[:, "Link"].astype(str)
|
||||
df.loc[:, "Link"] = df.loc[:, "Link"] + ")"
|
||||
|
||||
# Output data
|
||||
return {
|
||||
"title": "Stocks: [Market Watch] SEC Filings",
|
||||
"description": df.to_string(),
|
||||
}
|
||||
29
bots/stocks/due_diligence/supplier.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from gamestonk_terminal.stocks.due_diligence import csimarket_model
|
||||
|
||||
|
||||
def supplier_command(ticker=""):
|
||||
"""Displays suppliers of the company [CSIMarket]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("dd-supplier %s", ticker)
|
||||
|
||||
if not ticker:
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
tickers = csimarket_model.get_suppliers(ticker)
|
||||
|
||||
if not tickers:
|
||||
raise Exception("Enter a valid ticker")
|
||||
|
||||
# Debug user output
|
||||
if cfg.DEBUG:
|
||||
logger.debug(tickers)
|
||||
|
||||
# Output data
|
||||
return {
|
||||
"title": "Stocks: [CSIMarket] Company Suppliers",
|
||||
"description": tickers,
|
||||
}
|
||||
52
bots/stocks/government/contracts.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def contracts_command(
|
||||
ticker: str = "", past_transaction_days: int = 10, raw: bool = False
|
||||
):
|
||||
"""Displays contracts associated with tickers [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov-contracts %s %s %s", ticker, past_transaction_days, raw)
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
# Retrieve Data
|
||||
df_contracts = quiverquant_model.get_government_trading("contracts", ticker)
|
||||
|
||||
if df_contracts.empty:
|
||||
raise Exception("No government contracts found")
|
||||
|
||||
# Output Data
|
||||
df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]).dt.date
|
||||
|
||||
df_contracts = df_contracts[
|
||||
df_contracts["Date"].isin(df_contracts["Date"].unique()[:past_transaction_days])
|
||||
]
|
||||
|
||||
df_contracts.drop_duplicates(inplace=True)
|
||||
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
df_contracts.groupby("Date").sum().div(1000).plot(kind="bar", rot=0, ax=ax)
|
||||
ax.set_ylabel("Amount ($1k)")
|
||||
ax.set_title(f"Sum of latest government contracts to {ticker}")
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_contracts.png")
|
||||
imagefile = "gov_contracts.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": f"Stocks: [quiverquant.com] Contracts by {ticker}",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
115
bots/stocks/government/gtrades.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import matplotlib.dates as mdates
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def gtrades_command(
|
||||
ticker: str = "",
|
||||
gov_type="",
|
||||
past_transactions_months: int = 10,
|
||||
raw: bool = False,
|
||||
):
|
||||
"""Displays government trades [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"gov-gtrades %s %s %s %s",
|
||||
ticker,
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
raw,
|
||||
)
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
possible_args = ["congress", "senate", "house"]
|
||||
if gov_type == "":
|
||||
gov_type = "congress"
|
||||
elif gov_type not in possible_args:
|
||||
raise Exception(
|
||||
"Enter a valid government argument, options are: congress, senate and house"
|
||||
)
|
||||
|
||||
# Retrieve Data
|
||||
df_gov = quiverquant_model.get_government_trading(gov_type, ticker)
|
||||
|
||||
if df_gov.empty:
|
||||
raise Exception(f"No {gov_type} trading data found")
|
||||
|
||||
# Output Data
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=False)
|
||||
|
||||
start_date = datetime.now() - timedelta(days=past_transactions_months * 30)
|
||||
|
||||
df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"])
|
||||
|
||||
df_gov = df_gov[df_gov["TransactionDate"] > start_date]
|
||||
|
||||
if df_gov.empty:
|
||||
logger.debug("No recent %s trading data found", gov_type)
|
||||
raise Exception(f"No recent {gov_type} trading data found")
|
||||
|
||||
df_gov["min"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[0].strip("$").replace(",", "").strip()
|
||||
)
|
||||
df_gov["max"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[1].replace(",", "").strip().strip("$")
|
||||
if "-" in x
|
||||
else x.strip("$").replace(",", "").split("\n")[0]
|
||||
)
|
||||
|
||||
df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: int(float(x["min"]))
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -int(float(x["max"])),
|
||||
axis=1,
|
||||
)
|
||||
df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: int(float(x["max"]))
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -1 * int(float(x["min"])),
|
||||
axis=1,
|
||||
)
|
||||
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=True)
|
||||
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
ax.fill_between(
|
||||
df_gov["TransactionDate"].unique(),
|
||||
df_gov.groupby("TransactionDate")["lower"].sum().values / 1000,
|
||||
df_gov.groupby("TransactionDate")["upper"].sum().values / 1000,
|
||||
)
|
||||
|
||||
ax.set_xlim(
|
||||
[
|
||||
df_gov["TransactionDate"].values[0],
|
||||
df_gov["TransactionDate"].values[-1],
|
||||
]
|
||||
)
|
||||
ax.grid()
|
||||
ax.set_title(f"{gov_type.capitalize()} trading on {ticker}")
|
||||
ax.set_xlabel("Date")
|
||||
ax.set_ylabel("Amount ($1k)")
|
||||
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d"))
|
||||
plt.gcf().autofmt_xdate()
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_gtrades.png")
|
||||
imagefile = "gov_gtrades.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": "Stocks: [quiverquant.com] Government Trades",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
60
bots/stocks/government/histcont.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def histcont_command(ticker=""):
|
||||
"""Displays historical quarterly-contracts [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov-histcont %s", ticker)
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
# Retrieve Data
|
||||
df_contracts = quiverquant_model.get_government_trading(
|
||||
"quarter-contracts", ticker=ticker
|
||||
)
|
||||
|
||||
if df_contracts.empty:
|
||||
logger.debug("No quarterly government contracts found")
|
||||
raise Exception("No quarterly government contracts found")
|
||||
|
||||
# Output Data
|
||||
amounts = df_contracts.sort_values(by=["Year", "Qtr"])["Amount"].values
|
||||
|
||||
qtr = df_contracts.sort_values(by=["Year", "Qtr"])["Qtr"].values
|
||||
year = df_contracts.sort_values(by=["Year", "Qtr"])["Year"].values
|
||||
|
||||
quarter_ticks = [
|
||||
f"{quarter[0]}" if quarter[1] == 1 else "" for quarter in zip(year, qtr)
|
||||
]
|
||||
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15)
|
||||
|
||||
ax.set_xlim([-0.5, len(amounts) - 0.5])
|
||||
ax.set_xticks(np.arange(0, len(amounts)))
|
||||
ax.set_xticklabels(quarter_ticks)
|
||||
ax.grid()
|
||||
ax.set_title(f"Historical Quarterly Government Contracts for {ticker.upper()}")
|
||||
ax.set_xlabel("Date")
|
||||
ax.set_ylabel("Amount ($1k)")
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_histcont.png")
|
||||
imagefile = "gov_histcont.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": "Stocks: Historical Quarterly Government Contract ",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
79
bots/stocks/government/lastcontracts.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def lastcontracts_command(past_transactions_days: int = 2, num: int = 20):
|
||||
"""Displays last government contracts [quiverquant.com]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov lastcontracts %s %s", past_transactions_days, num)
|
||||
|
||||
df_contracts = quiverquant_model.get_government_trading("contracts")
|
||||
|
||||
if df_contracts.empty:
|
||||
logger.debug("No government contracts found")
|
||||
raise Exception("No government contracts found")
|
||||
|
||||
df_contracts.sort_values("Date", ascending=False)
|
||||
|
||||
df_contracts["Date"] = pd.to_datetime(df_contracts["Date"])
|
||||
df_contracts["Date"] = df_contracts["Date"].dt.date
|
||||
|
||||
df_contracts.drop_duplicates(inplace=True)
|
||||
df_contracts = df_contracts[
|
||||
df_contracts["Date"].isin(
|
||||
df_contracts["Date"].unique()[:past_transactions_days]
|
||||
)
|
||||
]
|
||||
|
||||
df_contracts = df_contracts[["Date", "Ticker", "Amount", "Agency"]][:num]
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
title = "Stocks: [quiverquant.com] Top buy government trading"
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df_contracts["Ticker"].values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
embeds = []
|
||||
df_contracts = df_contracts.T
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
for column in df_contracts.columns.values:
|
||||
description = "```" + df_contracts[column].fillna("").to_string() + "```"
|
||||
embeds.append(
|
||||
disnake.Embed(description=description, colour=cfg.COLOR,).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
181
bots/stocks/government/lasttrades.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def lasttrades_command(gov_type="", past_days: int = 5, representative=""):
|
||||
"""Displays trades made by the congress/senate/house [quiverquant.com]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"gov-lasttrades %s %s %s",
|
||||
gov_type,
|
||||
past_days,
|
||||
representative,
|
||||
)
|
||||
|
||||
possible_args = ["congress", "senate", "house"]
|
||||
if gov_type == "":
|
||||
gov_type = "congress"
|
||||
elif gov_type not in possible_args:
|
||||
raise Exception(
|
||||
"Enter a valid government argument, options are: congress, senate and house"
|
||||
)
|
||||
|
||||
# Retrieve Data
|
||||
df_gov = quiverquant_model.get_government_trading(gov_type)
|
||||
|
||||
# Output Data
|
||||
if df_gov.empty:
|
||||
raise Exception(f"No {gov_type} trading data found")
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=False)
|
||||
|
||||
df_gov = df_gov[
|
||||
df_gov["TransactionDate"].isin(df_gov["TransactionDate"].unique()[:past_days])
|
||||
]
|
||||
|
||||
if gov_type == "congress":
|
||||
df_gov = df_gov[
|
||||
[
|
||||
"TransactionDate",
|
||||
"Ticker",
|
||||
"Representative",
|
||||
"Transaction",
|
||||
"Range",
|
||||
"House",
|
||||
"ReportDate",
|
||||
]
|
||||
].rename(
|
||||
columns={
|
||||
"TransactionDate": "Transaction Date",
|
||||
"ReportDate": "Report Date",
|
||||
}
|
||||
)
|
||||
else:
|
||||
df_gov = df_gov[
|
||||
[
|
||||
"TransactionDate",
|
||||
"Ticker",
|
||||
"Representative",
|
||||
"Transaction",
|
||||
"Range",
|
||||
]
|
||||
].rename(columns={"TransactionDate": "Transaction Date"})
|
||||
|
||||
if representative:
|
||||
df_gov_rep = df_gov[
|
||||
df_gov["Representative"].str.split().str[0] == representative
|
||||
]
|
||||
|
||||
if df_gov_rep.empty:
|
||||
raise Exception(
|
||||
f"No representative {representative} found in the past {past_days}"
|
||||
f" days. The following are available: "
|
||||
f"{', '.join(df_gov['Representative'].str.split().str[0].unique())}"
|
||||
)
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
title = f"Stocks: [quiverquant.com] Trades by {representative}"
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df_gov_rep["Ticker"].values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
embeds = []
|
||||
df_gov_rep = df_gov_rep.T
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports = [f"{initial_str}"]
|
||||
for column in df_gov_rep.columns.values:
|
||||
description = "```" + df_gov_rep[column].fillna("").to_string() + "```"
|
||||
embeds.append(
|
||||
disnake.Embed(description=description, colour=cfg.COLOR,).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
output = {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
|
||||
else:
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
title = f"Stocks: [quiverquant.com] Trades for {gov_type.upper()}"
|
||||
initial_str = "Overview"
|
||||
i = 1
|
||||
for col_name in df_gov["Ticker"].values:
|
||||
menu = f"\nPage {i}: {col_name}"
|
||||
initial_str += f"\nPage {i}: {col_name}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
embeds = []
|
||||
df_gov = df_gov.T
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=initial_str,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports = [f"{initial_str}"]
|
||||
for column in df_gov.columns.values:
|
||||
description = "```" + df_gov[column].fillna("").to_string() + "```"
|
||||
embeds.append(
|
||||
disnake.Embed(description=description, colour=cfg.COLOR,).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
output = {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
|
||||
return output
|
||||
92
bots/stocks/government/lobbying.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from typing import Any, Dict
|
||||
import disnake
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def lobbying_command(ticker="", num: int = 10):
|
||||
"""Displays lobbying details [quiverquant.com]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov-lobbying %s", ticker)
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("A ticker is required")
|
||||
|
||||
# Retrieve Data
|
||||
df_lobbying = quiverquant_model.get_government_trading(
|
||||
"corporate-lobbying", ticker=ticker
|
||||
)
|
||||
|
||||
if df_lobbying.empty:
|
||||
logger.debug("No corporate lobbying found")
|
||||
raise Exception("No corporate lobbying found")
|
||||
|
||||
# Output Data
|
||||
report = ""
|
||||
title = (f"Stocks: [quiverquant.com] {ticker.upper()} Lobbying Details",)
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
for _, row in (
|
||||
df_lobbying.sort_values(by=["Date"], ascending=False).head(num).iterrows()
|
||||
):
|
||||
amount = (
|
||||
"$" + str(int(float(row["Amount"]))) if row["Amount"] is not None else "N/A"
|
||||
)
|
||||
report += f"{row['Date']}: {row['Client']} {amount}"
|
||||
if row["Amount"] is not None:
|
||||
report += "\t" + row["Specific_Issue"].replace("\n", " ").replace("\r", "")
|
||||
report += "\n"
|
||||
|
||||
if len(report) <= 4000:
|
||||
description = f"```{report}```"
|
||||
embed = disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
output: Dict[str, Any] = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
}
|
||||
else:
|
||||
i = 0
|
||||
str_start = 0
|
||||
str_end = 4000
|
||||
embeds = []
|
||||
reports = []
|
||||
while i <= len(report) / 4000:
|
||||
descript = f"```{report[str_start:str_end]}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=descript,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{descript}")
|
||||
str_end = str_start
|
||||
str_start += 4000
|
||||
i += 1
|
||||
|
||||
output = {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
|
||||
return output
|
||||
101
bots/stocks/government/qtrcontracts.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def qtrcontracts_command(num: int = 20, analysis=""):
|
||||
"""Displays a look at government contracts [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov-qtrcontracts %s %s", num, analysis)
|
||||
|
||||
possible_args = ["total", "upmom", "downmom"]
|
||||
if analysis == "":
|
||||
analysis = "total"
|
||||
elif analysis not in possible_args:
|
||||
raise Exception(
|
||||
"Enter a valid analysis argument, options are: total, upmom and downmom"
|
||||
)
|
||||
|
||||
# Retrieve Data
|
||||
df_contracts = quiverquant_model.get_government_trading("quarter-contracts")
|
||||
|
||||
if df_contracts.empty:
|
||||
raise Exception("No quarterly government contracts found")
|
||||
|
||||
tickers = quiverquant_model.analyze_qtr_contracts(analysis, num)
|
||||
|
||||
# Output Data
|
||||
if analysis in {"upmom", "downmom"}:
|
||||
description = tickers.to_string()
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
max_amount = 0
|
||||
quarter_ticks = []
|
||||
for symbol in tickers:
|
||||
amounts = (
|
||||
df_contracts[df_contracts["Ticker"] == symbol]
|
||||
.sort_values(by=["Year", "Qtr"])["Amount"]
|
||||
.values
|
||||
)
|
||||
|
||||
qtr = (
|
||||
df_contracts[df_contracts["Ticker"] == symbol]
|
||||
.sort_values(by=["Year", "Qtr"])["Qtr"]
|
||||
.values
|
||||
)
|
||||
year = (
|
||||
df_contracts[df_contracts["Ticker"] == symbol]
|
||||
.sort_values(by=["Year", "Qtr"])["Year"]
|
||||
.values
|
||||
)
|
||||
|
||||
ax.plot(np.arange(0, len(amounts)), amounts / 1_000_000, "-*", lw=2, ms=15)
|
||||
|
||||
if len(amounts) > max_amount:
|
||||
max_amount = len(amounts)
|
||||
quarter_ticks = [
|
||||
f"{quarter[0]} - Q{quarter[1]} " for quarter in zip(year, qtr)
|
||||
]
|
||||
|
||||
ax.set_xlim([-0.5, max_amount - 0.5])
|
||||
ax.set_xticks(np.arange(0, max_amount))
|
||||
ax.set_xticklabels(quarter_ticks)
|
||||
ax.grid()
|
||||
ax.legend(tickers)
|
||||
titles = {
|
||||
"upmom": "Highest increasing quarterly Government Contracts",
|
||||
"downmom": "Highest decreasing quarterly Government Contracts",
|
||||
}
|
||||
ax.set_title(titles[analysis])
|
||||
ax.set_xlabel("Date")
|
||||
ax.set_ylabel("Amount ($1M)")
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_qtrcontracts.png")
|
||||
imagefile = "gov_qtrcontracts.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
output = {
|
||||
"title": "Stocks: [quiverquant.com] Government Contracts",
|
||||
"imagefile": imagefile,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
elif analysis == "total":
|
||||
|
||||
tickers.index = [ind + " " * (7 - len(ind)) for ind in tickers.index]
|
||||
tickers[:] = [str(round(val[0] / 1e9, 2)) for val in tickers.values]
|
||||
tickers.columns = ["Amount [M]"]
|
||||
|
||||
output = {
|
||||
"title": "Stocks: [quiverquant.com] Government Contracts",
|
||||
"description": tickers.to_string(),
|
||||
}
|
||||
|
||||
return output
|
||||
111
bots/stocks/government/topbuys.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def topbuys_command(
|
||||
gov_type="",
|
||||
past_transactions_months: int = 5,
|
||||
num: int = 20,
|
||||
raw: bool = False,
|
||||
):
|
||||
"""Displays most purchased stocks by the congress/senate/house [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"gov-topbuys %s %s %s %s",
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
num,
|
||||
raw,
|
||||
)
|
||||
|
||||
possible_args = ["congress", "senate", "house"]
|
||||
if gov_type == "":
|
||||
gov_type = "congress"
|
||||
elif gov_type not in possible_args:
|
||||
raise Exception(
|
||||
"Enter a valid government argument, options are: congress, senate and house"
|
||||
)
|
||||
|
||||
# Retrieve Data
|
||||
df_gov = quiverquant_model.get_government_trading(gov_type)
|
||||
|
||||
if df_gov.empty:
|
||||
logger.debug("No %s trading data found", gov_type)
|
||||
raise Exception("No trading data found")
|
||||
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=False)
|
||||
start_date = datetime.now() - timedelta(days=past_transactions_months * 30)
|
||||
|
||||
df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"])
|
||||
|
||||
df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna()
|
||||
# Catch bug where error shown for purchase of >5,000,000
|
||||
df_gov["Range"] = df_gov["Range"].apply(
|
||||
lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x
|
||||
)
|
||||
df_gov["min"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[0].strip("$").replace(",", "").strip()
|
||||
)
|
||||
df_gov["max"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[1].replace(",", "").strip().strip("$")
|
||||
if "-" in x
|
||||
else x.strip("$").replace(",", "")
|
||||
)
|
||||
df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: float(x["min"])
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -float(x["max"]),
|
||||
axis=1,
|
||||
)
|
||||
df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: float(x["max"])
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -float(x["min"]),
|
||||
axis=1,
|
||||
)
|
||||
description = None
|
||||
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=True)
|
||||
if raw:
|
||||
df = pd.DataFrame(
|
||||
df_gov.groupby("Ticker")["upper"]
|
||||
.sum()
|
||||
.div(1000)
|
||||
.sort_values(ascending=False)
|
||||
.head(n=num)
|
||||
)
|
||||
description = "```" + df.to_string() + "```"
|
||||
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values(ascending=False).head(
|
||||
n=num
|
||||
).plot(kind="bar", rot=0, ax=ax)
|
||||
|
||||
ax.set_ylabel("Amount [1k $]")
|
||||
ax.set_title(
|
||||
f"Top {num} purchased stocks over last {past_transactions_months} "
|
||||
f"months (upper bound) for {gov_type.upper()}"
|
||||
)
|
||||
plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False)
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_topbuys.png")
|
||||
imagefile = "gov_topbuys.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": f"Stocks: [quiverquant.com] Top purchases for {gov_type.upper()}",
|
||||
"imagefile": imagefile,
|
||||
"description": description,
|
||||
}
|
||||
43
bots/stocks/government/toplobbying.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def toplobbying_command(num: int = 10, raw: bool = False):
|
||||
"""Displays top lobbying firms [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("gov-toplobbying %s %s", num, raw)
|
||||
|
||||
# Retrieve Data
|
||||
df_lobbying = quiverquant_model.get_government_trading("corporate-lobbying")
|
||||
|
||||
if df_lobbying.empty:
|
||||
raise Exception("No corporate lobbying found")
|
||||
|
||||
df_lobbying["Amount"] = df_lobbying.Amount.astype(float).fillna(0) / 100_000
|
||||
|
||||
lobbying_by_ticker = pd.DataFrame(
|
||||
df_lobbying.groupby("Ticker")["Amount"].agg("sum")
|
||||
).sort_values(by="Amount", ascending=False)
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
lobbying_by_ticker.head(num).plot(kind="bar", ax=ax)
|
||||
ax.set_xlabel("Ticker")
|
||||
ax.set_ylabel("Total Amount ($100k)")
|
||||
ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}")
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("ta_toplobbying.png")
|
||||
imagefile = "ta_toplobbying.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": "Stocks: [quiverquant.com] Top Lobbying Firms",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
122
bots/stocks/government/topsells.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.government import quiverquant_model
|
||||
|
||||
|
||||
def topsells_command(
|
||||
gov_type="",
|
||||
past_transactions_months: int = 5,
|
||||
num: int = 10,
|
||||
raw: bool = False,
|
||||
):
|
||||
"""Displays most sold stocks by the congress/senate/house [quiverquant.com]"""
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"gov-topsells %s %s %s %s",
|
||||
gov_type,
|
||||
past_transactions_months,
|
||||
num,
|
||||
raw,
|
||||
)
|
||||
|
||||
possible_args = ["congress", "senate", "house"]
|
||||
if gov_type == "":
|
||||
gov_type = "congress"
|
||||
elif gov_type not in possible_args:
|
||||
raise Exception(
|
||||
"Enter a valid government argument, options are: congress, senate and house"
|
||||
)
|
||||
|
||||
# Retrieve Data
|
||||
df_gov = quiverquant_model.get_government_trading(gov_type)
|
||||
|
||||
# Output Data
|
||||
if df_gov.empty:
|
||||
raise Exception(f"No {gov_type} trading data found\n")
|
||||
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=False)
|
||||
|
||||
start_date = datetime.now() - timedelta(days=past_transactions_months * 30)
|
||||
|
||||
df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"])
|
||||
|
||||
df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna()
|
||||
|
||||
df_gov["Range"] = df_gov["Range"].apply(
|
||||
lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x
|
||||
)
|
||||
|
||||
df_gov["min"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[0]
|
||||
.strip("$")
|
||||
.replace(",", "")
|
||||
.strip()
|
||||
.replace(">$", "")
|
||||
.strip()
|
||||
)
|
||||
df_gov["max"] = df_gov["Range"].apply(
|
||||
lambda x: x.split("-")[1]
|
||||
.replace(",", "")
|
||||
.strip()
|
||||
.strip("$")
|
||||
.replace(">$", "")
|
||||
.strip()
|
||||
if "-" in x
|
||||
else x.strip("$").replace(",", "").replace(">$", "").strip()
|
||||
)
|
||||
|
||||
df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: float(x["min"])
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -float(x["max"]),
|
||||
axis=1,
|
||||
)
|
||||
df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply(
|
||||
lambda x: float(x["max"])
|
||||
if x["Transaction"] == "Purchase"
|
||||
else -float(x["min"]),
|
||||
axis=1,
|
||||
)
|
||||
description = None
|
||||
df_gov = df_gov.sort_values("TransactionDate", ascending=True)
|
||||
if raw:
|
||||
df = pd.DataFrame(
|
||||
df_gov.groupby("Ticker")["upper"]
|
||||
.sum()
|
||||
.div(1000)
|
||||
.sort_values(ascending=True)
|
||||
.abs()
|
||||
.head(n=num)
|
||||
)
|
||||
description = "```" + df.to_string() + "```"
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values().abs().head(
|
||||
n=num
|
||||
).plot(kind="bar", rot=0, ax=ax)
|
||||
ax.set_ylabel("Amount ($1k)")
|
||||
ax.set_title(
|
||||
f"{num} most sold stocks over last {past_transactions_months} months"
|
||||
f" (upper bound) for {gov_type}"
|
||||
)
|
||||
plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False)
|
||||
fig.tight_layout()
|
||||
|
||||
plt.savefig("gov_topsells.png")
|
||||
imagefile = "gov_topsells.png"
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
return {
|
||||
"title": f"Stocks: [quiverquant.com] Top sells for {gov_type.upper()}",
|
||||
"imagefile": imagefile,
|
||||
"description": description,
|
||||
}
|
||||
119
bots/stocks/insider/lins.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
|
||||
import df2img
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import gst_imgur, logger
|
||||
from bots.helpers import save_image
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.insider import finviz_model
|
||||
|
||||
|
||||
def lins_command(ticker: str = "", num: int = 10):
|
||||
"""Display insider activity for a given stock ticker. [Source: Finviz]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker : Stock Ticker
|
||||
num : Number of latest insider activity to display
|
||||
"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("disc-lins %s", num)
|
||||
|
||||
d_finviz_insider = finviz_model.get_last_insider_activity(ticker)
|
||||
|
||||
df = pd.DataFrame.from_dict(d_finviz_insider)
|
||||
df.set_index("Date", inplace=True)
|
||||
|
||||
df = df[
|
||||
[
|
||||
"Relationship",
|
||||
"Transaction",
|
||||
"#Shares",
|
||||
"Cost",
|
||||
"Value ($)",
|
||||
"#Shares Total",
|
||||
"Insider Trading",
|
||||
"SEC Form 4",
|
||||
]
|
||||
]
|
||||
|
||||
df = df.head(num)
|
||||
df = df.replace(to_replace="Option Exercise", value="Opt Ex.", regex=True)
|
||||
|
||||
title = f"Insider Trading for {ticker.upper()}"
|
||||
|
||||
embeds: list = []
|
||||
|
||||
i, i2, end = 0, 0, 20
|
||||
df_pg = []
|
||||
embeds_img = []
|
||||
dindex = len(df.index)
|
||||
while i < dindex:
|
||||
df_pg = df.iloc[i:end]
|
||||
df_pg.append(df_pg)
|
||||
fig = df2img.plot_dataframe(
|
||||
df_pg,
|
||||
fig_size=(1600, (40 + (40 * 20))),
|
||||
col_width=[5, 14, 4, 4, 3, 4, 5, 8, 7],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image(f"disc-insider{i}.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link = uploaded_image.link
|
||||
embeds_img.append(
|
||||
f"{image_link}",
|
||||
)
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
)
|
||||
i2 += 1
|
||||
i += 20
|
||||
end += 20
|
||||
os.remove(imagefile)
|
||||
|
||||
# Author/Footer
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
url=cfg.AUTHOR_URL,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
embeds[i].set_footer(
|
||||
text=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
i = 0
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_image(url=embeds_img[i])
|
||||
|
||||
i += 1
|
||||
embeds[0].set_footer(text=f"Page 1 of {len(embeds)}")
|
||||
choices = [
|
||||
disnake.SelectOption(label="Home", value="0", emoji="🟢"),
|
||||
]
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
"embeds_img": embeds_img,
|
||||
}
|
||||
122
bots/stocks/options/cc_hist.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.helpers import image_border
|
||||
|
||||
|
||||
def cc_hist_command(
|
||||
ticker: str = None, expiry: str = "", strike: float = 10, opt_type: str = ""
|
||||
):
|
||||
"""Plot historical option prices
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiry: str
|
||||
expiration date
|
||||
strike: float
|
||||
Option strike price
|
||||
put: bool
|
||||
Calls for call
|
||||
Puts for put
|
||||
"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"opt-hist {ticker} {strike} {opt_type} {expiry}")
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
yf_ticker = yf.Ticker(ticker)
|
||||
dates = list(yf_ticker.options)
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yf.Ticker(ticker).option_chain(expiry)
|
||||
|
||||
if opt_type == "Calls":
|
||||
options = options.calls
|
||||
if opt_type == "Puts":
|
||||
options = options.puts
|
||||
|
||||
chain_id = options.loc[options.strike == strike, "contractSymbol"].values[0]
|
||||
df_hist = yf.download(chain_id)
|
||||
df_hist.index.name = "date"
|
||||
|
||||
plt_title = [
|
||||
f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical",
|
||||
"Volume",
|
||||
]
|
||||
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.06,
|
||||
subplot_titles=plt_title,
|
||||
row_width=[0.2, 0.7],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Candlestick(
|
||||
x=df_hist.index,
|
||||
open=df_hist.Open,
|
||||
high=df_hist.High,
|
||||
low=df_hist.Low,
|
||||
close=df_hist.Close,
|
||||
name="OHLC",
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
colors = [
|
||||
"green" if row.Open < row["Close"] else "red" for _, row in df_hist.iterrows()
|
||||
] # pylint: disable=E1120
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df_hist.index,
|
||||
y=df_hist.Volume,
|
||||
name="Volume",
|
||||
marker_color=colors,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=25, b=5),
|
||||
template=cfg.PLT_CANDLE_STYLE_TEMPLATE,
|
||||
showlegend=False,
|
||||
yaxis_title="Premium",
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
)
|
||||
fig.update_xaxes(
|
||||
rangebreaks=[
|
||||
dict(bounds=["sat", "mon"]),
|
||||
],
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
rand = np.random.randint(70000)
|
||||
imagefile = f"opt_hist{rand}.png"
|
||||
fig.write_image(imagefile)
|
||||
imagefile = image_border(imagefile)
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
fig.write_html(f"in/cc_hist_{rand}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cc_hist_{rand}.html)"
|
||||
|
||||
return {
|
||||
"title": f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
154
bots/stocks/options/hist.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.options import syncretism_model
|
||||
|
||||
|
||||
def hist_command(
|
||||
ticker: str = None,
|
||||
expiry: str = "",
|
||||
strike: float = 10,
|
||||
opt_type: str = "",
|
||||
greek: str = "",
|
||||
):
|
||||
"""Plot historical option prices
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiry: str
|
||||
expiration date
|
||||
strike: float
|
||||
Option strike price
|
||||
put: bool
|
||||
Calls for call
|
||||
Puts for put
|
||||
"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"opt-hist {ticker} {strike} {opt_type} {expiry} {greek}")
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if opt_type == "Puts":
|
||||
put = bool(True)
|
||||
if opt_type == "Calls":
|
||||
put = bool(False)
|
||||
chain_id = ""
|
||||
|
||||
df_hist = syncretism_model.get_historical_greeks(
|
||||
ticker, expiry, chain_id, strike, put
|
||||
)
|
||||
title = f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical {greek.upper()}"
|
||||
# Output data
|
||||
fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]])
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker.upper(),
|
||||
x=df_hist.index,
|
||||
y=df_hist.price,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
),
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="Premium",
|
||||
x=df_hist.index,
|
||||
y=df_hist["premium"],
|
||||
opacity=1,
|
||||
yaxis="y2",
|
||||
),
|
||||
)
|
||||
if greek:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"{greek.upper()}",
|
||||
x=df_hist.index,
|
||||
y=df_hist[greek],
|
||||
opacity=1,
|
||||
yaxis="y3",
|
||||
),
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=10, r=10, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=title,
|
||||
title_x=0.03,
|
||||
yaxis_title="<b>Stock Price</b> ($)",
|
||||
yaxis=dict(
|
||||
side="right",
|
||||
fixedrange=False,
|
||||
titlefont=dict(color="#fdc708"),
|
||||
tickfont=dict(color="#fdc708"),
|
||||
position=0.02,
|
||||
nticks=20,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
domain=[0.037, 1],
|
||||
),
|
||||
xaxis2=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis3=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
fixedrange=False,
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
yaxis2=dict(
|
||||
side="left",
|
||||
fixedrange=False,
|
||||
anchor="x",
|
||||
overlaying="y",
|
||||
titlefont=dict(color="#d81aea"),
|
||||
tickfont=dict(color="#d81aea"),
|
||||
nticks=20,
|
||||
),
|
||||
yaxis3=dict(
|
||||
side="left",
|
||||
position=0,
|
||||
fixedrange=False,
|
||||
overlaying="y",
|
||||
titlefont=dict(color="#00e6c3"),
|
||||
tickfont=dict(color="#00e6c3"),
|
||||
nticks=20,
|
||||
),
|
||||
hovermode="x unified",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "opt_hist.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/hist_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/hist_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
50
bots/stocks/options/iv.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import df2img
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.stocks.options import barchart_model
|
||||
|
||||
|
||||
def iv_command(ticker: str = None):
|
||||
"""Options IV"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-iv %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
df = barchart_model.get_options_info(ticker)
|
||||
df = df.fillna("")
|
||||
df = df.set_axis(
|
||||
[
|
||||
" ",
|
||||
"",
|
||||
],
|
||||
axis="columns",
|
||||
)
|
||||
df.set_index(" ", inplace=True)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(600, 1500),
|
||||
col_width=[3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("opt-iv.png", fig)
|
||||
|
||||
return {
|
||||
"title": f"{ticker.upper()} Options: IV",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
137
bots/stocks/options/oi.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.options import op_helpers, yfinance_model
|
||||
|
||||
|
||||
def oi_command(
|
||||
ticker: str = None,
|
||||
expiry: str = "",
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Options OI"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-oi %s %s %s %s", ticker, expiry, min_sp, max_sp)
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, expiry)
|
||||
calls = options.calls
|
||||
puts = options.puts
|
||||
current_price = yfinance_model.get_price(ticker)
|
||||
|
||||
min_strike = 0.75 * current_price
|
||||
max_strike = 1.95 * current_price
|
||||
|
||||
if len(calls) > 40:
|
||||
min_strike = 0.75 * current_price
|
||||
max_strike = 1.25 * current_price
|
||||
|
||||
if min_sp:
|
||||
min_strike = min_sp
|
||||
if max_sp:
|
||||
max_strike = max_sp
|
||||
|
||||
call_oi = calls.set_index("strike")["openInterest"] / 1000
|
||||
put_oi = puts.set_index("strike")["openInterest"] / 1000
|
||||
|
||||
df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
|
||||
df_opt = df_opt.rename(
|
||||
columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
|
||||
)
|
||||
|
||||
max_pain = op_helpers.calculate_max_pain(df_opt)
|
||||
fig = go.Figure()
|
||||
|
||||
dmax = df_opt[["OI_call", "OI_put"]].values.max()
|
||||
dmin = df_opt[["OI_call", "OI_put"]].values.min()
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df_opt.index,
|
||||
y=df_opt["OI_call"],
|
||||
name="Calls",
|
||||
mode="lines+markers",
|
||||
line=dict(color="green", width=3),
|
||||
)
|
||||
)
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df_opt.index,
|
||||
y=df_opt["OI_put"],
|
||||
name="Puts",
|
||||
mode="lines+markers",
|
||||
line=dict(color="red", width=3),
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[current_price, current_price],
|
||||
y=[dmin, dmax],
|
||||
mode="lines",
|
||||
line=dict(color="gold", width=2),
|
||||
name="Current Price",
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[max_pain, max_pain],
|
||||
y=[dmin, dmax],
|
||||
mode="lines",
|
||||
line=dict(color="grey", width=3, dash="dash"),
|
||||
name=f"Max Pain: {max_pain}",
|
||||
)
|
||||
)
|
||||
fig.update_xaxes(
|
||||
range=[min_strike, max_strike],
|
||||
constrain="domain",
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=60, b=20),
|
||||
template=cfg.PLT_SCAT_STYLE_TEMPLATE,
|
||||
title=f"Open Interest for {ticker.upper()} expiring {expiry}",
|
||||
title_x=0.5,
|
||||
legend_title="",
|
||||
xaxis_title="Strike",
|
||||
yaxis_title="Open Interest (1k)",
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
),
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
dragmode="pan",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "opt_oi.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/oi_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Open Interest for {ticker.upper()} expiring {expiry}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
151
bots/stocks/options/opt_chain.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import os
|
||||
|
||||
import df2img
|
||||
import disnake
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import gst_imgur, logger
|
||||
from bots.helpers import save_image
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.options import yfinance_model
|
||||
|
||||
|
||||
def chain_command(
|
||||
ticker: str = None,
|
||||
expiry: str = None,
|
||||
opt_type: str = None,
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Show calls/puts for given ticker and expiration"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"opt-chain %s %s %s %s %s", ticker, expiry, opt_type, min_sp, max_sp
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if not ticker:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, str(expiry))
|
||||
calls_df = options.calls.fillna(0)
|
||||
puts_df = options.puts.fillna(0)
|
||||
|
||||
column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"}
|
||||
columns = [
|
||||
"strike",
|
||||
"bid",
|
||||
"ask",
|
||||
"volume",
|
||||
"openInterest",
|
||||
"impliedVolatility",
|
||||
]
|
||||
|
||||
if opt_type == "Calls":
|
||||
df = calls_df[columns].rename(columns=column_map)
|
||||
if opt_type == "Puts":
|
||||
df = puts_df[columns].rename(columns=column_map)
|
||||
|
||||
min_strike = np.percentile(df["strike"], 1)
|
||||
max_strike = np.percentile(df["strike"], 100)
|
||||
|
||||
if min_sp:
|
||||
min_strike = min_sp
|
||||
if max_sp:
|
||||
max_strike = max_sp
|
||||
if min_sp > max_sp: # type: ignore
|
||||
min_sp, max_sp = max_strike, min_strike
|
||||
|
||||
df = df[df["strike"] >= min_strike]
|
||||
df = df[df["strike"] <= max_strike]
|
||||
|
||||
df["iv"] = pd.to_numeric(df["iv"].astype(float))
|
||||
|
||||
formats = {"iv": "{:.2f}"}
|
||||
for col, f in formats.items():
|
||||
df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640
|
||||
df.set_index("strike", inplace=True)
|
||||
|
||||
title = (
|
||||
f"Stocks: {opt_type} Option Chain for {ticker.upper()} on {expiry} [yfinance]"
|
||||
)
|
||||
|
||||
embeds: list = []
|
||||
# Output
|
||||
i, i2, end = 0, 0, 20
|
||||
df_pg = []
|
||||
embeds_img = []
|
||||
dindex = len(df.index)
|
||||
while i < dindex:
|
||||
df_pg = df.iloc[i:end]
|
||||
df_pg.append(df_pg)
|
||||
fig = df2img.plot_dataframe(
|
||||
df_pg,
|
||||
fig_size=(1000, (40 + (40 * 20))),
|
||||
col_width=[3, 3, 3, 3],
|
||||
tbl_cells=dict(
|
||||
height=35,
|
||||
),
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
template="plotly_dark",
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image(f"opt-chain{i}.png", fig)
|
||||
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link = uploaded_image.link
|
||||
embeds_img.append(
|
||||
f"{image_link}",
|
||||
)
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
)
|
||||
i2 += 1
|
||||
i += 20
|
||||
end += 20
|
||||
os.remove(imagefile)
|
||||
|
||||
# Author/Footer
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
url=cfg.AUTHOR_URL,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
embeds[i].set_footer(
|
||||
text=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
i = 0
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_image(url=embeds_img[i])
|
||||
|
||||
i += 1
|
||||
embeds[0].set_footer(text=f"Page 1 of {len(embeds)}")
|
||||
choices = [
|
||||
disnake.SelectOption(label="Home", value="0", emoji="🟢"),
|
||||
]
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
"embeds_img": embeds_img,
|
||||
}
|
||||
380
bots/stocks/options/overview.py
Normal file
@@ -0,0 +1,380 @@
|
||||
import os
|
||||
|
||||
import df2img
|
||||
import disnake
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
import yfinance as yf
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots import helpers
|
||||
from bots.config_discordbot import gst_imgur, logger
|
||||
from bots.menus.menu import Menu
|
||||
from gamestonk_terminal.stocks.options import op_helpers, yfinance_model
|
||||
from gamestonk_terminal.stocks.options.barchart_model import get_options_info
|
||||
|
||||
|
||||
# pylint: disable=R0914
|
||||
# pylint: disable=R0915
|
||||
def overview_command(
|
||||
ticker: str = None,
|
||||
expiry: str = None,
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Options Overview"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-overview %s %s %s %s", ticker, expiry, min_sp, max_sp)
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
# Get options info/dates, Look for logo_url
|
||||
df = get_options_info(ticker) # Barchart Options IV Overview
|
||||
|
||||
dates = yfinance_model.option_expirations(ticker) # Expiration dates
|
||||
tup = f"{ticker.upper()}"
|
||||
url = yf.Ticker(tup).info["logo_url"]
|
||||
url += "?raw=true" if url else ""
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, str(expiry))
|
||||
calls = options.calls.fillna(0)
|
||||
puts = options.puts.fillna(0)
|
||||
current_price = yfinance_model.get_price(ticker)
|
||||
|
||||
min_strike2 = np.percentile(calls["strike"], 1)
|
||||
max_strike2 = np.percentile(calls["strike"], 100)
|
||||
min_strike = 0.75 * current_price
|
||||
max_strike = 1.95 * current_price
|
||||
|
||||
if len(calls) > 40:
|
||||
min_strike = 0.75 * current_price
|
||||
max_strike = 1.25 * current_price
|
||||
|
||||
if min_sp:
|
||||
min_strike = min_sp
|
||||
min_strike2 = min_sp
|
||||
if max_sp:
|
||||
max_strike = max_sp
|
||||
max_strike2 = max_sp
|
||||
if min_sp > max_sp: # type: ignore
|
||||
min_sp, max_sp = max_strike2, min_strike2
|
||||
|
||||
call_oi = calls.set_index("strike")["openInterest"] / 1000
|
||||
put_oi = puts.set_index("strike")["openInterest"] / 1000
|
||||
|
||||
df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
|
||||
df_opt = df_opt.rename(
|
||||
columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
|
||||
)
|
||||
|
||||
max_pain = op_helpers.calculate_max_pain(df_opt)
|
||||
fig = go.Figure()
|
||||
|
||||
dmax = df_opt[["OI_call", "OI_put"]].values.max()
|
||||
dmin = df_opt[["OI_call", "OI_put"]].values.min()
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df_opt.index,
|
||||
y=df_opt["OI_call"],
|
||||
name="Calls",
|
||||
mode="lines+markers",
|
||||
line=dict(color="green", width=3),
|
||||
)
|
||||
)
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df_opt.index,
|
||||
y=df_opt["OI_put"],
|
||||
name="Puts",
|
||||
mode="lines+markers",
|
||||
line=dict(color="red", width=3),
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[current_price, current_price],
|
||||
y=[dmin, dmax],
|
||||
mode="lines",
|
||||
line=dict(color="gold", width=2),
|
||||
name="Current Price",
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[max_pain, max_pain],
|
||||
y=[dmin, dmax],
|
||||
mode="lines",
|
||||
line=dict(color="grey", width=3, dash="dash"),
|
||||
name=f"Max Pain: {max_pain}",
|
||||
)
|
||||
)
|
||||
fig.update_xaxes(
|
||||
range=[min_strike, max_strike],
|
||||
constrain="domain",
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=60, b=20),
|
||||
template=cfg.PLT_SCAT_STYLE_TEMPLATE,
|
||||
title=f"Open Interest for {ticker.upper()} expiring {expiry}",
|
||||
title_x=0.5,
|
||||
legend_title="",
|
||||
xaxis_title="Strike",
|
||||
yaxis_title="Open Interest (1k)",
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
),
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
dragmode="pan",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "opt_oi.png"
|
||||
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/oi_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
uploaded_image_oi = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link_oi = uploaded_image_oi.link
|
||||
os.remove(imagefile)
|
||||
|
||||
column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"}
|
||||
columns = [
|
||||
"strike",
|
||||
"bid",
|
||||
"ask",
|
||||
"volume",
|
||||
"openInterest",
|
||||
"impliedVolatility",
|
||||
]
|
||||
|
||||
calls_df = calls[columns].rename(columns=column_map)
|
||||
puts_df = puts[columns].rename(columns=column_map)
|
||||
|
||||
calls_df = calls_df[calls_df["strike"] >= min_strike2]
|
||||
calls_df = calls_df[calls_df["strike"] <= max_strike2]
|
||||
puts_df = puts_df[puts_df["strike"] >= min_strike2]
|
||||
puts_df = puts_df[puts_df["strike"] <= max_strike2]
|
||||
|
||||
calls_df["iv"] = pd.to_numeric(calls_df["iv"].astype(float))
|
||||
puts_df["iv"] = pd.to_numeric(puts_df["iv"].astype(float))
|
||||
|
||||
formats = {"iv": "{:.2f}"}
|
||||
for col, f in formats.items():
|
||||
calls_df[col] = calls_df[col].map(
|
||||
lambda x: f.format(x) # pylint: disable=W0640
|
||||
)
|
||||
puts_df[col] = puts_df[col].map(lambda x: f.format(x)) # pylint: disable=W0640
|
||||
|
||||
calls_df.set_index("strike", inplace=True)
|
||||
puts_df.set_index("strike", inplace=True)
|
||||
|
||||
if "-" in df.iloc[0, 1]:
|
||||
iv = f"```diff\n- {df.iloc[0, 1]}\n```"
|
||||
else:
|
||||
iv = f"```yaml\n {df.iloc[0, 1]}\n```"
|
||||
|
||||
pfix, sfix = f"{ticker.upper()} ", f" expiring {expiry}"
|
||||
if expiry == dates[0]:
|
||||
pfix = f"{ticker.upper()} Weekly "
|
||||
sfix = ""
|
||||
|
||||
titles = [f"{ticker.upper()} Overview", f"{pfix}Open Interest{sfix}"]
|
||||
embeds = [
|
||||
disnake.Embed(
|
||||
title=f"{ticker.upper()} Overview",
|
||||
color=cfg.COLOR,
|
||||
),
|
||||
disnake.Embed(
|
||||
title=f"{pfix}Open Interest{sfix}",
|
||||
description=plt_link,
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
]
|
||||
choices = [
|
||||
disnake.SelectOption(label=f"{ticker.upper()} Overview", value="0", emoji="🟢"),
|
||||
disnake.SelectOption(label=f"{pfix}Open Interest{sfix}", value="1", emoji="🟢"),
|
||||
]
|
||||
embeds_img = []
|
||||
|
||||
i, i2, end = 0, 0, 20
|
||||
df_calls = []
|
||||
dindex = len(calls_df.index)
|
||||
while i <= dindex:
|
||||
df_calls = calls_df.iloc[i:end]
|
||||
df_calls.append(df_calls)
|
||||
figc = df2img.plot_dataframe(
|
||||
df_calls,
|
||||
fig_size=(1000, (40 + (40 * 20))),
|
||||
col_width=[3, 3, 3, 3],
|
||||
tbl_cells=cfg.PLT_TBL_CELLS,
|
||||
font=cfg.PLT_TBL_FONT,
|
||||
template=cfg.PLT_TBL_STYLE_TEMPLATE,
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = helpers.save_image("opt-calls.png", figc)
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link = uploaded_image.link
|
||||
embeds_img.append(
|
||||
f"{image_link}",
|
||||
)
|
||||
titles.append(f"{pfix}Calls{sfix}")
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=f"{pfix}Calls{sfix}",
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
)
|
||||
i2 += 1
|
||||
i += 20
|
||||
end += 20
|
||||
os.remove(imagefile)
|
||||
|
||||
# Add Calls page field
|
||||
i, page, puts_page = 2, 0, 3
|
||||
i3 = i2 + 2
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Calls Page 1", value="2", emoji="🟢"),
|
||||
)
|
||||
for i in range(2, i3):
|
||||
page += 1
|
||||
puts_page += 1
|
||||
|
||||
embeds[i].add_field(name=f"Calls Page {page}", value="_ _", inline=True)
|
||||
|
||||
# Puts Pages
|
||||
i, end = 0, 20
|
||||
df_puts = []
|
||||
|
||||
dindex = len(puts_df.index)
|
||||
while i <= dindex:
|
||||
df_puts = puts_df.iloc[i:end]
|
||||
df_puts.append(df_puts)
|
||||
figp = df2img.plot_dataframe(
|
||||
df_puts,
|
||||
fig_size=(1000, (40 + (40 * 20))),
|
||||
col_width=[3, 3, 3, 3],
|
||||
tbl_cells=cfg.PLT_TBL_CELLS,
|
||||
font=cfg.PLT_TBL_FONT,
|
||||
template=cfg.PLT_TBL_STYLE_TEMPLATE,
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = helpers.save_image("opt-puts.png", figp)
|
||||
uploaded_image = gst_imgur.upload_image(imagefile, title="something")
|
||||
image_link = uploaded_image.link
|
||||
embeds_img.append(
|
||||
f"{image_link}",
|
||||
)
|
||||
titles.append(f"{pfix}Puts{sfix}")
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=f"{pfix}Puts{sfix}",
|
||||
colour=cfg.COLOR,
|
||||
),
|
||||
)
|
||||
i2 += 1
|
||||
i += 20
|
||||
end += 20
|
||||
os.remove(imagefile)
|
||||
|
||||
# Add Puts page field
|
||||
i, page = 0, 0
|
||||
puts_page -= 1
|
||||
i2 += 2
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Puts Page 1", value=f"{puts_page}", emoji="🟢"),
|
||||
)
|
||||
for i in range(puts_page, i2):
|
||||
page += 1
|
||||
embeds[i].add_field(name=f"Puts Page {page}", value="_ _", inline=True)
|
||||
|
||||
# Author/Footer
|
||||
for i in range(0, i2):
|
||||
embeds[i].set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
url=cfg.AUTHOR_URL,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
embeds[i].set_footer(
|
||||
text=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
# Set images to Pages
|
||||
i = 0
|
||||
img_i = 0
|
||||
embeds[1].set_image(url=image_link_oi)
|
||||
for i in range(2, i2):
|
||||
embeds[i].set_image(url=embeds_img[img_i])
|
||||
img_i += 1
|
||||
i += 1
|
||||
|
||||
if url:
|
||||
embeds[0].set_thumbnail(url=f"{url}")
|
||||
else:
|
||||
embeds[0].set_thumbnail(url=cfg.AUTHOR_ICON_URL)
|
||||
|
||||
# Overview Section
|
||||
embeds[0].add_field(name=f"{df.iloc[0, 0]}", value=iv, inline=False)
|
||||
|
||||
embeds[0].add_field(
|
||||
name=f"•{df.iloc[1, 0]}", value=f"```css\n{df.iloc[1, 1]}\n```", inline=True
|
||||
)
|
||||
for N in range(2, 6):
|
||||
embeds[0].add_field(
|
||||
name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}",
|
||||
value=f"```css\n{df.iloc[N, 1]}\n```",
|
||||
inline=True,
|
||||
)
|
||||
|
||||
embeds[0].add_field(name="_ _", value="_ _", inline=False)
|
||||
for N in range(6, 8):
|
||||
embeds[0].add_field(
|
||||
name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}",
|
||||
value=f"```css\n{df.iloc[N, 1]}\n```",
|
||||
inline=True,
|
||||
)
|
||||
|
||||
embeds[0].add_field(name="_ _", value="_ _", inline=False)
|
||||
for N in range(8, 10):
|
||||
embeds[0].add_field(
|
||||
name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}",
|
||||
value=f"```css\n{df.iloc[N, 1]}\n```",
|
||||
inline=True,
|
||||
)
|
||||
|
||||
embeds[0].add_field(name="_ _", value="_ _", inline=False)
|
||||
for N in range(10, 12):
|
||||
embeds[0].add_field(
|
||||
name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}",
|
||||
value=f"```css\n{df.iloc[N, 1]}\n```",
|
||||
inline=True,
|
||||
)
|
||||
|
||||
embeds[0].set_footer(text=f"Page 1 of {len(embeds)}")
|
||||
os.remove(imagefile)
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"titles": titles,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
"embeds_img": embeds_img,
|
||||
}
|
||||
96
bots/stocks/options/unu.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import df2img
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import requests
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import save_image
|
||||
from gamestonk_terminal.helper_funcs import get_user_agent
|
||||
|
||||
|
||||
def unu_command(num: int = None):
|
||||
"""Unusual Options"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-unu %s", num)
|
||||
|
||||
# Check for argument
|
||||
if num is None:
|
||||
num = 10
|
||||
|
||||
pages = np.arange(0, num // 20 + 1)
|
||||
data_list = []
|
||||
for page_num in pages:
|
||||
|
||||
r = requests.get(
|
||||
f"https://app.fdscanner.com/api2/unusualvolume?p=0&page_size=20&page={int(page_num)}",
|
||||
headers={"User-Agent": get_user_agent()},
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
logger.debug("Error in fdscanner request")
|
||||
return pd.DataFrame(), "request error"
|
||||
|
||||
data_list.append(r.json())
|
||||
|
||||
ticker, expiry, option_strike, option_type, ask, bid, oi, vol, voi = (
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
for data in data_list:
|
||||
for entry in data["data"]:
|
||||
ticker.append(entry["tk"])
|
||||
expiry.append(entry["expiry"])
|
||||
option_strike.append(float(entry["s"]))
|
||||
option_type.append("Put" if entry["t"] == "P" else "Call")
|
||||
ask.append(entry["a"])
|
||||
bid.append(entry["b"])
|
||||
oi.append(entry["oi"])
|
||||
vol.append(entry["v"])
|
||||
voi.append(entry["vol/oi"])
|
||||
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"Ticker": ticker,
|
||||
"Exp": expiry,
|
||||
"Strike": option_strike,
|
||||
"Type": option_type,
|
||||
"Vol/OI": voi,
|
||||
"Vol": vol,
|
||||
"OI": oi,
|
||||
}
|
||||
)
|
||||
|
||||
df = df.replace({"2021-", "2022-"}, "", regex=True)
|
||||
df.set_index("Ticker", inplace=True)
|
||||
dindex = len(df.index)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(800, (40 + (40 * dindex))),
|
||||
col_width=[3, 3, 3, 3, 3, 3, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("opt-unu.png", fig)
|
||||
|
||||
return {
|
||||
"title": "Unusual Options",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
119
bots/stocks/options/vol.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.options import yfinance_model
|
||||
|
||||
|
||||
def vol_command(
|
||||
ticker: str = None,
|
||||
expiry: str = "",
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Options VOL"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-vol %s %s %s %s", ticker, expiry, min_sp, max_sp)
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, expiry)
|
||||
current_price = yfinance_model.get_price(ticker)
|
||||
|
||||
if min_sp is None:
|
||||
min_strike = 0.75 * current_price
|
||||
else:
|
||||
min_strike = min_sp
|
||||
|
||||
if max_sp is None:
|
||||
max_strike = 1.25 * current_price
|
||||
else:
|
||||
max_strike = max_sp
|
||||
|
||||
calls = options.calls
|
||||
puts = options.puts
|
||||
call_v = calls.set_index("strike")["volume"] / 1000
|
||||
put_v = puts.set_index("strike")["volume"] / 1000
|
||||
|
||||
df_opt = pd.merge(put_v, call_v, left_index=True, right_index=True)
|
||||
dmax = df_opt.values.max()
|
||||
|
||||
fig = go.Figure()
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=call_v.index,
|
||||
y=call_v.values,
|
||||
name="Calls",
|
||||
mode="lines+markers",
|
||||
line=dict(color="green", width=3),
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=put_v.index,
|
||||
y=put_v.values,
|
||||
name="Puts",
|
||||
mode="lines+markers",
|
||||
line=dict(color="red", width=3),
|
||||
)
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[current_price, current_price],
|
||||
y=[0, dmax],
|
||||
mode="lines",
|
||||
line=dict(color="gold", width=2),
|
||||
name="Current Price",
|
||||
)
|
||||
)
|
||||
fig.update_xaxes(
|
||||
range=[min_strike, max_strike],
|
||||
constrain="domain",
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=60, b=20),
|
||||
template=cfg.PLT_SCAT_STYLE_TEMPLATE,
|
||||
title=f"Volume for {ticker.upper()} expiring {expiry}",
|
||||
title_x=0.5,
|
||||
legend_title="",
|
||||
xaxis_title="Strike",
|
||||
yaxis_title="Volume (1k)",
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
),
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
dragmode="pan",
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "opt_vol.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/vol_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vol_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Volume for {ticker.upper()} expiring {expiry}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
124
bots/stocks/options/vsurf.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from scipy.spatial import Delaunay
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.stocks.options import yfinance_model
|
||||
|
||||
|
||||
def vsurf_command(
|
||||
ticker: str = "",
|
||||
z: str = "IV",
|
||||
):
|
||||
"""Display vol surface
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: Stock Ticker
|
||||
z : The variable for the Z axis
|
||||
"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("opt-oi %s %s", ticker, z)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
data = yfinance_model.get_iv_surface(ticker)
|
||||
if data.empty:
|
||||
raise Exception(f"No options data found for {ticker}.\n")
|
||||
|
||||
Y = data.dte
|
||||
X = data.strike
|
||||
if z == "IV":
|
||||
Z = data.impliedVolatility
|
||||
label = "Volatility"
|
||||
elif z == "OI":
|
||||
Z = data.openInterest
|
||||
label = "Open Interest"
|
||||
elif z == "LP":
|
||||
Z = data.lastPrice
|
||||
label = "Last Price"
|
||||
|
||||
points3D = np.vstack((X, Y, Z)).T
|
||||
points2D = points3D[:, :2]
|
||||
tri = Delaunay(points2D)
|
||||
I, J, K = tri.simplices.T
|
||||
|
||||
lighting_effects = dict(
|
||||
ambient=0.5, diffuse=0.5, roughness=0.5, specular=0.4, fresnel=0.4
|
||||
)
|
||||
fig = go.Figure(
|
||||
data=[
|
||||
go.Mesh3d(
|
||||
z=Z,
|
||||
x=X,
|
||||
y=Y,
|
||||
i=I,
|
||||
j=J,
|
||||
k=K,
|
||||
intensity=Z,
|
||||
colorscale=cfg.PLT_3DMESH_COLORSCALE,
|
||||
hovertemplate="<b>DTE</b>: %{y} <br><b>Strike</b>: %{x} <br><b>"
|
||||
+ z
|
||||
+ "</b>: %{z}<extra></extra>",
|
||||
showscale=False,
|
||||
flatshading=True,
|
||||
lighting=lighting_effects,
|
||||
)
|
||||
]
|
||||
)
|
||||
fig.update_layout(
|
||||
scene=dict(
|
||||
xaxis=dict(
|
||||
title="Strike",
|
||||
tickfont=dict(size=11),
|
||||
titlefont=dict(size=12),
|
||||
),
|
||||
yaxis=dict(
|
||||
title="DTE",
|
||||
),
|
||||
zaxis=dict(
|
||||
title=z,
|
||||
),
|
||||
),
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=40, b=20),
|
||||
template=cfg.PLT_3DMESH_STYLE_TEMPLATE,
|
||||
title=f"{label} Surface for {ticker.upper()}",
|
||||
title_x=0.5,
|
||||
hoverlabel=cfg.PLT_3DMESH_HOVERLABEL,
|
||||
scene_camera=dict(
|
||||
up=dict(x=0, y=0, z=2),
|
||||
center=dict(x=0, y=0, z=-0.3),
|
||||
eye=dict(x=1.25, y=1.25, z=0.69),
|
||||
),
|
||||
scene=cfg.PLT_3DMESH_SCENE,
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "opt-vsurf.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/vsurf_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vsurf_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"{label} Surface for {ticker.upper()}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
40
bots/stocks/quote.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import df2img
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import quote, save_image
|
||||
|
||||
|
||||
def quote_command(ticker: str = None):
|
||||
"""Ticker Quote"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("quote %s", ticker)
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
df = quote(ticker)
|
||||
fig = df2img.plot_dataframe(
|
||||
df,
|
||||
fig_size=(600, 1500),
|
||||
col_width=[2, 3],
|
||||
tbl_cells=dict(
|
||||
align="left",
|
||||
height=35,
|
||||
),
|
||||
template="plotly_dark",
|
||||
font=dict(
|
||||
family="Consolas",
|
||||
size=20,
|
||||
),
|
||||
paper_bgcolor="rgba(0, 0, 0, 0)",
|
||||
)
|
||||
imagefile = save_image("quote.png", fig)
|
||||
|
||||
return {
|
||||
"title": f"{ticker.upper()} Quote",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
125
bots/stocks/screener/financial.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def financial_command(preset="template", sort="", limit: int = 5, ascend: bool = False):
|
||||
"""Displays returned results from preset by financial metrics [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-financial %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"financial",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
description = ""
|
||||
title = "Stocks: [Finviz] Financial Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
df_screen = df_screen.dropna(axis="columns", how="all")
|
||||
|
||||
if sort:
|
||||
if " ".join(sort) in so.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),
|
||||
so.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! Select from: {', '.join(so.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")
|
||||
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
137
bots/stocks/screener/historical.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import configparser
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import yfinance as yf
|
||||
from finvizfinance.screener import ticker
|
||||
from matplotlib import pyplot as plt
|
||||
from pandas.plotting import register_matplotlib_converters
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.screener import finviz_model
|
||||
|
||||
# pylint:disable=no-member
|
||||
|
||||
|
||||
def historical_command(signal: str = "", start=""):
|
||||
"""Displays historical price comparison between similar companies [Yahoo Finance]"""
|
||||
|
||||
# Debug user input
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-historical %s %s", signal, start)
|
||||
|
||||
# Check for argument
|
||||
if signal not in so.d_signals_desc:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
register_matplotlib_converters()
|
||||
|
||||
screen = ticker.Ticker()
|
||||
|
||||
if signal in finviz_model.d_signals:
|
||||
screen.set_filter(signal=finviz_model.d_signals[signal])
|
||||
|
||||
else:
|
||||
preset_filter = configparser.RawConfigParser()
|
||||
preset_filter.optionxform = str # type: ignore
|
||||
preset_filter.read(so.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))
|
||||
logger.debug(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."
|
||||
)
|
||||
|
||||
return {
|
||||
"title": "ERROR Stocks: [Yahoo Finance] Historical Screener",
|
||||
"description": error,
|
||||
}
|
||||
|
||||
for parsed_stock in l_parsed_stocks:
|
||||
l_stocks.remove(parsed_stock)
|
||||
|
||||
if signal:
|
||||
plt.title(
|
||||
f"Screener Historical Price using {finviz_model.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]])
|
||||
|
||||
imagefile = "scr_historical.png"
|
||||
plt.savefig(imagefile)
|
||||
imagefile = image_border(imagefile)
|
||||
|
||||
return {
|
||||
"title": "Stocks: [Yahoo Finance] Historical Screener",
|
||||
"description": description,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
128
bots/stocks/screener/overview.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def overview_command(
|
||||
preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False
|
||||
):
|
||||
"""Displays stocks with overview data such as Sector and Industry [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-overview %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"overview",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
description = ""
|
||||
title = "Stocks: [Finviz] Overview Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
df_screen = df_screen.dropna(axis="columns", how="all")
|
||||
|
||||
if sort:
|
||||
if " ".join(sort) in so.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),
|
||||
so.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! Select from: {', '.join(so.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")
|
||||
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
127
bots/stocks/screener/ownership.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def ownership_command(
|
||||
preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False
|
||||
):
|
||||
"""Displays stocks based on own share float and ownership data [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-ownership %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"ownership",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
description = ""
|
||||
title = "Stocks: [Finviz] Ownership Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
df_screen = df_screen.dropna(axis="columns", how="all")
|
||||
|
||||
if sort:
|
||||
if " ".join(sort) in so.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),
|
||||
so.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! Select from: {', '.join(so.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")
|
||||
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
148
bots/stocks/screener/performance.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def performance_command(
|
||||
preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False
|
||||
):
|
||||
"""Displays stocks and sort by performance categories [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-performance %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"performance",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
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 = ""
|
||||
title = "Stocks: [Finviz] Performance Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
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")
|
||||
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
29
bots/stocks/screener/presets_custom.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.stocks.screener import screener_options as so
|
||||
|
||||
|
||||
def presets_custom_command():
|
||||
"""Displays every custom preset"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-presets")
|
||||
|
||||
description = ""
|
||||
for preset in so.presets:
|
||||
with open(
|
||||
so.presets_path + preset + ".ini",
|
||||
encoding="utf8",
|
||||
) as f:
|
||||
preset_line = ""
|
||||
for line in f:
|
||||
if line.strip() == "[General]":
|
||||
break
|
||||
preset_line += line.strip()
|
||||
description += f"**{preset}:** *{preset_line.split('Description: ')[1].replace('#', '')}*\n"
|
||||
|
||||
return {
|
||||
"title": "Stocks: Screener Custom Presets",
|
||||
"description": description,
|
||||
}
|
||||
20
bots/stocks/screener/presets_default.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.stocks.screener import screener_options as so
|
||||
|
||||
|
||||
def presets_default_command():
|
||||
"""Displays default presets"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-presets")
|
||||
|
||||
description = ""
|
||||
for signame, sigdesc in so.d_signals_desc.items():
|
||||
description += f"**{signame}:** *{sigdesc}*\n"
|
||||
|
||||
return {
|
||||
"title": "Stocks: Screener Default Presets",
|
||||
"description": description,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
import bots.config_discordbot as cfg
|
||||
|
||||
presets_path = os.path.join(
|
||||
cfg.GST_PATH, "gamestonk_terminal", "stocks", "screener", "presets", ""
|
||||
127
bots/stocks/screener/technical.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def technical_command(
|
||||
preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False
|
||||
):
|
||||
"""Displays stocks according to chosen preset, sorting by technical factors [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-technical %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"technical",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
description = ""
|
||||
title = "Stocks: [Finviz] Technical Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
df_screen = df_screen.dropna(axis="columns", how="all")
|
||||
|
||||
if sort:
|
||||
if " ".join(sort) in so.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),
|
||||
so.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! Select from: {', '.join(so.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")
|
||||
|
||||
embeds = []
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
127
bots/stocks/screener/valuation.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import difflib
|
||||
|
||||
import disnake
|
||||
import pandas as pd
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots.menus.menu import Menu
|
||||
from bots.stocks.screener import screener_options as so
|
||||
from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data
|
||||
|
||||
|
||||
def valuation_command(
|
||||
preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False
|
||||
):
|
||||
"""Displays results from chosen preset focusing on valuation metrics [Finviz]"""
|
||||
|
||||
# Check for argument
|
||||
if preset == "template" or preset not in so.all_presets:
|
||||
raise Exception("Invalid preset selected!")
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("scr-valuation %s %s %s %s", preset, sort, limit, ascend)
|
||||
|
||||
# Check for argument
|
||||
if limit < 0:
|
||||
raise Exception("Number has to be above 0")
|
||||
|
||||
# Output Data
|
||||
df_screen = get_screener_data(
|
||||
preset,
|
||||
"valuation",
|
||||
limit,
|
||||
ascend,
|
||||
)
|
||||
|
||||
description = ""
|
||||
title = "Stocks: [Finviz] Valuation Screener"
|
||||
if isinstance(df_screen, pd.DataFrame):
|
||||
if df_screen.empty:
|
||||
raise Exception("No data found.")
|
||||
|
||||
df_screen = df_screen.dropna(axis="columns", how="all")
|
||||
|
||||
if sort:
|
||||
if " ".join(sort) in so.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),
|
||||
so.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! Select from: {', '.join(so.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")
|
||||
|
||||
embeds = []
|
||||
choices = [
|
||||
disnake.SelectOption(label="Overview", value="0", emoji="🟢"),
|
||||
]
|
||||
initial_str = description + "Overview"
|
||||
i = 1
|
||||
for column in df_screen.columns.values:
|
||||
menu = f"\nPage {i}: {column}"
|
||||
initial_str += f"\nPage {i}: {column}"
|
||||
if i < 19:
|
||||
choices.append(
|
||||
disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
if i == 20:
|
||||
choices.append(
|
||||
disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"),
|
||||
)
|
||||
i += 1
|
||||
reports = [f"{initial_str}"]
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
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:
|
||||
description = f"```{df_screen[column].fillna('')}```"
|
||||
embeds.append(
|
||||
disnake.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=cfg.COLOR,
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
)
|
||||
reports.append(f"{description}")
|
||||
|
||||
return {
|
||||
"view": Menu,
|
||||
"title": title,
|
||||
"description": reports,
|
||||
"embed": embeds,
|
||||
"choices": choices,
|
||||
}
|
||||
150
bots/stocks/technical_analysis/ad.py
Normal file
@@ -0,0 +1,150 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import volume_model
|
||||
|
||||
|
||||
def ad_command(ticker="", is_open="False", start="", end=""):
|
||||
"""Displays chart with accumulation/distribution line [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug("ta-ad %s %s %s %s", ticker, is_open, start, end)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if is_open in ["false", "False", "FALSE", ""]:
|
||||
is_open = False
|
||||
|
||||
if is_open in ["true", "True", "TRUE"]:
|
||||
is_open = True
|
||||
|
||||
if is_open not in [True, False]:
|
||||
raise Exception("is_open argument has to be true or false")
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
divisor = 1_000_000
|
||||
df_vol = df_stock["Volume"].dropna()
|
||||
df_vol = df_vol.values / divisor
|
||||
df_ta = volume_model.ad(df_stock, is_open)
|
||||
df_cal = df_ta.values
|
||||
df_cal = df_cal / divisor
|
||||
|
||||
fig = make_subplots(
|
||||
rows=3,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.06,
|
||||
row_width=[0.2, 0.2, 0.2],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"].values,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
colors = [
|
||||
"green" if row.Open < row["Adj Close"] else "red"
|
||||
for _, row in df_stock.iterrows()
|
||||
]
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df_stock.index,
|
||||
y=df_vol,
|
||||
name="Volume [M]",
|
||||
marker_color=colors,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="A/D [M]",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
line=dict(width=2),
|
||||
opacity=1,
|
||||
),
|
||||
row=3,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hline(
|
||||
y=0,
|
||||
fillcolor="grey",
|
||||
opacity=1,
|
||||
layer="below",
|
||||
line_width=3,
|
||||
line=dict(color="grey", dash="dash"),
|
||||
row=3,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=10, r=0, t=40, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"{ticker} AD",
|
||||
title_x=0.4,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis2_title="Volume [M]",
|
||||
yaxis3_title="A/D [M]",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_ad.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/ad_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ad_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Accumulation/Distribution Line {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
145
bots/stocks/technical_analysis/adosc.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import volume_model
|
||||
|
||||
|
||||
def adosc_command(ticker="", is_open="False", fast="3", slow="10", start="", end=""):
|
||||
"""Displays chart with chaikin oscillator [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
# pylint: disable=logging-too-many-args
|
||||
logger.debug(
|
||||
"ta-adosc %s %s %s %s %s",
|
||||
ticker,
|
||||
is_open,
|
||||
fast,
|
||||
slow,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if not fast.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
fast = float(fast)
|
||||
if not slow.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
slow = float(slow)
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
# Output Data
|
||||
divisor = 1_000_000
|
||||
df_vol = df_stock["Volume"].dropna()
|
||||
df_vol = df_vol.values / divisor
|
||||
df_ta = volume_model.adosc(df_stock, is_open, fast, slow)
|
||||
df_cal = df_ta.values
|
||||
df_cal = df_cal / divisor
|
||||
|
||||
fig = make_subplots(
|
||||
rows=3,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.06,
|
||||
row_width=[0.2, 0.2, 0.2],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"].values,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
colors = [
|
||||
"green" if row.Open < row["Adj Close"] else "red"
|
||||
for _, row in df_stock.iterrows()
|
||||
]
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=df_stock.index,
|
||||
y=df_vol,
|
||||
name="Volume",
|
||||
marker_color=colors,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name="AD Osc [M]",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
line=dict(width=2),
|
||||
opacity=1,
|
||||
),
|
||||
row=3,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=10, r=0, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"{ticker} AD Oscillator",
|
||||
title_x=0.3,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_adosc.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/adosc_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adosc_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Accumulation/Distribution Oscillator {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
176
bots/stocks/technical_analysis/adx.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import trend_indicators_model
|
||||
|
||||
|
||||
def adx_command(ticker="", length="14", scalar="100", drift="1", start="", end=""):
|
||||
"""Displays chart with average directional movement index [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
# pylint: disable=logging-too-many-args
|
||||
logger.debug(
|
||||
"ta-adx %s %s %s %s %s",
|
||||
ticker,
|
||||
length,
|
||||
scalar,
|
||||
drift,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if not length.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
length = float(length)
|
||||
if not scalar.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
scalar = float(scalar)
|
||||
if not drift.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
drift = float(drift)
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
df_ta = trend_indicators_model.adx(
|
||||
df_stock["High"],
|
||||
df_stock["Low"],
|
||||
df_stock["Adj Close"],
|
||||
length,
|
||||
scalar,
|
||||
drift,
|
||||
)
|
||||
|
||||
# Output Data
|
||||
leg_adx = df_ta.columns[0].replace("_", " ")
|
||||
neg_di = df_ta.columns[1].replace("_", " ")
|
||||
pos_di = df_ta.columns[2].replace("_", " ")
|
||||
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.07,
|
||||
row_width=[0.5, 0.5],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"].values,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
mode="lines",
|
||||
name=f"ADX ({leg_adx})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
opacity=1,
|
||||
line=dict(width=2),
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
mode="lines",
|
||||
name=f"+DI ({pos_di})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 1].values,
|
||||
opacity=1,
|
||||
line=dict(width=1),
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
mode="lines",
|
||||
name=f"- DI ({neg_di})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 2].values,
|
||||
opacity=1,
|
||||
line=dict(width=1),
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hline(
|
||||
y=25,
|
||||
fillcolor="grey",
|
||||
opacity=1,
|
||||
layer="below",
|
||||
line_width=3,
|
||||
line=dict(color="grey", dash="dash"),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=20, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"Average Directional Movement Index (ADX) on {ticker}",
|
||||
title_x=0.3,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_adx.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/adx_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adx_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Average-Directional-Movement-Index {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
160
bots/stocks/technical_analysis/aroon.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import trend_indicators_model
|
||||
|
||||
|
||||
def aroon_command(ticker="", length="25", scalar="100", start="", end=""):
|
||||
"""Displays chart with aroon indicator [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"ta-aroon %s %s %s %s %s",
|
||||
ticker,
|
||||
length,
|
||||
scalar,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if not length.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
length = int(length)
|
||||
if not scalar.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
scalar = float(scalar)
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
df_ta = trend_indicators_model.aroon(
|
||||
df_stock["High"], df_stock["Low"], length, scalar
|
||||
)
|
||||
|
||||
# Output Data
|
||||
aadown = df_ta.columns[0].replace("_", " ")
|
||||
aaup = df_ta.columns[1].replace("_", " ")
|
||||
aaosc = df_ta.columns[2].replace("_", " ")
|
||||
|
||||
fig = make_subplots(
|
||||
rows=3,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.07,
|
||||
row_width=[0.2, 0.2, 0.2],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"].values,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"Aroon DOWN ({aadown})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
opacity=1,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"Aroon UP ({aaup})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 1].values,
|
||||
opacity=1,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"Aroon OSC ({aaosc})",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 2].values,
|
||||
opacity=1,
|
||||
),
|
||||
row=3,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hline(
|
||||
y=50,
|
||||
fillcolor="grey",
|
||||
opacity=1,
|
||||
layer="below",
|
||||
line_width=3,
|
||||
line=dict(color="grey", dash="dash"),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=20, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"Aroon on {ticker}",
|
||||
title_x=0.5,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_aroon.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/aroon_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/aroon_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Aroon-Indicator {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
169
bots/stocks/technical_analysis/bbands.py
Normal file
@@ -0,0 +1,169 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import volatility_model
|
||||
|
||||
|
||||
def bbands_command(ticker="", length="5", n_std="2", mamode="sma", start="", end=""):
|
||||
"""Displays chart with bollinger bands [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
# pylint: disable=logging-too-many-args
|
||||
logger.debug(
|
||||
"ta-bbands %s %s %s %s %s %s",
|
||||
ticker,
|
||||
length,
|
||||
n_std,
|
||||
mamode,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
possible_ma = [
|
||||
"dema",
|
||||
"ema",
|
||||
"fwma",
|
||||
"hma",
|
||||
"linreg",
|
||||
"midpoint",
|
||||
"pwma",
|
||||
"rma",
|
||||
"sinwma",
|
||||
"sma",
|
||||
"swma",
|
||||
"t3",
|
||||
"tema",
|
||||
"trima",
|
||||
"vidya",
|
||||
"wma",
|
||||
"zlma",
|
||||
]
|
||||
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if not length.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
length = float(length)
|
||||
if not n_std.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
n_std = float(n_std)
|
||||
|
||||
if mamode not in possible_ma:
|
||||
raise Exception("Invalid ma entered")
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
|
||||
df_ta = volatility_model.bbands(df_stock["Adj Close"], length, n_std, mamode)
|
||||
|
||||
# Output Data
|
||||
bbu = df_ta.columns[2].replace("_", " ")
|
||||
bbm = df_ta.columns[1].replace("_", " ")
|
||||
bbl = df_ta.columns[0].replace("_", " ")
|
||||
|
||||
fig = go.Figure()
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"{bbu}",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 2].values,
|
||||
opacity=1,
|
||||
mode="lines",
|
||||
line_color="indigo",
|
||||
),
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"{bbl}",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
opacity=1,
|
||||
mode="lines",
|
||||
line_color="indigo",
|
||||
fill="tonexty",
|
||||
fillcolor="rgba(74, 0, 128, 0.2)",
|
||||
),
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"{bbm}",
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 1].values,
|
||||
opacity=1,
|
||||
line=dict(
|
||||
width=1.5,
|
||||
dash="dash",
|
||||
),
|
||||
),
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=f"{ticker}",
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"],
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
),
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=50, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"{ticker} Bollinger Bands ({mamode.upper()})",
|
||||
title_x=0.5,
|
||||
yaxis_title="Stock Price ($)",
|
||||
xaxis_title="Time",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_bbands.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/bbands_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/bbands_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Bollinger-Bands {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
170
bots/stocks/technical_analysis/cci.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
from bots.config_discordbot import logger
|
||||
from bots import helpers
|
||||
from gamestonk_terminal.common.technical_analysis import momentum_model
|
||||
|
||||
|
||||
def cci_command(ticker="", length="14", scalar="0.015", start="", end=""):
|
||||
"""Displays chart with commodity channel index [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"ta-cci %s %s %s %s %s",
|
||||
ticker,
|
||||
length,
|
||||
scalar,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
# pylint
|
||||
try:
|
||||
length = int(length)
|
||||
except ValueError as e:
|
||||
raise Exception("Length has to be an integer") from e
|
||||
try:
|
||||
scalar = float(scalar)
|
||||
except ValueError as e:
|
||||
raise Exception("Scalar has to be an integer") from e
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
df_ta = momentum_model.cci(
|
||||
df_stock["High"], df_stock["Low"], df_stock["Adj Close"], length, scalar
|
||||
)
|
||||
|
||||
dmin = df_ta.values.min()
|
||||
dmax = df_ta.values.max()
|
||||
# Output Data
|
||||
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.07,
|
||||
row_width=[0.5, 0.6],
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
name=ticker,
|
||||
x=df_stock.index,
|
||||
y=df_stock["Adj Close"].values,
|
||||
line=dict(color="#fdc708", width=2),
|
||||
opacity=1,
|
||||
showlegend=False,
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
showlegend=False,
|
||||
x=df_ta.index,
|
||||
y=df_ta.iloc[:, 0].values,
|
||||
opacity=1,
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hrect(
|
||||
y0=100,
|
||||
y1=dmax,
|
||||
fillcolor="red",
|
||||
opacity=0.2,
|
||||
layer="below",
|
||||
line_width=0,
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hrect(
|
||||
y0=-100,
|
||||
y1=dmin,
|
||||
fillcolor="green",
|
||||
opacity=0.2,
|
||||
layer="below",
|
||||
line_width=0,
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hline(
|
||||
y=-100,
|
||||
fillcolor="green",
|
||||
opacity=1,
|
||||
layer="below",
|
||||
line_width=3,
|
||||
line=dict(color="green", dash="dash"),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.add_hline(
|
||||
y=100,
|
||||
fillcolor="red",
|
||||
opacity=1,
|
||||
layer="below",
|
||||
line_width=3,
|
||||
line=dict(color="red", dash="dash"),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=20, t=30, b=20),
|
||||
template=cfg.PLT_TA_STYLE_TEMPLATE,
|
||||
colorway=cfg.PLT_TA_COLORWAY,
|
||||
title=f"{ticker} CCI",
|
||||
title_x=0.5,
|
||||
yaxis_title="Stock Price ($)",
|
||||
yaxis=dict(
|
||||
fixedrange=False,
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
),
|
||||
dragmode="pan",
|
||||
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
||||
)
|
||||
config = dict({"scrollZoom": True})
|
||||
imagefile = "ta_cci.png"
|
||||
|
||||
# Check if interactive settings are enabled
|
||||
plt_link = ""
|
||||
if cfg.INTERACTIVE:
|
||||
html_ran = helpers.uuid_get()
|
||||
fig.write_html(f"in/cci_{html_ran}.html", config=config)
|
||||
plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cci_{html_ran}.html)"
|
||||
|
||||
fig.update_layout(
|
||||
width=800,
|
||||
height=500,
|
||||
)
|
||||
imagefile = helpers.image_border(imagefile, fig=fig)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Commodity-Channel-Index {ticker}",
|
||||
"description": plt_link,
|
||||
"imagefile": imagefile,
|
||||
}
|
||||
86
bots/stocks/technical_analysis/cg.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
import bots.config_discordbot as cfg
|
||||
import bots.helpers
|
||||
from bots.config_discordbot import logger
|
||||
from bots.helpers import image_border
|
||||
from gamestonk_terminal.common.technical_analysis import momentum_model
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
|
||||
|
||||
def cg_command(ticker="", length="14", start="", end=""):
|
||||
"""Displays chart with centre of gravity [Yahoo Finance]"""
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
logger.debug(
|
||||
"ta-cg %s %s %s %s",
|
||||
ticker,
|
||||
length,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if start == "":
|
||||
start = datetime.now() - timedelta(days=365)
|
||||
else:
|
||||
start = datetime.strptime(start, cfg.DATE_FORMAT)
|
||||
|
||||
if end == "":
|
||||
end = datetime.now()
|
||||
else:
|
||||
end = datetime.strptime(end, cfg.DATE_FORMAT)
|
||||
|
||||
if not length.lstrip("-").isnumeric():
|
||||
raise Exception("Number has to be an integer")
|
||||
length = float(length)
|
||||
|
||||
ticker = ticker.upper()
|
||||
df_stock = bots.helpers.load(ticker, start)
|
||||
if df_stock.empty:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
# Retrieve Data
|
||||
df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)]
|
||||
df_close = df_stock["Adj Close"]
|
||||
df_close.columns = ["values"]
|
||||
df_ta = momentum_model.cg(df_close, length)
|
||||
|
||||
# Output Data
|
||||
fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
ax = axes[0]
|
||||
ax.set_title(f"{ticker} Centre of Gravity")
|
||||
ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=1)
|
||||
ax.set_xlim(df_stock.index[0], df_stock.index[-1])
|
||||
ax.set_ylabel("Share Price ($)")
|
||||
ax.grid(b=True, which="major", color="#666666", linestyle="-")
|
||||
|
||||
ax2 = axes[1]
|
||||
ax2.plot(df_ta.index, df_ta.values, "b", lw=2, label="CG")
|
||||
# shift cg 1 bar forward for signal
|
||||
signal = df_ta.values
|
||||
signal = np.roll(signal, 1)
|
||||
ax2.plot(df_ta.index, signal, "g", lw=1, label="Signal")
|
||||
ax2.set_xlim(df_stock.index[0], df_stock.index[-1])
|
||||
ax2.grid(b=True, which="major", color="#666666", linestyle="-")
|
||||
|
||||
plt.gcf().autofmt_xdate()
|
||||
fig.tight_layout(pad=1)
|
||||
plt.legend()
|
||||
imagefile = "ta_cg.png"
|
||||
plt.savefig(imagefile)
|
||||
|
||||
imagefile = image_border(imagefile)
|
||||
|
||||
return {
|
||||
"title": f"Stocks: Center-of-Gravity {ticker}",
|
||||
"imagefile": imagefile,
|
||||
}
|
||||