diff --git a/exports/discovery/.gitkeep b/exports/discovery/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/exports/due_diligence/.gitkeep b/exports/due_diligence/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/gamestonk_terminal/common/behavioural_analysis/sentimentinvestor_view.py b/gamestonk_terminal/common/behavioural_analysis/sentimentinvestor_view.py index 9095057791d..0c38b553b0b 100644 --- a/gamestonk_terminal/common/behavioural_analysis/sentimentinvestor_view.py +++ b/gamestonk_terminal/common/behavioural_analysis/sentimentinvestor_view.py @@ -7,7 +7,7 @@ import os import statistics import textwrap import time -from typing import Union, Optional, List, Tuple +from typing import Union, Optional, List, Tuple, Any import matplotlib.dates as mdates import pandas as pd @@ -35,6 +35,83 @@ pd.plotting.register_matplotlib_converters() # suppress warning messages for a clean interface logging.getLogger().setLevel(logging.CRITICAL) +__command_descriptions = { + "popular": f""" + The {Style.BRIGHT}popular{Style.RESET_ALL} command prints the stocks with highest Average Hype Index right now. + + {Style.BRIGHT}AHI (Absolute Hype Index){Style.RESET_ALL} + --- + AHI is a measure of how much people are talking about a stock on social media. + It is calculated by dividing the total number of mentions for the chosen stock + on a social network by the mean number of mentions any stock receives on that + social medium. + + === + + {Style.BRIGHT}Sentiment Investor{Style.RESET_ALL} analyzes data from four major social media platforms to + generate hourly metrics on over 2,000 stocks. Sentiment provides volume and + sentiment metrics powered by proprietary NLP models. + """, + "emerging": f""" + The {Style.BRIGHT}emerging{Style.RESET_ALL} command prints the stocks with highest Relative Hype Index right now. + + {Style.BRIGHT}RHI (Relative Hype Index){Style.RESET_ALL} + --- + RHI is a measure of whether people are talking about a stock more or less than + usual, calculated by dividing the mean AHI for the past day by the mean AHI for + for the past week for that stock. + + === + + {Style.BRIGHT}Sentiment Investor{Style.RESET_ALL} analyzes data from four major social media platforms to + generate hourly metrics on over 2,000 stocks. Sentiment provides volume and + sentiment metrics powered by proprietary NLP models. + """, +} + + +def sort_sentiment(metric: str, other_args: List[str], command_name: str) -> None: + parser = argparse.ArgumentParser( + add_help=False, + prog=command_name, + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent(__command_descriptions[command_name]), + ) + + parser.add_argument( + "-l", + "--limit", + action="store", + dest="limit", + type=int, + default=10, + help="the maximum number of stocks to retrieve", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + data = sentipy.sort(metric, ns_parser.limit) + + table: List[List[Any]] = [] + for index, stock in enumerate(data): + if not hasattr(stock, "symbol") or not hasattr(stock, metric): + logging.warning("data for stock %s is incomplete, ignoring", index + 1) + table.append([]) + else: + table.append([index + 1, stock.symbol, stock.__getattribute__(metric)]) + + print( + tabulate.tabulate(table, headers=["Rank", "Ticker", metric], floatfmt=".3f") + ) + print("") + + except Exception as e: + logging.error(e) + print(e, "\n") + @dataclasses.dataclass class _Boundary: diff --git a/gamestonk_terminal/stocks/behavioural_analysis/ba_controller.py b/gamestonk_terminal/stocks/behavioural_analysis/ba_controller.py index cb3b9850852..1fbad080a33 100644 --- a/gamestonk_terminal/stocks/behavioural_analysis/ba_controller.py +++ b/gamestonk_terminal/stocks/behavioural_analysis/ba_controller.py @@ -50,6 +50,9 @@ class BehaviouralAnalysisController: "metrics", "social", "historical", + "emerging", + "popular", + "popularsi", ] def __init__(self, ticker: str, start: datetime): @@ -104,6 +107,10 @@ class BehaviouralAnalysisController: print(" queries top related queries with this stock") print(" rise top rising related queries with stock") print("SentimentInvestor:") + print(" popularsi show most popular stocks on social media right now") + print( + " emerging show stocks that are being talked about more than usual" + ) print(" metrics core social sentiment metrics for this stock") print(" social social media figures for stock popularity") print(" historical plot the past week of data for a selected metric") @@ -264,6 +271,14 @@ class BehaviouralAnalysisController: """Process historical command""" sentimentinvestor_view.historical(self.ticker, other_args) + def call_popularsi(self, other_args: List[str]): + """Process popular command""" + sentimentinvestor_view.sort_sentiment("AHI", other_args, "popular") + + def call_emerging(self, other_args: List[str]): + """Process emerging command""" + sentimentinvestor_view.sort_sentiment("RHI", other_args, "emerging") + def menu(ticker: str, start: datetime): """Behavioural Analysis Menu""" diff --git a/gamestonk_terminal/stocks/discovery/ark_model.py b/gamestonk_terminal/stocks/discovery/ark_model.py index 39f2ceab213..5efac3b0fde 100644 --- a/gamestonk_terminal/stocks/discovery/ark_model.py +++ b/gamestonk_terminal/stocks/discovery/ark_model.py @@ -22,7 +22,6 @@ def get_ark_orders() -> DataFrame: ARK orders data frame with the following columns: ticker, date, shares, weight, fund, direction """ - url_orders = "https://cathiesark.com/ark-funds-combined/trades" raw_page = requests.get(url_orders, headers={"User-Agent": get_user_agent()}).text diff --git a/gamestonk_terminal/stocks/discovery/ark_view.py b/gamestonk_terminal/stocks/discovery/ark_view.py index 2e413573489..ce65e4f5f6a 100644 --- a/gamestonk_terminal/stocks/discovery/ark_view.py +++ b/gamestonk_terminal/stocks/discovery/ark_view.py @@ -1,18 +1,13 @@ """ ARK View """ __docformat__ = "numpy" -import argparse -from typing import List -from colorama import Fore, Style +import os import pandas as pd +from tabulate import tabulate +from colorama import Fore, Style -from gamestonk_terminal.helper_funcs import ( - check_positive, - patch_pandas_text_adjustment, - parse_known_args_and_warn, -) from gamestonk_terminal import feature_flags as gtff - +from gamestonk_terminal.helper_funcs import export_data from gamestonk_terminal.stocks.discovery import ark_model @@ -40,64 +35,45 @@ def direction_color_red_green(val: str) -> str: return ret -def ark_orders_view(other_args: List[str]): +def ark_orders_view(num: int, export: str): """Prints a table of the last N ARK Orders Parameters ---------- - other_args : List[str] - argparse other args - ["-n", "10"] + num: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="ARK Orders", - description=""" - Orders by ARK Investment Management LLC - https://ark-funds.com/. [Source: https://cathiesark.com] - """, + df_orders = ark_model.get_ark_orders() + + if df_orders.empty: + print("The ARK orders aren't available at the moment.\n") + return + + pd.set_option("mode.chained_assignment", None) + df_orders = ark_model.add_order_total(df_orders.head(num)) + + if gtff.USE_COLOR: + df_orders["direction"] = df_orders["direction"].apply(direction_color_red_green) + + # df_orders["link"] = "https://finviz.com/quote.ashx?t=" + df_orders["ticker"] + + print("Orders by ARK Investment Management LLC") + print( + tabulate( + df_orders, + headers=df_orders.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=20, - help="Last N orders.", + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "arkord", + df_orders, ) - - try: - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_orders = ark_model.get_ark_orders() - - if df_orders.empty: - print("The ARK orders aren't available at the moment.\n") - return - - pd.set_option("mode.chained_assignment", None) - df_orders = ark_model.add_order_total(df_orders.head(ns_parser.n_num)) - - if gtff.USE_COLOR: - df_orders["direction"] = df_orders["direction"].apply( - direction_color_red_green - ) - - patch_pandas_text_adjustment() - - df_orders["link"] = "https://finviz.com/quote.ashx?t=" + df_orders["ticker"] - - pd.set_option("display.max_colwidth", None) - pd.set_option("display.max_rows", None) - pd.set_option("display.float_format", "{:,.2f}".format) - print("") - print("Orders by ARK Investment Management LLC") - print("") - print(df_orders.to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/disc_controller.py b/gamestonk_terminal/stocks/discovery/disc_controller.py index 9cd42da9f74..71d8b667cf3 100644 --- a/gamestonk_terminal/stocks/discovery/disc_controller.py +++ b/gamestonk_terminal/stocks/discovery/disc_controller.py @@ -4,24 +4,28 @@ __docformat__ = "numpy" import argparse import os from typing import List +from datetime import datetime from matplotlib import pyplot as plt from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal.menu import session +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + check_non_negative, + check_positive, + valid_date, + check_int_range, +) from gamestonk_terminal.stocks.discovery import ( ark_view, fidelity_view, seeking_alpha_view, - short_interest_view, - simply_wallst_view, - spachero_view, - unusual_whales_view, - yahoo_finance_view, + shortinterest_view, + yahoofinance_view, finra_ats_view, finnhub_view, stockgrid_view, - sentimentinvestor_view, ) @@ -35,26 +39,27 @@ class DiscoveryController: "help", "q", "quit", - "ipo", + ] + + CHOICES_COMMANDS = [ + "pipo", + "fipo", "gainers", "losers", - "orders", - "ark_orders", - "up_earnings", - "high_short", - "low_float", - "simply_wallst", - "spachero", - "uwhales", + "ford", + "arkord", + "upcoming", "latest", "trending", + "highshort", + "lowfloat", "darkpool", "darkshort", "shortvol", - "popular", - "emerging", ] + CHOICES += CHOICES_COMMANDS + def __init__(self): """Constructor""" self.disc_parser = argparse.ArgumentParser(add_help=False, prog="disc") @@ -66,46 +71,38 @@ class DiscoveryController: @staticmethod def print_help(): """Print help""" - print( - "https://github.com/GamestonkTerminal/GamestonkTerminal/tree/main/gamestonk_terminal/stocks/discovery" - ) - print("\nDiscovery Mode:") - print(" cls clear screen") - print(" ?/help show this discovery menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" ipo past and future IPOs [Finnhub]") - print(" gainers show latest top gainers [Yahoo Finance]") - print(" losers show latest top losers [Yahoo Finance]") - print(" orders orders by Fidelity Customers [Fidelity]") - print( - " ark_orders orders by ARK Investment Management LLC [www.cathiesark.com]" - ) - print(" up_earnings upcoming earnings release dates [Seeking Alpha]") - print( - " high_short show top high short interest stocks of over 20% ratio [www.highshortinterest.com]" - ) - print( - " low_float show low float stocks under 10M shares float [www.lowfloat.com]" - ) - print(" simply_wallst Simply Wall St. research data [Simply Wall St.]") - print(" spachero great website for SPACs research [SpacHero]") - print(" uwhales good website for SPACs research [UnusualWhales]") - print(" latest latest news [Seeking Alpha]") - print(" trending trending news [Seeking Alpha]") - print( - " darkpool promising tickers based on dark pool shares regression [FINRA]" - ) - print(" darkshort dark pool short position [Stockgrid.io]") - print(" shortvol short interest and days to cover [Stockgrid.io]") - print( - " popular show most popular stocks on social media right now [SentimentInvestor.com]" - ) - print( - " emerging show stocks that are being talked about more than usual [SentimentInvestor.com]" - ) - print("") + help_text = """https://github.com/GamestonkTerminal/GamestonkTerminal/tree/main/gamestonk_terminal/stocks/discovery + +Discovery: + cls clear screen") + ?/help show this menu again") + q quit this menu, and shows back to main menu") + quit quit to abandon program") + +Finnhub: + pipo past IPOs dates + fipo future IPOs dates +Yahoo Finance: + gainers show latest top gainers + losers show latest top losers +Fidelity: + ford orders by Fidelity Customers +cathiesark.com: + arkord orders by ARK Investment Management LLC +Seeking Alpha: + upcoming upcoming earnings release dates + latest latest news + trending trending news +shortinterest.com + highshort show top high short interest stocks of over 20% ratio + lowfloat show low float stocks under 10M shares float +FINRA: + darkpool promising tickers based on dark pool shares regression +Stockgrid: + darkshort dark pool short position + shortvol short interest and days to cover +""" + print(help_text) def switch(self, an_input: str): """Process and dispatch input @@ -150,57 +147,534 @@ class DiscoveryController: """Process Quit command - quit the program""" return True - def call_ipo(self, other_args: List[str]): - """Process ipo command""" - finnhub_view.ipo_calendar(other_args) + def call_pipo(self, other_args: List[str]): + """Process pipo command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="pipo", + description=""" + Past IPOs dates. [Source: https://finnhub.io] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="num", + type=check_non_negative, + default=5, + help="Number of past days to look for IPOs.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + finnhub_view.past_ipo( + num_days_behind=ns_parser.num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_fipo(self, other_args: List[str]): + """Process fipo command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="fipo", + description=""" + Future IPOs dates. [Source: https://finnhub.io] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="num", + type=check_non_negative, + default=5, + help="Number of future days to look for IPOs.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + finnhub_view.future_ipo( + num_days_ahead=ns_parser.num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_gainers(self, other_args: List[str]): """Process gainers command""" - yahoo_finance_view.gainers_view(other_args) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="gainers", + description="Print up to 25 top ticker gainers. [Source: Yahoo Finance]", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="num", + type=check_int_range(1, 25), + default=5, + help="Number of the top gainers stocks to retrieve.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + yahoofinance_view.display_gainers( + num_stocks=ns_parser.num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_losers(self, other_args: List[str]): """Process losers command""" - yahoo_finance_view.losers_view(other_args) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="losers", + description="Print up to 25 top ticker losers. [Source: Yahoo Finance]", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="num", + type=check_int_range(1, 25), + default=5, + help="Number of the top losers stocks to retrieve.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") - def call_orders(self, other_args: List[str]): - """Process orders command""" - fidelity_view.orders_view(other_args) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return - def call_ark_orders(self, other_args: List[str]): - """Process ark_orders command""" - ark_view.ark_orders_view(other_args) + yahoofinance_view.display_losers( + num_stocks=ns_parser.num, + export=ns_parser.export, + ) - def call_up_earnings(self, other_args: List[str]): - """Process up_earnings command""" - seeking_alpha_view.earnings_release_dates_view(other_args) + except Exception as e: + print(e, "\n") - def call_high_short(self, other_args: List[str]): - """Process high_short command""" - short_interest_view.high_short_interest_view(other_args) + def call_ford(self, other_args: List[str]): + """Process ford command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ford", + description=""" + Orders by Fidelity customers. Information shown in the table below + is based on the volume of orders entered on the "as of" date shown. Securities + identified are not recommended or endorsed by Fidelity and are displayed for + informational purposes only. [Source: Fidelity] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="Number of top ordered stocks to be printed.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") - def call_low_float(self, other_args: List[str]): - """Process low_float command""" - short_interest_view.low_float_view(other_args) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return - def call_simply_wallst(self, other_args: List[str]): - """Process simply_wallst command""" - simply_wallst_view.simply_wallst_view(other_args) + fidelity_view.orders_view( + num=ns_parser.n_num, + export=ns_parser.export, + ) - def call_spachero(self, other_args: List[str]): - """Process spachero command""" - spachero_view.spachero_view(other_args) + except Exception as e: + print(e, "\n") - def call_uwhales(self, other_args: List[str]): - """Process uwhales command""" - unusual_whales_view.unusual_whales_view(other_args) + def call_arkord(self, other_args: List[str]): + """Process arkord command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="arkord", + description=""" + Orders by ARK Investment Management LLC - https://ark-funds.com/. [Source: https://cathiesark.com] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="Last N ARK orders.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + ark_view.ark_orders_view( + num=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_upcoming(self, other_args: List[str]): + """Process upcoming command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="upcoming", + description="""Upcoming earnings release dates. [Source: Seeking Alpha]""", + ) + parser.add_argument( + "-p", + "--pages", + action="store", + dest="n_pages", + type=check_positive, + default=10, + help="Number of pages to read upcoming earnings from in Seeking Alpha website.", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=1, + help="Number of upcoming earnings release dates to display", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + seeking_alpha_view.upcoming_earning_release_dates( + num_pages=ns_parser.n_pages, + num_earnings=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_latest(self, other_args: List[str]): """Process latest command""" - seeking_alpha_view.latest_news_view(other_args) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="latest", + description="""Latest news articles. [Source: Seeking Alpha]""", + ) + parser.add_argument( + "-i", + "--id", + action="store", + dest="n_id", + type=check_positive, + default=-1, + help="article ID", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=5, + help="number of articles being printed", + ) + parser.add_argument( + "-d", + "--date", + action="store", + dest="s_date", + type=valid_date, + default=datetime.now().strftime("%Y-%m-%d"), + help="starting date of articles", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-i") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + seeking_alpha_view.news( + news_type="latest", + article_id=ns_parser.n_id, + num=ns_parser.n_num, + start_date=ns_parser.s_date, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_trending(self, other_args: List[str]): """Process trending command""" - seeking_alpha_view.trending_news_view(other_args) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="trending", + description="""Trending news articles. [Source: Seeking Alpha]""", + ) + parser.add_argument( + "-i", + "--id", + action="store", + dest="n_id", + type=check_positive, + default=-1, + help="article ID", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=5, + help="number of articles being printed", + ) + parser.add_argument( + "-d", + "--date", + action="store", + dest="s_date", + type=valid_date, + default=datetime.now().strftime("%Y-%m-%d"), + help="starting date of articles", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-i") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + seeking_alpha_view.news( + news_type="trending", + article_id=ns_parser.n_id, + num=ns_parser.n_num, + start_date=ns_parser.s_date, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_highshort(self, other_args: List[str]): + """Process highshort command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="highshort", + description=""" + Print top stocks being more heavily shorted. HighShortInterest.com provides + a convenient sorted database of stocks which have a short interest of over + 20 percent. Additional key data such as the float, number of outstanding shares, + and company industry is displayed. Data is presented for the Nasdaq Stock Market, + the New York Stock Exchange, and the American Stock Exchange. [Source: www.highshortinterest.com] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="Number of top stocks to print.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + shortinterest_view.high_short_interest( + num=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_lowfloat(self, other_args: List[str]): + """Process lowfloat command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="lowfloat", + description=""" + Print top stocks with lowest float. LowFloat.com provides a convenient + sorted database of stocks which have a float of under 10 million shares. Additional key + data such as the number of outstanding shares, short interest, and company industry is + displayed. Data is presented for the Nasdaq Stock Market, the New York Stock Exchange, + the American Stock Exchange, and the Over the Counter Bulletin Board. [Source: www.lowfloat.com] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="Number of top stocks to print.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + shortinterest_view.low_float( + num=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_darkpool(self, other_args: List[str]): """Process darkpool command""" @@ -214,14 +688,6 @@ class DiscoveryController: """Process shortvol command""" stockgrid_view.shortvol(other_args) - def call_popular(self, other_args: List[str]): - """Process popular command""" - sentimentinvestor_view.sort("AHI", other_args, "popular") - - def call_emerging(self, other_args: List[str]): - """Process popular command""" - sentimentinvestor_view.sort("RHI", other_args, "emerging") - def menu(): """Discovery Menu""" diff --git a/gamestonk_terminal/stocks/discovery/fidelity_model.py b/gamestonk_terminal/stocks/discovery/fidelity_model.py index a7ae69779db..b79c1fc9bf5 100644 --- a/gamestonk_terminal/stocks/discovery/fidelity_model.py +++ b/gamestonk_terminal/stocks/discovery/fidelity_model.py @@ -20,7 +20,6 @@ def get_orders() -> Tuple[str, DataFrame]: Fidelity orders Dataframe with the following columns: Symbol, Buy / Sell Ratio, Price Change, Company, # Buy Orders, # Sell Orders """ - url_orders = ( "https://eresearch.fidelity.com/eresearch/gotoBL/fidelityTopOrders.jhtml" ) diff --git a/gamestonk_terminal/stocks/discovery/fidelity_view.py b/gamestonk_terminal/stocks/discovery/fidelity_view.py index 379cf812749..dadcce1ebd6 100644 --- a/gamestonk_terminal/stocks/discovery/fidelity_view.py +++ b/gamestonk_terminal/stocks/discovery/fidelity_view.py @@ -1,17 +1,13 @@ """ Fidelity View """ __docformat__ = "numpy" -import argparse -from typing import List +import os import re +from tabulate import tabulate from colorama import Fore, Style import pandas as pd -from gamestonk_terminal.helper_funcs import ( - check_positive, - patch_pandas_text_adjustment, - parse_known_args_and_warn, -) +from gamestonk_terminal.helper_funcs import export_data from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.stocks.discovery import fidelity_model @@ -69,58 +65,45 @@ def price_change_color_red_green(val: str) -> str: return color + val + Style.RESET_ALL -def orders_view(other_args: List[str]): - """Prints a table with the last N orders by Fidelity customers +def orders_view(num: int, export: str): + """Prints last N orders by Fidelity customers. [Source: Fidelity] Parameters ---------- - other_args : List[str] - argparse other args - ["-n", "10"] + num: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="orders", - description=""" - Orders by Fidelity customers. Information shown in the table below - is based on the volume of orders entered on the "as of" date shown. Securities - identified are not recommended or endorsed by Fidelity and are displayed for - informational purposes only. [Source: Fidelity] - """, + order_header, df_orders = fidelity_model.get_orders() + print(order_header, ":") + + pd.set_option("display.max_colwidth", None) + + if gtff.USE_COLOR: + df_orders["Buy / Sell Ratio"] = df_orders["Buy / Sell Ratio"].apply( + buy_sell_ratio_color_red_green + ) + df_orders["Price Change"] = df_orders["Price Change"].apply( + price_change_color_red_green + ) + + df_orders = df_orders.head(n=num).iloc[:, :-1] + + print( + tabulate( + df_orders, + headers=df_orders.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ) ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="Number of top ordered stocks to be printed.", + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "ford", + df_orders, ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - order_header, df_orders = fidelity_model.get_orders() - - print(order_header, ":") - - pd.set_option("display.max_colwidth", None) - - if gtff.USE_COLOR: - df_orders["Buy / Sell Ratio"] = df_orders["Buy / Sell Ratio"].apply( - buy_sell_ratio_color_red_green - ) - df_orders["Price Change"] = df_orders["Price Change"].apply( - price_change_color_red_green - ) - - patch_pandas_text_adjustment() - - print(df_orders.head(n=ns_parser.n_num).iloc[:, :-1].to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/finnhub_model.py b/gamestonk_terminal/stocks/discovery/finnhub_model.py new file mode 100644 index 00000000000..7e41de5096e --- /dev/null +++ b/gamestonk_terminal/stocks/discovery/finnhub_model.py @@ -0,0 +1,40 @@ +import requests +import pandas as pd +from gamestonk_terminal import config_terminal as cfg + + +def get_ipo_calendar(from_date: str, to_date: str) -> pd.DataFrame: + """Get IPO calendar + + Parameters + ---------- + from_date : str + from date (%Y-%m-%d) to get IPO calendar + to_date : str + to date (%Y-%m-%d) to get IPO calendar + + Returns + ------- + pd.DataFrame + Get dataframe with economic calendar events + """ + response = requests.get( + f"https://finnhub.io/api/v1/calendar/ipo?from={from_date}&to={to_date}&token={cfg.API_FINNHUB_KEY}" + ) + if response.status_code == 200: + d_data = response.json() + if "ipoCalendar" in d_data: + d_refactor_columns = { + "numberOfShares": "Number of Shares", + "totalSharesValue": "Total Shares Value", + "date": "Date", + "exchange": "Exchange", + "name": "Name", + "price": "Price", + "status": "Status", + } + return pd.DataFrame(d_data["ipoCalendar"]).rename( + columns=d_refactor_columns + ) + + return pd.DataFrame() diff --git a/gamestonk_terminal/stocks/discovery/finnhub_view.py b/gamestonk_terminal/stocks/discovery/finnhub_view.py index df3d2f8a40b..674e2219a0d 100644 --- a/gamestonk_terminal/stocks/discovery/finnhub_view.py +++ b/gamestonk_terminal/stocks/discovery/finnhub_view.py @@ -1,123 +1,102 @@ -import argparse -from typing import List +import os from datetime import datetime, timedelta -import requests import pandas as pd -from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal.helper_funcs import ( - parse_known_args_and_warn, - check_non_negative, -) +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.stocks.discovery import finnhub_model -def get_ipo_calendar(from_date: str, to_date: str) -> pd.DataFrame: - """Get IPO calendar +def past_ipo(num_days_behind: int, export: str) -> pd.DataFrame: + """Past IPOs dates. [Source: Finnhub] Parameters ---------- - from_date : str - from date (%Y-%m-%d) to get IPO calendar - to_date : str - to date (%Y-%m-%d) to get IPO calendar + num_days_behind: int + Number of days to look behind for IPOs dates + export : str + Export dataframe data to csv,json,xlsx file Returns ------- - pd.DataFrame - Get dataframe with economic calendar events + df_past_ipo : pd.DataFrame + Past IPOs dates """ - response = requests.get( - f"https://finnhub.io/api/v1/calendar/ipo?from={from_date}&to={to_date}&token={cfg.API_FINNHUB_KEY}" + today = datetime.now() + past_date = today - timedelta(days=num_days_behind) + + df_past_ipo = ( + finnhub_model.get_ipo_calendar( + past_date.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d") + ) + .rename(columns={"Date": "Past"}) + .fillna("") ) - if response.status_code == 200: - d_data = response.json() - if "ipoCalendar" in d_data: - d_refactor_columns = { - "numberOfShares": "Number of Shares", - "totalSharesValue": "Total Shares Value", - "date": "Date", - "exchange": "Exchange", - "name": "Name", - "price": "Price", - "status": "Status", - } - return pd.DataFrame(d_data["ipoCalendar"]).rename( - columns=d_refactor_columns + + if df_past_ipo.empty: + print(f"No IPOs found since the last {num_days_behind} days") + else: + print( + tabulate( + df_past_ipo, + headers=df_past_ipo.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", ) + ) + print("") - return pd.DataFrame() + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "pipo", + df_past_ipo, + ) -def ipo_calendar(other_args: List[str]): - """Past and future IPOs calendar [Finnhub] +def future_ipo(num_days_ahead: int, export: str) -> pd.DataFrame: + """Future IPOs dates. [Source: Finnhub] Parameters ---------- - other_args : List[str] - Command line arguments to be processed with argparse + num_days_ahead: int + Number of days to look ahead for IPOs dates + export : str + Export dataframe data to csv,json,xlsx file + + Returns + ------- + df_future_ipo : pd.DataFrame + Future IPOs dates """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="ipo", - description=""" - Past and future IPOs. [Source: https://finnhub.io] - """, - ) - parser.add_argument( - "-p", - "--past", - action="store", - dest="past_days", - type=check_non_negative, - default=0, - help="Number of past days to look for IPOs.", - ) - parser.add_argument( - "-f", - "--future", - action="store", - dest="future_days", - type=check_non_negative, - default=10, - help="Number of future days to look for IPOs.", + today = datetime.now() + future_date = today + timedelta(days=num_days_ahead) + + df_future_ipo = ( + finnhub_model.get_ipo_calendar( + today.strftime("%Y-%m-%d"), future_date.strftime("%Y-%m-%d") + ) + .rename(columns={"Date": "Future"}) + .fillna("") ) - try: - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-f") + if df_future_ipo.empty: + print(f"No IPOs found for the next {num_days_ahead} days") + else: + print( + tabulate( + df_future_ipo, + headers=df_future_ipo.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ) + ) + print("") - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - today = datetime.now() - - if ns_parser.future_days > 0: - future_date = today + timedelta(days=ns_parser.future_days) - - df_future_ipo = get_ipo_calendar( - today.strftime("%Y-%m-%d"), future_date.strftime("%Y-%m-%d") - ).rename(columns={"Date": "Future"}) - - if df_future_ipo.empty: - print(f"No IPOs found for the next {ns_parser.future_days} days") - else: - print(df_future_ipo.to_string(index=False)) - print("") - - if ns_parser.past_days > 0: - past_date = today - timedelta(days=ns_parser.past_days) - - df_past_ipo = get_ipo_calendar( - past_date.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d") - ).rename(columns={"Date": "Past"}) - - if df_past_ipo.empty: - print(f"No IPOs found since the last {ns_parser.past_days} days") - else: - print(df_past_ipo.to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "fipo", + df_future_ipo, + ) diff --git a/gamestonk_terminal/stocks/discovery/seeking_alpha_model.py b/gamestonk_terminal/stocks/discovery/seeking_alpha_model.py index 1a2df5f03a3..7e63063bd4a 100644 --- a/gamestonk_terminal/stocks/discovery/seeking_alpha_model.py +++ b/gamestonk_terminal/stocks/discovery/seeking_alpha_model.py @@ -44,7 +44,6 @@ def get_next_earnings(pages: int) -> DataFrame: DataFrame Upcoming earnings DataFrame """ - earnings = list() url_next_earnings = "https://seekingalpha.com/earnings/earnings-calendar" diff --git a/gamestonk_terminal/stocks/discovery/seeking_alpha_view.py b/gamestonk_terminal/stocks/discovery/seeking_alpha_view.py index 5861b4fd9ac..eb1e66b1fa4 100644 --- a/gamestonk_terminal/stocks/discovery/seeking_alpha_view.py +++ b/gamestonk_terminal/stocks/discovery/seeking_alpha_view.py @@ -1,240 +1,151 @@ """ Seeking Alpha View """ __docformat__ = "numpy" -import argparse -from typing import List +import os from datetime import datetime +from tabulate import tabulate import pandas as pd -from gamestonk_terminal.helper_funcs import ( - check_positive, - parse_known_args_and_warn, - valid_date, -) +from gamestonk_terminal.helper_funcs import export_data from gamestonk_terminal.stocks.discovery import seeking_alpha_model -def earnings_release_dates_view(other_args: List[str]): - """Prints a data frame with earnings release dates +def upcoming_earning_release_dates(num_pages: int, num_earnings: int, export: str): + """Displays upcoming earnings release dates Parameters ---------- - other_args : List[str] - argparse other args - ["-p", "20", "-n", "5"] + num_pages: int + Number of pages to scrap + num_earnings: int + Number of upcoming earnings release dates + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="up_earnings", - description="""Upcoming earnings release dates. [Source: Seeking Alpha]""", - ) - parser.add_argument( - "-p", - "--pages", - action="store", - dest="n_pages", - type=check_positive, - default=10, - help="Number of pages to read upcoming earnings from in Seeking Alpha website.", - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=3, - help="Number of upcoming earnings release dates to print", - ) # TODO: Check why there are repeated companies # TODO: Create a similar command that returns not only upcoming, but antecipated earnings # i.e. companies where expectation on their returns are high - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return + df_earnings = seeking_alpha_model.get_next_earnings(num_pages) - df_earnings = seeking_alpha_model.get_next_earnings(ns_parser.n_pages) + pd.set_option("display.max_colwidth", None) + if export: + l_earnings = list() + l_earnings_dates = list() - pd.set_option("display.max_colwidth", None) - for n_days, earning_date in enumerate(df_earnings.index.unique()): - if n_days > (ns_parser.n_num - 1): + for n_days, earning_date in enumerate(df_earnings.index.unique()): + if n_days > (num_earnings - 1): + break + + # TODO: Potentially extract Market Cap for each Ticker, and sort + # by Market Cap. Then cut the number of tickers shown to 10 with + # bigger market cap. Didier attempted this with yfinance, but + # the computational time involved wasn't worth pursuing that solution. + + df_earn = df_earnings[earning_date == df_earnings.index][ + ["Ticker", "Name"] + ].dropna() + + if export: + l_earnings_dates.append(earning_date.date()) + l_earnings.append(df_earn) + + df_earn.index = df_earn["Ticker"].values + df_earn.drop(columns=["Ticker"], inplace=True) + + print( + tabulate( + df_earn, + showindex=True, + headers=[f"Earnings on {earning_date.date()}"], + tablefmt="fancy_grid", + ), + "\n", + ) + + if export: + for i, _ in enumerate(l_earnings): + l_earnings[i].reset_index(drop=True, inplace=True) + df_data = pd.concat(l_earnings, axis=1, ignore_index=True) + df_data.columns = l_earnings_dates + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "upcoming", + df_data, + ) + + +def news(news_type: str, article_id: int, num: int, start_date: datetime, export: str): + """Prints the latest news article list. [Source: Seeking Alpha] + + Parameters + ---------- + news_type: str + Select between 'latest' or 'trending' + article_id: int + Article ID. If -1, none is selected + num: int + Number of articles to display. Only used if article_id is -1. + start_date : datetime + Date from when to get articles dates. Only used if article_id is -1. + export : str + Export dataframe data to csv,json,xlsx file + """ + # User wants to see all latest news + if article_id == -1: + if news_type == "latest": + articles = seeking_alpha_model.get_article_list(start_date, num) + elif news_type == "trending": + articles = seeking_alpha_model.get_trending_list(num) + else: + print("Wrong type of news selected", "\n") + + if export: + df_articles = pd.DataFrame(articles) + + for idx, article in enumerate(articles): + print( + article["publishedAt"].replace("T", " ").replace("Z", ""), + "-", + article["id"], + "-", + article["title"], + ) + print(article["url"]) + print("") + + if idx >= num - 1: break - print(f"Earning Release on {earning_date.date()}") - print("----------------------------------------------") - print( - df_earnings[earning_date == df_earnings.index][ - ["Ticker", "Name"] - ].to_string(index=False, header=False) - ) - print("") - - except Exception as e: - print(e, "\n") - - -def latest_news_view(other_args: List[str]): - """Prints the latest news article list - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-i", "123123", "-n", "5"] - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="latest", - description="""Latest news articles. [Source: Seeking Alpha]""", - ) - parser.add_argument( - "-i", - "--id", - action="store", - dest="n_id", - type=check_positive, - default=-1, - help="article ID number", - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="number of articles being printed", - ) - parser.add_argument( - "-d", - "--date", - action="store", - dest="n_date", - type=valid_date, - default=datetime.now().strftime("%Y-%m-%d"), - help="starting date", - ) - - try: - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-i") - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - # User wants to see all latest news - if ns_parser.n_id == -1: - articles = seeking_alpha_model.get_article_list( - ns_parser.n_date, ns_parser.n_num - ) - for idx, article in enumerate(articles): - print( - article["publishedAt"].replace("T", " ").replace("Z", ""), - "-", - article["id"], - "-", - article["title"], - ) - print(article["url"]) - print("") - - if idx >= ns_parser.n_num - 1: - break - - # User wants to access specific article + # User wants to access specific article + else: + if news_type == "latest": + article = seeking_alpha_model.get_article_data(article_id) + elif news_type == "trending": + article = seeking_alpha_model.get_article_data(article_id) else: - article = seeking_alpha_model.get_article_data(ns_parser.n_id) - print( - article["publishedAt"][: article["publishedAt"].rfind(":") - 3].replace( - "T", " " - ), - " ", - article["title"], - ) - print(article["url"]) - print("") - print(article["content"]) + print("Wrong type of news selected", "\n") - except Exception as e: - print(e, "\n") + if export: + df_articles = pd.DataFrame(article) + print( + article["publishedAt"][: article["publishedAt"].rfind(":") - 3].replace( + "T", " " + ), + " ", + article["title"], + ) + print(article["url"]) + print("") + print(article["content"]) -def trending_news_view(other_args: List[str]): - """Prints the trending news article list - - Parameters - ---------- - other_args : List[str] - argparse other args - ["i", "123123", "-n", "5"] - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="trending", - description="""Trending news articles. [Source: Seeking Alpha]""", - ) - parser.add_argument( - "-i", - "--id", - action="store", - dest="n_id", - type=check_positive, - default=-1, - help="article ID number", - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="number of articles being printed", - ) - - try: - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-i") - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - # User wants to see all trending articles - if ns_parser.n_id == -1: - articles = seeking_alpha_model.get_trending_list(ns_parser.n_num) - for idx, article in enumerate(articles): - print( - article["publishedAt"].replace("T", " ").replace("Z", ""), - "-", - article["id"], - "-", - article["title"], - ) - print(article["url"]) - print("") - - if idx >= ns_parser.n_num - 1: - break - - # User wants to access specific article - else: - article = seeking_alpha_model.get_article_data(ns_parser.n_id) - print( - article["publishedAt"][: article["publishedAt"].rfind(":") - 3].replace( - "T", " " - ), - " ", - article["title"], - ) - print(article["url"]) - print("") - print(article["content"]) - - except Exception as e: - print(e, "\n") + if export: + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + news_type, + df_articles, + ) diff --git a/gamestonk_terminal/stocks/discovery/sentimentinvestor_view.py b/gamestonk_terminal/stocks/discovery/sentimentinvestor_view.py deleted file mode 100644 index a13dd7b1459..00000000000 --- a/gamestonk_terminal/stocks/discovery/sentimentinvestor_view.py +++ /dev/null @@ -1,91 +0,0 @@ -import argparse -import logging -import textwrap -from typing import Any, List - -from colorama import Style -from sentipy.sentipy import Sentipy -from tabulate import tabulate - -from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn - -sentipy: Sentipy = Sentipy( - token=cfg.API_SENTIMENTINVESTOR_TOKEN, key=cfg.API_SENTIMENTINVESTOR_KEY -) -"""Initialise SentiPy with the user's API token and key""" - - -__command_descriptions = { - "popular": f""" - The {Style.BRIGHT}popular{Style.RESET_ALL} command prints the stocks with highest Average Hype Index right now. - - {Style.BRIGHT}AHI (Absolute Hype Index){Style.RESET_ALL} - --- - AHI is a measure of how much people are talking about a stock on social media. - It is calculated by dividing the total number of mentions for the chosen stock - on a social network by the mean number of mentions any stock receives on that - social medium. - - === - - {Style.BRIGHT}Sentiment Investor{Style.RESET_ALL} analyzes data from four major social media platforms to - generate hourly metrics on over 2,000 stocks. Sentiment provides volume and - sentiment metrics powered by proprietary NLP models. - """, - "emerging": f""" - The {Style.BRIGHT}emerging{Style.RESET_ALL} command prints the stocks with highest Relative Hype Index right now. - - {Style.BRIGHT}RHI (Relative Hype Index){Style.RESET_ALL} - --- - RHI is a measure of whether people are talking about a stock more or less than - usual, calculated by dividing the mean AHI for the past day by the mean AHI for - for the past week for that stock. - - === - - {Style.BRIGHT}Sentiment Investor{Style.RESET_ALL} analyzes data from four major social media platforms to - generate hourly metrics on over 2,000 stocks. Sentiment provides volume and - sentiment metrics powered by proprietary NLP models. - """, -} - - -def sort(metric: str, other_args: List[str], command_name: str) -> None: - parser = argparse.ArgumentParser( - add_help=False, - prog=command_name, - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent(__command_descriptions[command_name]), - ) - - parser.add_argument( - "-l", - "--limit", - action="store", - dest="limit", - type=int, - default=10, - help="the maximum number of stocks to retrieve", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - data = sentipy.sort(metric, ns_parser.limit) - - table: List[List[Any]] = [] - for index, stock in enumerate(data): - if not hasattr(stock, "symbol") or not hasattr(stock, metric): - logging.warning("data for stock %s is incomplete, ignoring", index + 1) - table.append([]) - else: - table.append([index + 1, stock.symbol, stock.__getattribute__(metric)]) - - print(tabulate(table, headers=["Rank", "Ticker", metric], floatfmt=".3f")) - print() - except Exception as e: - logging.error(e) - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/short_interest_view.py b/gamestonk_terminal/stocks/discovery/short_interest_view.py deleted file mode 100644 index 204958aad20..00000000000 --- a/gamestonk_terminal/stocks/discovery/short_interest_view.py +++ /dev/null @@ -1,102 +0,0 @@ -""" Short Interest View """ -__docformat__ = "numpy" - -import argparse -from typing import List -import pandas as pd -from gamestonk_terminal.helper_funcs import ( - check_positive, - parse_known_args_and_warn, -) - -from gamestonk_terminal.stocks.discovery import short_interest_model - - -def high_short_interest_view(other_args: List[str]): - """Prints top N shorted stocks - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-n", "20"] - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="high_short", - description=""" - Print top stocks being more heavily shorted. HighShortInterest.com provides - a convenient sorted database of stocks which have a short interest of over - 20 percent. Additional key data such as the float, number of outstanding shares, - and company industry is displayed. Data is presented for the Nasdaq Stock Market, - the New York Stock Exchange, and the American Stock Exchange. [Source: www.highshortinterest.com] - """, - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="Number of top stocks to print.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_high_short_interest = short_interest_model.get_high_short_interest() - - pd.set_option("display.max_colwidth", None) - print(df_high_short_interest.head(n=ns_parser.n_num).to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") - - -def low_float_view(other_args: List[str]): - """Prints top N low float stocks - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-n", "20"] - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="low_float", - description=""" - Print top stocks with lowest float. LowFloat.com provides a convenient - sorted database of stocks which have a float of under 10 million shares. Additional key - data such as the number of outstanding shares, short interest, and company industry is - displayed. Data is presented for the Nasdaq Stock Market, the New York Stock Exchange, - the American Stock Exchange, and the Over the Counter Bulletin Board. [Source: www.lowfloat.com] - """, - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="Number of top stocks to print.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_low_float = short_interest_model.get_low_float() - - pd.set_option("display.max_colwidth", None) - print(df_low_float.head(n=ns_parser.n_num).to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/short_interest_model.py b/gamestonk_terminal/stocks/discovery/shortinterest_model.py similarity index 100% rename from gamestonk_terminal/stocks/discovery/short_interest_model.py rename to gamestonk_terminal/stocks/discovery/shortinterest_model.py diff --git a/gamestonk_terminal/stocks/discovery/shortinterest_view.py b/gamestonk_terminal/stocks/discovery/shortinterest_view.py new file mode 100644 index 00000000000..b7951875251 --- /dev/null +++ b/gamestonk_terminal/stocks/discovery/shortinterest_view.py @@ -0,0 +1,75 @@ +""" Short Interest View """ +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data + +from gamestonk_terminal.stocks.discovery import shortinterest_model + + +def high_short_interest(num: int, export: str): + """Prints top N high shorted interest stocks from https://www.highshortinterest.com + + Parameters + ---------- + num: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df_high_short_interest = shortinterest_model.get_high_short_interest() + df_high_short_interest = df_high_short_interest.iloc[1:].head(n=num) + + print( + tabulate( + df_high_short_interest, + headers=df_high_short_interest.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "lowfloat", + df_high_short_interest, + ) + + +def low_float(num: int, export: str): + """Prints top N low float stocks from https://www.lowfloat.com + + Parameters + ---------- + num: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df_low_float = shortinterest_model.get_low_float() + df_low_float = df_low_float.iloc[1:].head(n=num) + + print( + tabulate( + df_low_float, + headers=df_low_float.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "lowfloat", + df_low_float, + ) + + +# TODO: Add https://www.pennystockflow.com diff --git a/gamestonk_terminal/stocks/discovery/simply_wallst_view.py b/gamestonk_terminal/stocks/discovery/simply_wallst_view.py deleted file mode 100644 index ee672178973..00000000000 --- a/gamestonk_terminal/stocks/discovery/simply_wallst_view.py +++ /dev/null @@ -1,76 +0,0 @@ -""" Simply Wallst View """ -__docformat__ = "numpy" - -import argparse -from typing import List -import webbrowser -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn - - -def simply_wallst_view(other_args: List[str]): - """Opens simplywall.st for a specific industry in a browser - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-i", "banks"] - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="simply_wallst", - description=""" - Simply Wall Street Research. Opens web browser. Although this does not require - an API key, it requires a subscription to the website by the user - (there's a 14 days free trial).[Source: Simply Wall St.] - """, - ) - parser.add_argument( - "-i", - "--industry", - action="store", - dest="s_industry", - type=str, - default="any", - help="Industry of interest.", - choices=[ - "any", - "automobiles", - "banks", - "capital-goods", - "commercial-services", - "consumer-durables", - "consumer-services", - "diversified-financials", - "energy", - "consumer-retailing", - "food-beverage-tobacco", - "healthcare", - "household", - "insurance", - "materials", - "media", - "pharmaceuticals-biotech", - "real-estate", - "retail", - "semiconductors", - "software", - "tech", - "telecom", - "transportation", - "utilities", - ], - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - webbrowser.open( - f"https://simplywall.st/stocks/us/{ns_parser.s_industry}?page=1" - ) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/spachero_view.py b/gamestonk_terminal/stocks/discovery/spachero_view.py deleted file mode 100644 index 312b5250800..00000000000 --- a/gamestonk_terminal/stocks/discovery/spachero_view.py +++ /dev/null @@ -1,34 +0,0 @@ -""" Spachero View """ -__docformat__ = "numpy" - -import argparse -from typing import List -import webbrowser -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn - - -def spachero_view(other_args: List[str]): - """Opens www.spachero.com website in a browser - - Parameters - ---------- - other_args : List[str] - argparse other args - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="spachero", - description="""Great website for SPACs research. [Source: www.spachero.com]""", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - webbrowser.open("https://www.spachero.com") - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/unusual_whales_view.py b/gamestonk_terminal/stocks/discovery/unusual_whales_view.py deleted file mode 100644 index e7e269480fb..00000000000 --- a/gamestonk_terminal/stocks/discovery/unusual_whales_view.py +++ /dev/null @@ -1,31 +0,0 @@ -import argparse -from typing import List -import webbrowser -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn - - -def unusual_whales_view(other_args: List[str]): - """Opens unusualwhales.com in a browser - - Parameters - ---------- - other_args : List[str] - argparse other args - """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="uwhales", - description="""Good website for SPACs research. [Source: www.unusualwhales.com]""", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - webbrowser.open("https://unusualwhales.com/spacs") - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/yahoo_finance_view.py b/gamestonk_terminal/stocks/discovery/yahoo_finance_view.py deleted file mode 100644 index 8313697ebd3..00000000000 --- a/gamestonk_terminal/stocks/discovery/yahoo_finance_view.py +++ /dev/null @@ -1,93 +0,0 @@ -""" Yahoo Finance View """ -__docformat__ = "numpy" - -import argparse -from typing import List -import pandas as pd -import requests - -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn - - -def gainers_view(other_args: List[str]): - """Prints top N gainers - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-n", "20"] - """ - - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="gainers", - description="Print up to 25 top ticker gainers. [Source: Yahoo Finance]", - ) - - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_gainers", - type=int, - default=5, - choices=range(1, 25), - help="Number of the top gainers stocks to retrieve.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - url = "https://finance.yahoo.com/screener/predefined/day_gainers" - - df_gainers = pd.read_html(requests.get(url).text)[0] - print(df_gainers.head(ns_parser.n_gainers).to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") - - -def losers_view(other_args: List[str]): - """Prints top N loosers - - Parameters - ---------- - other_args : List[str] - argparse other args - ["-n", "20"] - """ - - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="losers", - description="Print up to 25 top ticker losers. [Source: Yahoo Finance]", - ) - - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_losers", - type=int, - default=5, - choices=range(1, 25), - help="Number of the top losers stocks to retrieve.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - url = "https://finance.yahoo.com/screener/predefined/day_losers" - - df_losers = pd.read_html(requests.get(url).text)[0] - print(df_losers.head(ns_parser.n_losers).to_string(index=False)) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/discovery/yahoofinance_model.py b/gamestonk_terminal/stocks/discovery/yahoofinance_model.py new file mode 100644 index 00000000000..13a76f3cb6c --- /dev/null +++ b/gamestonk_terminal/stocks/discovery/yahoofinance_model.py @@ -0,0 +1,31 @@ +""" Yahoo Finance Model """ +__docformat__ = "numpy" + +import pandas as pd +import requests + + +def get_gainers() -> pd.DataFrame: + """Get Yahoo Finance gainers + + Returns + ------- + pd.DataFrame + Stock Gainers + """ + url_gainers = "https://finance.yahoo.com/screener/predefined/day_gainers" + + return pd.read_html(requests.get(url_gainers).text)[0] + + +def get_losers() -> pd.DataFrame: + """Get Yahoo Finance losers + + Returns + ------- + pd.DataFrame + Stock Losers + """ + url_losers = "https://finance.yahoo.com/screener/predefined/day_losers" + + return pd.read_html(requests.get(url_losers).text)[0] diff --git a/gamestonk_terminal/stocks/discovery/yahoofinance_view.py b/gamestonk_terminal/stocks/discovery/yahoofinance_view.py new file mode 100644 index 00000000000..5c400ea60e8 --- /dev/null +++ b/gamestonk_terminal/stocks/discovery/yahoofinance_view.py @@ -0,0 +1,79 @@ +""" Yahoo Finance View """ +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.stocks.discovery import yahoofinance_model + + +def display_gainers(num_stocks: int, export: str): + """Display gainers. [Source: Yahoo Finance] + + Parameters + ---------- + num_stocks: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df_gainers = yahoofinance_model.get_gainers().head(num_stocks) + df_gainers.dropna(how="all", axis=1, inplace=True) + df_gainers = df_gainers.replace(float("NaN"), "") + + if df_gainers.empty: + print("No gainers found.") + else: + print( + tabulate( + df_gainers, + headers=df_gainers.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ) + ) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "gainers", + df_gainers, + ) + + +def display_losers(num_stocks: int, export: str): + """Display losers. [Source: Yahoo Finance] + + Parameters + ---------- + num_stocks: int + Number of stocks to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df_losers = yahoofinance_model.get_losers().head(num_stocks) + df_losers.dropna(how="all", axis=1, inplace=True) + df_losers = df_losers.replace(float("NaN"), "") + + if df_losers.empty: + print("No losers found.") + else: + print( + tabulate( + df_losers, + headers=df_losers.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ) + ) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "losers", + df_losers, + ) diff --git a/tests/test_discovery/test_discovery_ark_view.py b/tests/test_discovery/test_discovery_ark_view.py index c92b0aa3e08..fbdba193a96 100644 --- a/tests/test_discovery/test_discovery_ark_view.py +++ b/tests/test_discovery/test_discovery_ark_view.py @@ -41,4 +41,4 @@ class TestDiscoveryArkView: parse_dates=["date"], ) - ark_orders_view(["-n", "10"]) + ark_orders_view(10, "") diff --git a/tests/test_discovery/test_discovery_fidelity_api.py b/tests/test_discovery/test_discovery_fidelity_api.py index 7ff92717cec..49bdd78bbe0 100644 --- a/tests/test_discovery/test_discovery_fidelity_api.py +++ b/tests/test_discovery/test_discovery_fidelity_api.py @@ -10,7 +10,7 @@ from gamestonk_terminal.stocks.discovery.fidelity_view import ( class TestDiscoveryFidelityApi(unittest.TestCase): def test_orders(self): - orders_view([]) + orders_view(5, "") def test_buy_sell_ratio_color_red_green(self): res = buy_sell_ratio_color_red_green("56% Buys, 44% Sells") diff --git a/tests/test_discovery/test_discovery_short_interest_model.py b/tests/test_discovery/test_discovery_short_interest_model.py index 7dd168d1c7d..ca6b223c3f0 100644 --- a/tests/test_discovery/test_discovery_short_interest_model.py +++ b/tests/test_discovery/test_discovery_short_interest_model.py @@ -8,7 +8,7 @@ from gamestonk_terminal.test_helper import ( # noqa: F401 pytest_generate_tests, ) -from gamestonk_terminal.stocks.discovery.short_interest_model import ( +from gamestonk_terminal.stocks.discovery.shortinterest_model import ( get_high_short_interest, get_low_float, ) @@ -17,7 +17,7 @@ assertions = unittest.TestCase("__init__") class TestDiscoveryShortInterestApi: - @mock.patch("gamestonk_terminal.stocks.discovery.short_interest_model.requests") + @mock.patch("gamestonk_terminal.stocks.discovery.shortinterest_model.requests") @parameterize_from_file( "test_get_high_short_interest", "../tests/data/discovery_short_interest_model.yaml", @@ -36,7 +36,7 @@ class TestDiscoveryShortInterestApi: expected_result.replace("\r\n", "\n"), ) - @mock.patch("gamestonk_terminal.stocks.discovery.short_interest_model.requests") + @mock.patch("gamestonk_terminal.stocks.discovery.shortinterest_model.requests") @parameterize_from_file( "test_get_low_float", "../tests/data/discovery_short_interest_model.yaml",