diff --git a/bots/cmds/dps_cmds.py b/bots/cmds/dps_cmds.py index 04f9988fa45..d11453a2f1f 100644 --- a/bots/cmds/dps_cmds.py +++ b/bots/cmds/dps_cmds.py @@ -12,15 +12,7 @@ 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", -} +from bots.common import commands_dict class DarkPoolShortsCommands(commands.Cog): @@ -57,7 +49,7 @@ class DarkPoolShortsCommands(commands.Cog): async def pos( self, inter: disnake.AppCmdInter, - sort: str = commands.Param(choices=pos_choices), + sort: str = commands.Param(choices=commands_dict.dps_pos_choices), num: int = 10, ): """Dark pool short position [Stockgrid] diff --git a/bots/cmds/economy_cmds.py b/bots/cmds/economy_cmds.py index e0fbb70a322..c4a602ebecc 100644 --- a/bots/cmds/economy_cmds.py +++ b/bots/cmds/economy_cmds.py @@ -16,34 +16,7 @@ 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", -} +from bots.common import commands_dict class EconomyCommands(commands.Cog): @@ -116,7 +89,7 @@ class EconomyCommands(commands.Cog): async def valuation( self, inter: disnake.AppCmdInter, - economy_group: str = commands.Param(choices=group), + economy_group: str = commands.Param(choices=commands_dict.econ_group), ): """Valuation of sectors, industry, country [Finviz] @@ -132,7 +105,7 @@ class EconomyCommands(commands.Cog): async def performance( self, inter: disnake.AppCmdInter, - economy_group: str = commands.Param(choices=group), + economy_group: str = commands.Param(choices=commands_dict.econ_group), ): """Performance of sectors, industry, country [Finviz] diff --git a/bots/cmds/options_cmds.py b/bots/cmds/options_cmds.py index 95a36794881..4dd3f860c3e 100644 --- a/bots/cmds/options_cmds.py +++ b/bots/cmds/options_cmds.py @@ -17,12 +17,7 @@ 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 - -vsurf_choices = { - "Volatility": "IV", - "Open Interest": "OI", - "Last Price": "LP", -} +from bots.common import commands_dict class SlashCommands(commands.Cog): @@ -268,7 +263,7 @@ class SlashCommands(commands.Cog): self, inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), - z: str = commands.Param(choices=vsurf_choices), + z: str = commands.Param(choices=commands_dict.options_vsurf_choices), ): """Display Volatility Surface diff --git a/bots/cmds/screener_cmds.py b/bots/cmds/screener_cmds.py index 21f2ff0edad..2b249431edb 100644 --- a/bots/cmds/screener_cmds.py +++ b/bots/cmds/screener_cmds.py @@ -13,108 +13,8 @@ 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 +from bots.common import commands_dict -sort = { - "overview": [ - "Ticker", - "Company", - "Sector", - "Industry", - "Country", - "Market Cap", - "P/E", - "Price", - "Change", - "Volume", - ], - "valuation": [ - "Ticker", - "Market Cap", - "P/E", - "Fwd P/E", - "PEG", - "P/S", - "P/B", - "P/C", - "P/FCF", - "EPS this Y", - "EPS next Y", - "EPS past 5Y", - "EPS next 5Y", - "Sales past 5Y", - "Price", - "Change", - "Volume", - ], - "financial": [ - "Ticker", - "Market Cap", - "Dividend", - "ROA", - "ROE", - "ROI", - "Curr R", - "Quick R", - "LTDebt/Eq", - "Debt/Eq", - "Gross M", - "Oper M", - "Profit M", - "Earnings", - "Price", - "Change", - "Volume", - ], - "ownership": [ - "Ticker", - "Market Cap", - "Outstanding", - "Float", - "Insider Own", - "Insider Trans", - "Inst Own", - "Inst Trans", - "Float Short", - "Short Ratio", - "Avg Volume", - "Price", - "Change", - "Volume", - ], - "performance": [ - "Ticker", - "Perf Week", - "Perf Month", - "Perf Quart", - "Perf Half", - "Perf Year", - "Perf YTD", - "Volatility W", - "Volatility M", - "Recom", - "Avg Volume", - "Rel Volume", - "Price", - "Change", - "Volume", - ], - "technical": [ - "Ticker", - "Beta", - "ATR", - "SMA20", - "SMA50", - "SMA200", - "52W High", - "52W Low", - "RSI", - "Price", - "Change", - "from Open", - "Gap", - "Volume", - ], -} # pylint: disable=R0912 @@ -157,7 +57,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["overview"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["overview"]), limit: int = 5, ascend: bool = False, ): @@ -179,7 +79,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["valuation"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["valuation"]), limit: int = 5, ascend: bool = False, ): @@ -201,7 +101,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["financial"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["financial"]), limit: int = 5, ascend: bool = False, ): @@ -223,7 +123,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["ownership"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["ownership"]), limit: int = 5, ascend: bool = False, ): @@ -245,7 +145,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["performance"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["performance"]), limit: int = 5, ascend: bool = False, ): @@ -267,7 +167,7 @@ class ScreenerCommands(commands.Cog): self, inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), - sort: str = commands.Param(choices=sort["technical"]), + sort: str = commands.Param(choices=commands_dict.screener_sort["technical"]), limit: int = 5, ascend: bool = False, ): diff --git a/bots/cmds/ta_cmds.py b/bots/cmds/ta_cmds.py index ed96782d9b7..ecfc8d753ca 100644 --- a/bots/cmds/ta_cmds.py +++ b/bots/cmds/ta_cmds.py @@ -29,26 +29,6 @@ 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", - "ema", - "fwma", - "hma", - "linreg", - "midpoint", - "pwma", - "rma", - "sinwma", - "sma", - "swma", - "t3", - "tema", - "trima", - "vidya", - "wma", - "zlma", -] - class TechnicalAnalysisCommands(commands.Cog): """Technical Analysis menu.""" diff --git a/bots/common/commands_dict.py b/bots/common/commands_dict.py new file mode 100644 index 00000000000..eca02974354 --- /dev/null +++ b/bots/common/commands_dict.py @@ -0,0 +1,809 @@ +import re +from typing import List + +import pandas as pd +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.stocks.candle import candle_command +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 +from bots.stocks.disc.ford import ford_command +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 +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 +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 as overview_opt_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 +from bots.stocks.screener.financial import financial_command +from bots.stocks.screener.historical import historical_command +from bots.stocks.screener.overview import overview_command as overview_screener_command +from bots.stocks.screener.ownership import ownership_command +from bots.stocks.screener.performance import ( + performance_command as performance_screener_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 as valuation_screener_command, +) +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.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 + +re_date = re.compile(r"/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/") +re_int = re.compile(r"^[1-9]\d*$") +re_float = re.compile(r"^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$") +re_name = re.compile(r"(?s).*") +re_window = re.compile(r"(?s).*") + +gov_type = ["congress", "senate", "house"] + +dps_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", +} + +econ_group = [ + "basic_materials", + "capitalization", + "communication_services", + "consumer_cyclical", + "consumer_defensive", + "country", + "energy", + "financial", + "healthcare", + "industry", + "industrials", + "real_estate", + "sector", + "technology", + "utilities", +] + +econ_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", +} + +options_vsurf_choices = { + "Volatility": "IV", + "Open Interest": "OI", + "Last Price": "LP", +} + +opt_intervals = [1, 5, 15, 30, 60, 1440] + + +screener_sort = { + "overview": [ + "Ticker", + "Company", + "Sector", + "Industry", + "Country", + "Market Cap", + "P/E", + "Price", + "Change", + "Volume", + ], + "valuation": [ + "Ticker", + "Market Cap", + "P/E", + "Fwd P/E", + "PEG", + "P/S", + "P/B", + "P/C", + "P/FCF", + "EPS this Y", + "EPS next Y", + "EPS past 5Y", + "EPS next 5Y", + "Sales past 5Y", + "Price", + "Change", + "Volume", + ], + "financial": [ + "Ticker", + "Market Cap", + "Dividend", + "ROA", + "ROE", + "ROI", + "Curr R", + "Quick R", + "LTDebt/Eq", + "Debt/Eq", + "Gross M", + "Oper M", + "Profit M", + "Earnings", + "Price", + "Change", + "Volume", + ], + "ownership": [ + "Ticker", + "Market Cap", + "Outstanding", + "Float", + "Insider Own", + "Insider Trans", + "Inst Own", + "Inst Trans", + "Float Short", + "Short Ratio", + "Avg Volume", + "Price", + "Change", + "Volume", + ], + "performance": [ + "Ticker", + "Perf Week", + "Perf Month", + "Perf Quart", + "Perf Half", + "Perf Year", + "Perf YTD", + "Volatility W", + "Volatility M", + "Recom", + "Avg Volume", + "Rel Volume", + "Price", + "Change", + "Volume", + ], + "technical": [ + "Ticker", + "Beta", + "ATR", + "SMA20", + "SMA50", + "SMA200", + "52W High", + "52W Low", + "RSI", + "Price", + "Change", + "from Open", + "Gap", + "Volume", + ], +} + +presets = [ + "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", +] + +possible_ma = [ + "dema", + "ema", + "fwma", + "hma", + "linreg", + "midpoint", + "pwma", + "rma", + "sinwma", + "sma", + "swma", + "t3", + "tema", + "trima", + "vidya", + "wma", + "zlma", +] + + +def get_tickers() -> List[str]: + col_list = ["Name"] + df = pd.read_csv("files/tickers.csv", usecols=col_list) + df = df["Name"] + return df.tolist() + + +tickers = get_tickers() + +commands = { + "dd-analyst": { + "function": analyst_command, + "required": {"ticker": tickers}, + }, + "dd-pt": { + "function": pt_command, + "required": {"ticker": tickers}, + "optional": {"raw": [True, False], "start": re_date}, + }, + "dd-est": {"function": est_command, "required": {"ticker": tickers}}, + "dd-sec": {"function": sec_command, "required": {"ticker": tickers}}, + "dd-supplier": { + "function": supplier_command, + "required": {"ticker": tickers}, + }, + "dd-customer": { + "function": customer_command, + "required": {"ticker": tickers}, + }, + "dd-arktrades": { + "function": arktrades_command, + "required": { + "ticker": tickers, + }, + "optional": {"num": re_int}, + }, + "dps-shorted": {"function": shorted_command, "optional": re_int}, + "dps-hsi": {"function": hsi_command, "optional": {"num": re_int}}, + "dps-pos": { + "function": pos_command, + "required": {"sort": dps_pos_choices.values()}, + "optional": {"num": re_int}, + }, + "dps-sidtc": { + "function": sidtc_command, + "required": {"sort": ["float", "dtc", "si"]}, + "optional": {"num": re_int}, + }, + "dps-ftd": { + "function": ftd_command, + "required": {"ticker": tickers}, + "optional": {"start": re_date, "end": re_date}, + }, + "dps-dpotc": { + "function": dpotc_command, + "required": {"ticker": tickers}, + }, + "dps-spos": {"function": spos_command, "required": {"ticker": tickers}}, + "dps-psi": {"function": psi_command, "required": {"ticker": tickers}}, + "econ-feargreed": { + "function": feargreed_command, + }, + "econ-overview": { + "function": overview_command, + }, + "econ-indices": { + "function": indices_command, + }, + "econ-futures": { + "function": futures_command, + }, + "econ-usbonds": { + "function": usbonds_command, + }, + "econ-glbonds": { + "function": glbonds_command, + }, + "econ-energy": { + "function": energy_command, + }, + "econ-metals": { + "function": metals_command, + }, + "econ-meats": { + "function": meats_command, + }, + "econ-grains": { + "function": grains_command, + }, + "econ-softs": { + "function": softs_command, + }, + "econ-currencies": { + "function": currencies_command, + }, + "econ-valuation": { + "function": valuation_command, + "required": {"economy_group": econ_group}, + }, + "econ-performance": { + "function": performance_command, + "required": {"economy_group": econ_group}, + }, + "gov-lasttrades": { + "function": lasttrades_command, + "required": {"gov_type": gov_type}, + "optional": {"past_days": re_int, "representative": re_name}, + }, + "gov-topbuys": { + "function": topbuys_command, + "required": {"gov_type": gov_type}, + "optional": { + "past_transactions_months": re_int, + "num": re_int, + "raw": [True, False], + }, + }, + "gov-topsells": { + "function": topsells_command, + "required": {"gov_type": gov_type}, + "optional": { + "past_transactions_months": re_int, + "num": re_int, + "raw": [True, False], + }, + }, + "gov-lastcontracts": { + "function": lastcontracts_command, + "optional": {"past_transactions_days": re_int, "num": re_int}, + }, + "gov-qtrcontracts": { + "function": qtrcontracts_command, + "required": {"analysis": ["total", "upmom", "downmom"]}, + "optional": { + "num": re_int, + }, + }, + "gov-toplobbying": { + "function": toplobbying_command, + "optional": {"num": re_int, "raw": [True, False]}, + }, + "gov-gtrades": { + "function": gtrades_command, + "required": { + "ticker": tickers, + "gov_type": gov_type, + }, + "optional": {"past_transactions_months": re_int, "raw": [True, False]}, + }, + "gov-contracts": { + "function": contracts_command, + "required": { + "ticker": tickers, + "past_transaction_days": re_int, + "raw": [True, False], + }, + }, + "gov-histcont": {"function": histcont_command, "required": {"ticker": tickers}}, + "gov-lobbying": { + "function": lobbying_command, + "required": {"ticker": tickers}, + "optional": { + "num": re_int, + }, + }, + "opt-chain": { + "function": chain_command, + "required": { + "ticker": tickers, + "expiry": re_date, + "opt_type": ["Calls", "Puts"], + }, + "optional": {"min_sp": re_float, "max_sp": re_float}, + }, + "opt-oi": { + "function": oi_command, + "required": {"ticker": tickers, "expiry": re_date}, + "optional": {"min_sp": re_float, "max_sp": re_float}, + }, + "opt-iv": { + "function": iv_command, + "required": {"ticker": tickers}, + }, + "q": {"function": quote_command, "required": {"ticker": tickers}}, + "disc-ford": { + "function": ford_command, + }, + "opt-unu": { + "function": unu_command, + }, + "ins-last": { + "function": lins_command, + "required": {"ticker": tickers}, + "optional": {"num": re_int}, + }, + "cc": { + "function": candle_command, + "required": {"ticker": tickers, "interval": opt_intervals}, + "optional": {"past_days": re_int, "start": re_date, "end": re_date}, + }, + "opt-overview": { + "function": overview_opt_command, + "required": {"ticker": tickers, "expiry": re_date}, + "optional": { + "min_sp": re_float, + "max_sp": re_float, + }, + }, + "opt-vol": { + "function": vol_command, + "required": {"ticker": tickers, "expiry": re_date}, + }, + "opt-vsurf": { + "function": vsurf_command, + "required": {"ticker": tickers, "z": options_vsurf_choices.values()}, + }, + "opt-hist": { + "function": hist_command, + "required": { + "ticker": tickers, + "expiry": re_date, + "strike": re_float, + "opt_type": ["Calls", "Puts"], + "greek": ["iv", "gamma", "delta", "theta", "rho", "vega"], + }, + }, + "opt-cc-hist": { + "function": cc_hist_command, + "required": { + "ticker": tickers, + "expiry": re_date, + "strike": re_float, + "opt_type": ["Calls", "Puts"], + }, + }, + "scr-presets_default": { + "function": presets_default_command, + }, + "scr-presets_custom": { + "function": presets_custom_command, + }, + "scr-historical": { + "function": historical_command, + "required": {"signal": signals}, + "optional": {"start": re_date}, + }, + "scr-overview": { + "function": overview_screener_command, + "required": {"preset": presets, "sort": screener_sort["overview"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "scr-valuation": { + "function": valuation_screener_command, + "required": {"preset": presets, "sort": screener_sort["valuation"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "scr-financial": { + "function": financial_command, + "required": {"preset": presets, "sort": screener_sort["financial"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "scr-ownership": { + "function": ownership_command, + "required": {"preset": presets, "sort": screener_sort["ownership"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "scr-performance": { + "function": performance_screener_command, + "required": {"preset": presets, "sort": screener_sort["performance"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "scr-technical": { + "function": technical_command, + "required": {"preset": presets, "sort": screener_sort["technical"]}, + "optional": {"limit": re_int, "ascend": [True, False]}, + }, + "ta-ema": { + "function": ema_command, + "required": {"ticker": tickers}, + "optional": { + "window": re_window, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-sma": { + "function": sma_command, + "required": {"ticker": tickers}, + "optional": { + "window": re_window, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-wma": { + "function": wma_command, + "required": {"ticker": tickers}, + "optional": { + "window": re_window, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-hma": { + "function": hma_command, + "required": {"ticker": tickers}, + "optional": { + "window": re_window, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-zlma": { + "function": zlma_command, + "required": {"ticker": tickers}, + "optional": { + "window": re_window, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-cci": { + "function": cci_command, + "required": {"ticker": tickers}, + "optional": { + "length": re_int, + "scalar": re_float, + "start": re_date, + "end": re_date, + }, + }, + "ta-macd": { + "function": macd_command, + "required": {"ticker": tickers}, + "optional": { + "length": re_int, + "scalar": re_float, + "start": re_date, + "end": re_date, + }, + }, + "ta-rsi": { + "function": rsi_command, + "required": {"ticker": tickers}, + "optional": { + "length": re_int, + "scalar": re_float, + "drift": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-stoch": { + "function": stoch_command, + "required": {"ticker": tickers}, + "optional": { + "fast_k": re_int, + "slow_k": re_int, + "slow_d": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-fisher": { + "function": fisher_command, + "required": {"ticker": tickers}, + "optional": {"length": re_int, "start": re_date, "end": re_date}, + }, + "ta-cg": { + "function": fisher_command, + "required": {"ticker": tickers}, + "optional": {"length": re_int, "start": re_date, "end": re_date}, + }, + "ta-adx": { + "function": adx_command, + "required": {"ticker": tickers}, + "optional": { + "length": re_int, + "scalar": re_int, + "drift": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-aroon": { + "function": aroon_command, + "required": {"ticker": tickers}, + "optional": { + "length": re_int, + "scalar": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-bbands": { + "function": bbands_command, + "required": { + "ticker": tickers, + "mamode": ["ema", "sma", "wma", "hma", "zlma"], + }, + "optional": {"length": re_int, "std": re_int, "start": re_date, "end": re_date}, + }, + "ta-donchian": { + "function": donchian_command, + "required": {"ticker": tickers}, + "optional": { + "upper_length": re_int, + "lower_length": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-kc": { + "function": kc_command, + "required": { + "ticker": tickers, + "mamode": ["ema", "sma", "wma", "hma", "zlma"], + }, + "optional": { + "length": re_int, + "scalar": re_int, + "offset": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-ad": { + "function": ad_command, + "required": {"ticker": tickers}, + "optional": {"is_open": ["True", "False"], "start": re_date, "end": re_date}, + }, + "ta-adosc": { + "function": adosc_command, + "required": { + "ticker": tickers, + }, + "optional": { + "is_open": ["True", "False"], + "fast": re_int, + "slow": re_int, + "start": re_date, + "end": re_date, + }, + }, + "ta-obv": { + "function": obv_command, + "required": {"ticker": tickers}, + "optional": {"start": re_date, "end": re_date}, + }, + "ta-fib": { + "function": fib_command, + "required": {"ticker": tickers}, + "optional": {"start": re_date, "end": re_date}, + }, + "ta-view": {"function": view_command, "required": {"ticker": tickers}}, + "ta-summary": {"function": summary_command, "required": {"ticker": tickers}}, + "ta-recom": {"function": recom_command, "required": {"ticker": tickers}}, +} diff --git a/bots/economy/indices.py b/bots/economy/indices.py index 56e92bc978a..f9c5a71d1dd 100644 --- a/bots/economy/indices.py +++ b/bots/economy/indices.py @@ -35,6 +35,7 @@ def indices_command(): df = df[ [ + " ", "Price", "Chg", "%Chg", diff --git a/bots/economy/overview.py b/bots/economy/overview.py index 61aa8b08d64..fbbef56869a 100644 --- a/bots/economy/overview.py +++ b/bots/economy/overview.py @@ -30,6 +30,7 @@ def overview_command(): df = df[ [ + " ", "Price", "Chg", "%Chg", diff --git a/bots/groupme/groupme_helpers.py b/bots/groupme/groupme_helpers.py new file mode 100644 index 00000000000..68399ca87dc --- /dev/null +++ b/bots/groupme/groupme_helpers.py @@ -0,0 +1,75 @@ +"""Groupme""" +__docformat__ = "numpy" + +from typing import Optional +import io +import json +import os +import pathlib +import urllib.request as urllib + +import requests +from dotenv import load_dotenv + +load_dotenv() + +base = "https://api.groupme.com/v3" +TOKEN = os.getenv("GROUPME_TOKEN") +end = f"?token={TOKEN}" + +group_to_bot = { + os.getenv("TEST_GROUP_ID"): os.getenv("TEST_GROUP_BOT"), + os.getenv("MAIN_GROUP_ID"): os.getenv("MAIN_GROUP_BOT"), +} + + +def shorten_message(message: Optional[str]) -> Optional[str]: + # TODO: make the cutoff algorithm better + if message is None: + return message + if len(message) > 990: + return f"{message[:990]}..." + return message + + +def upload_image(image: str, local: bool) -> requests.Response: + url = "https://image.groupme.com/pictures" + headers = { + "Content-Type": "image/jpeg", + "X-Access-Token": os.getenv("X_ACCESS_TOKEN"), + } + if local: + path_string = pathlib.Path(__file__).parent.parent.resolve() + path = os.path.join(path_string, image) + return requests.post(url, data=open(path, "rb").read(), headers=headers) + with urllib.urlopen(image) as data: + return requests.post(url, data=io.BytesIO(data.read()), headers=headers) + + +def send_message(message: str, group_id: str) -> requests.Response: + mid = "/bots/post" + bot_id = group_to_bot[group_id] + cleaned = shorten_message(message) + return requests.post(url=f"{base+mid}?bot_id={bot_id}&text={cleaned}") + + +def send_image( + image: str, group_id: str, text: str = None, local: bool = False +) -> requests.Response: + if "http" in image: + response = upload_image(image, False) + else: + response = upload_image(image, True) + response_json = response.json() + image_url = response_json["payload"]["picture_url"] + mid = "/bots/post" + bot_id = group_to_bot[group_id] + text = shorten_message(text) + data = { + "bot_id": bot_id, + "text": text, + "attachments": [{"type": "image", "url": image_url}], + } + if "http" not in image: + os.remove(image) + return requests.post(base + mid + end, data=json.dumps(data)) diff --git a/bots/groupme/run_groupme.py b/bots/groupme/run_groupme.py new file mode 100644 index 00000000000..e56b22a1172 --- /dev/null +++ b/bots/groupme/run_groupme.py @@ -0,0 +1,139 @@ +import json +from typing import List, Pattern, Dict, Any, Set, Union + +from bots.common.commands_dict import commands +from bots.groupme.groupme_helpers import send_message +from bots.helpers import ShowView + + +def get_syntax(selected: Dict[str, Any], cmd: str) -> str: + """Returns the syntax for a given command + + Parameters + ---------- + selected : Dict[str, Any] + The command object + cmd : str + The command that was attempted + + Returns + --------- + syntax : str + The syntax for the given command + """ + + syntax = f"{cmd}/" + syntax += "/".join(selected.get("required", [])) + return syntax + + +def get_arguments(selected: Dict[str, Any], req_name: str, group_id: str) -> None: + """Returns the arguments for a given command + + Parameters + ---------- + selected : Dict[str, Any] + The command object + req_name : str + The name of the requirement + group_id : str + The groupme chat id + """ + + if req_name == "ticker": + send_message("Please give a listed ticker", group_id) + elif req_name == "past_transactions_days": + send_message("Please give the number of days as an integer", group_id) + elif req_name == "raw": + send_message("Please type true or false", group_id) + else: + select = [str(x) for x in selected["required"].get(req_name, [])] + selections = ", ".join(select) + if len(selections) < 990: + selections = f"{selections[:990]}" + send_message(f"Options: {selections}", group_id) + + +def send_options(name: str, items: Union[List[Any], Set[Any]], group_id: str) -> None: + """Sends the options for a user + + Parameters + ---------- + name : str + The name of the section + items : List[str] + The items the user can select from + group_id : str + The groupme chat id + """ + message = name + clean = list(items) + clean.sort() + message += ", ".join(list(clean)) + send_message(message, group_id) + + +def handle_groupme(request) -> bool: + """Handles groupme bot inputs + + Parameters + ---------- + request : Request + The request object provided by FASTAPI + + Returns + ---------- + success : bool + Whether the response was sent successfully + """ + + req = json.loads(request.decode("utf-8")) + text = req.get("text").strip().lower() + group_id = req.get("group_id").strip() + + if text[0] == "!": + cmd = text[1:] + full_cmd = cmd.split("/") + group = full_cmd[0].split("-")[0] + parents = {x.split("-")[0] for x in commands} + if group in parents: + if full_cmd[0] in commands: + selected = commands[full_cmd[0]] + if len(full_cmd) != len(selected.get("required", [])) + 1: + syntax = get_syntax(selected, full_cmd[0]) + send_message(f"Required syntax: {syntax}", group_id) + return False + other_args = {} + for i, val in enumerate(full_cmd[1:]): + req_name = list(selected.get("required", {}).keys())[i] + required = selected.get("required", [])[req_name] + if isinstance(required, List) and required != [True, False]: + required = [str(x) for x in required] + if isinstance(val, str) and req_name in ["ticker"]: + val = val.upper() + elif isinstance(val, str) and req_name == "raw": + val = bool(val) + if (isinstance(required, List) and val not in required) or ( + isinstance(required, Pattern) and not required.match(val) + ): + syntax = get_syntax(selected, full_cmd[0]) + send_message( + f"{syntax}\nInvalid argument for: {req_name}", group_id + ) + get_arguments(selected, req_name, group_id) + return False + other_args[req_name] = val + func = selected["function"] + ShowView().groupme(func, group_id, cmd, **other_args) + return True + else: + show_cmds = [] + for command in commands: + if group == command[: len(group)]: + show_cmds.append(command) + send_options("Valid commands: ", show_cmds, group_id) + return False + else: + send_options("Valid categories: ", parents, group_id) + return False + return False diff --git a/bots/helpers.py b/bots/helpers.py index 32cfd9a5ed2..d5ea5e300bc 100644 --- a/bots/helpers.py +++ b/bots/helpers.py @@ -1,4 +1,5 @@ import os +from typing import List import uuid import df2img @@ -9,6 +10,7 @@ from numpy.core.fromnumeric import transpose from PIL import Image import bots.config_discordbot as cfg +from bots.groupme.groupme_helpers import send_image, send_message presets_custom = [ "potential_reversals", @@ -201,7 +203,9 @@ def image_border(file, **kwargs): if "fig" in kwargs: fig = kwargs["fig"] fig.write_image(imagefile) - img = Image.open(file) + img = Image.open(imagefile) + else: + img = Image.open(file) im_bg = Image.open(cfg.IMG_BG) h = img.height + 240 w = img.width + 520 @@ -218,7 +222,6 @@ def image_border(file, **kwargs): image = Image.open(file) image = autocrop_image(image, 0) image.save(imagefile, "PNG", quality=100) - os.remove(file) return imagefile @@ -269,3 +272,22 @@ class ShowView: ) await inter.send(embed=embed, delete_after=30.0) + + def groupme(self, func, group_id, name, *args, **kwargs): + data = func(*args, **kwargs) + if "imagefile" in data: + send_image(data["imagefile"], group_id, data.get("description", ""), True) + elif "embeds_img" in data: + send_image( + data["embeds_img"][0], group_id, data.get("description", ""), True + ) + elif "description" in data: + title = data.get("title", "") + # TODO: Allow navigation through pages + description = data.get("description") + if isinstance(description, List): + clean_desc = description[0].replace("Page ", "") + else: + clean_desc = description.replace("Page ", "") + message = f"{title}\n{clean_desc}" + send_message(message, group_id) diff --git a/bots/run_discordbot.py b/bots/run_discordbot.py index 8bb974a48f4..93c79d9d230 100644 --- a/bots/run_discordbot.py +++ b/bots/run_discordbot.py @@ -8,17 +8,26 @@ import config_discordbot as cfg import disnake from config_discordbot import logger from disnake.ext import commands -from fastapi import FastAPI +from fastapi import FastAPI, Request + +from bots.groupme.run_groupme import handle_groupme app = FastAPI() -# For next update @app.get("/") async def read_root(): return {"Hello": str(gst_bot.user)} +@app.post("/") +async def write_root(request: Request): + # TODO: Make this work for more bots + req_info = await request.body() + value = handle_groupme(req_info) + return {"Worked": value} + + activity = disnake.Activity( type=disnake.ActivityType.watching, name="Gamestonk Terminal: https://github.com/GamestonkTerminal/GamestonkTerminal", diff --git a/bots/stocks/government/contracts.py b/bots/stocks/government/contracts.py index 2aea0894a9f..dba827081c2 100644 --- a/bots/stocks/government/contracts.py +++ b/bots/stocks/government/contracts.py @@ -1,8 +1,9 @@ -import pandas as pd -from matplotlib import pyplot as plt +from typing import Union -import bots.config_discordbot as cfg +from matplotlib import pyplot as plt +import pandas as pd from bots.config_discordbot import logger +import bots.config_discordbot as cfg from bots.helpers import image_border from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal.helper_funcs import plot_autoscale @@ -10,9 +11,10 @@ from gamestonk_terminal.stocks.government import quiverquant_model def contracts_command( - ticker: str = "", past_transaction_days: int = 10, raw: bool = False + ticker: str = "", past_transaction_days: Union[int, str] = 10, raw: bool = False ): """Displays contracts associated with tickers [quiverquant.com]""" + past_transaction_days = int(past_transaction_days) # Debug user input if cfg.DEBUG: logger.debug("gov-contracts %s %s %s", ticker, past_transaction_days, raw) @@ -24,7 +26,10 @@ def contracts_command( df_contracts = quiverquant_model.get_government_trading("contracts", ticker) if df_contracts.empty: - raise Exception("No government contracts found") + return { + "title": f"Stocks: [quiverquant.com] Contracts by {ticker}", + "description": f"{ticker} does not have any contracts", + } # Output Data df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]).dt.date diff --git a/bots/stocks/screener/financial.py b/bots/stocks/screener/financial.py index d079bc9bf2a..240c7ea2989 100644 --- a/bots/stocks/screener/financial.py +++ b/bots/stocks/screener/financial.py @@ -115,7 +115,7 @@ def financial_command(preset="template", sort="", limit: int = 5, ascend: bool = ) ) reports.append(f"{description}") - + print(df_screen) return { "view": Menu, "title": title, diff --git a/gamestonk_terminal/forex/av_view.py b/gamestonk_terminal/forex/av_view.py index 04b65b7c4b5..e5be9700e18 100644 --- a/gamestonk_terminal/forex/av_view.py +++ b/gamestonk_terminal/forex/av_view.py @@ -87,7 +87,7 @@ def display_candle( candle_chart_kwargs["figratio"] = (10, 7) candle_chart_kwargs["figscale"] = 1.10 candle_chart_kwargs["figsize"] = plot_autoscale() - fig, _ = mpf.plot(data, **candle_chart_kwargs) + fig, ax = mpf.plot(data, **candle_chart_kwargs) fig.suptitle( f"{from_symbol}/{to_symbol}", x=0.055, @@ -95,6 +95,7 @@ def display_candle( horizontalalignment="left", ) theme.visualize_output(force_tight_layout=False) + ax[0].legend() else: if len(external_axes) != 1: logger.error("Expected list of 1 axis items.") diff --git a/gamestonk_terminal/stocks/.stocks_controller.py.swp b/gamestonk_terminal/stocks/.stocks_controller.py.swp deleted file mode 100644 index c9901cc5afe..00000000000 Binary files a/gamestonk_terminal/stocks/.stocks_controller.py.swp and /dev/null differ