diff --git a/gamestonk_terminal/fundamental_analysis/alpha_vantage_controller.py b/gamestonk_terminal/fundamental_analysis/alpha_vantage_controller.py new file mode 100644 index 00000000000..01d029a569c --- /dev/null +++ b/gamestonk_terminal/fundamental_analysis/alpha_vantage_controller.py @@ -0,0 +1,168 @@ +""" Alpha Vantage Controller """ +__docformat__ = "numpy" + +import argparse +from typing import List +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal.fundamental_analysis import alpha_vantage_view as avv +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session + + +class AlphaVantageController: + """ Alpha Vantage Controller """ + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "overview", + "key", + "income", + "balance", + "cash", + "earnings", + ] + + def __init__(self, ticker: str, start: str, interval: str): + """Constructor + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Stat date of the stock data + interval : str + Stock data interval + """ + + self.ticker = ticker + self.start = start + self.interval = interval + self.av_parser = argparse.ArgumentParser(add_help=False, prog="av") + self.av_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + def print_help(self): + """ Print help """ + + intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"] + + if self.start: + print( + f"\n{intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})" + ) + else: + print(f"\n{intraday} Stock: {self.ticker}") + + print("\nAlpha Vantage:") + print(" help show this alpha vantage menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" overview overview of the company") + print(" key company key metrics") + print(" income income statements of the company") + print(" balance balance sheet of the company") + print(" cash cash flow of the company") + print(" earnings earnings dates and reported EPS") + print("") + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + (known_args, other_args) = self.av_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_overview(self, other_args: List[str]): + """ Process overview command """ + avv.overview(other_args, self.ticker) + + def call_key(self, other_args: List[str]): + """ Process overview command """ + avv.key(other_args, self.ticker) + + def call_income(self, other_args: List[str]): + """ Process income command """ + avv.income_statement(other_args, self.ticker) + + def call_balance(self, other_args: List[str]): + """ Process balance command """ + avv.balance_sheet(other_args, self.ticker) + + def call_cash(self, other_args: List[str]): + """ Process cash command """ + avv.cash_flow(other_args, self.ticker) + + def call_earnings(self, other_args: List[str]): + """ Process earnings command """ + avv.earnings(other_args, self.ticker) + + +def menu(ticker: str, start: str, interval: str): + """Alpha Vantage menu + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + + av_controller = AlphaVantageController(ticker, start, interval) + av_controller.call_help(None) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in av_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (fa)>(av)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (fa)>(av)> ") + + try: + process_input = av_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/fundamental_analysis/alpha_vantage_api.py b/gamestonk_terminal/fundamental_analysis/alpha_vantage_view.py similarity index 79% rename from gamestonk_terminal/fundamental_analysis/alpha_vantage_api.py rename to gamestonk_terminal/fundamental_analysis/alpha_vantage_view.py index 36c648b614c..2adb4f8d177 100644 --- a/gamestonk_terminal/fundamental_analysis/alpha_vantage_api.py +++ b/gamestonk_terminal/fundamental_analysis/alpha_vantage_view.py @@ -1,112 +1,33 @@ +""" Alpha Vantage View """ +__docformat__ = "numpy" + import argparse +from typing import List import requests + from alpha_vantage.fundamentaldata import FundamentalData import pandas as pd -from pandas.io.json import json_normalize + from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.dataframe_helpers import clean_df_index from gamestonk_terminal.helper_funcs import ( check_positive, long_number_format, parse_known_args_and_warn, - get_flair, ) -from gamestonk_terminal.menu import session -from prompt_toolkit.completion import NestedCompleter -def print_menu(s_ticker, s_start, s_interval): - """ Print help """ +def overview(other_args: List[str], ticker: str): + """Alpha Vantage stock ticker overview - s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ - if s_start: - print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") - else: - print(f"\n{s_intraday} Stock: {s_ticker}") - - print("\nAlpha Vantage:") - print(" help show this alpha vantage menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" overview overview of the company") - print(" income income statements of the company") - print(" balance balance sheet of the company") - print(" cash cash flow of the company") - print(" earnings earnings dates and reported EPS") - print("") - - -def menu(s_ticker, s_start, s_interval): - - # Add list of arguments that the fundamental analysis parser accepts - av_parser = argparse.ArgumentParser(prog="av", add_help=False) - choices = [ - "help", - "q", - "quit", - "overview", - "income", - "balance", - "cash", - "earnings", - ] - av_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - - print_menu(s_ticker, s_start, s_interval) - - # Loop forever and ever - while True: - # Get input command from user - if session: - as_input = session.prompt( - f"{get_flair()} (fa)>(av)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (fa)>(av)> ") - - # Parse alpha vantage command of the list of possible commands - try: - (ns_known_args, l_args) = av_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - print_menu(s_ticker, s_start, s_interval) - - elif ns_known_args.cmd == "q": - # Just leave the menu - return False - - elif ns_known_args.cmd == "quit": - # Abandon the program - return True - - elif ns_known_args.cmd == "overview": - overview(l_args, s_ticker) - - elif ns_known_args.cmd == "income": - income_statement(l_args, s_ticker) - - elif ns_known_args.cmd == "balance": - balance_sheet(l_args, s_ticker) - - elif ns_known_args.cmd == "cash": - cash_flow(l_args, s_ticker) - - elif ns_known_args.cmd == "earnings": - earnings(l_args, s_ticker) - - else: - print("Command not recognized!") - - -def overview(l_args, s_ticker): parser = argparse.ArgumentParser( add_help=False, prog="overview", @@ -128,18 +49,18 @@ def overview(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return # Request OVERVIEW data from Alpha Vantage API - s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={s_ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" + s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" result = requests.get(s_req, stream=True) # If the returned data was successful if result.status_code == 200: # Parse json data to dataframe - df_fa = json_normalize(result.json()) + df_fa = pd.json_normalize(result.json()) # Keep json data sorting in dataframe df_fa = df_fa[list(result.json().keys())].T df_fa = df_fa.applymap(lambda x: long_number_format(x)) @@ -182,7 +103,17 @@ def overview(l_args, s_ticker): return -def key(l_args, s_ticker): +def key(other_args: List[str], ticker: str): + """Alpha Vantage key metrics + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="key", @@ -196,17 +127,17 @@ def key(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return # Request OVERVIEW data - s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={s_ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" + s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" result = requests.get(s_req, stream=True) # If the returned data was successful if result.status_code == 200: - df_fa = json_normalize(result.json()) + df_fa = pd.json_normalize(result.json()) df_fa = df_fa[list(result.json().keys())].T df_fa = df_fa.applymap(lambda x: long_number_format(x)) clean_df_index(df_fa) @@ -248,10 +179,20 @@ def key(l_args, s_ticker): return -def income_statement(l_args, s_ticker): +def income_statement(other_args: List[str], ticker: str): + """Alpha Vantage income statement + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, - prog="incom", + prog="income", description=""" Prints a complete income statement over time. This can be either quarterly or annually. The following fields are expected: Accepted date, Cost and expenses, Cost of revenue, @@ -283,7 +224,7 @@ def income_statement(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -295,10 +236,10 @@ def income_statement(l_args, s_ticker): fd = FundamentalData(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas") if ns_parser.b_quarter: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_income_statement_quarterly(symbol=s_ticker) + df_fa, _ = fd.get_income_statement_quarterly(symbol=ticker) else: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_income_statement_annual(symbol=s_ticker) + df_fa, _ = fd.get_income_statement_annual(symbol=ticker) df_fa = clean_fundamentals_df(df_fa, num=ns_parser.n_num) print(df_fa) @@ -310,7 +251,17 @@ def income_statement(l_args, s_ticker): return -def balance_sheet(l_args, s_ticker): +def balance_sheet(other_args: List[str], ticker: str): + """Alpha Vantage balance sheet + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="balance", @@ -351,7 +302,7 @@ def balance_sheet(l_args, s_ticker): ) try: - (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + (ns_parser, l_unknown_args) = parser.parse_known_args(other_args) if l_unknown_args: print(f"The following args couldn't be interpreted: {l_unknown_args}\n") @@ -365,10 +316,10 @@ def balance_sheet(l_args, s_ticker): fd = FundamentalData(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas") if ns_parser.b_quarter: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_balance_sheet_quarterly(symbol=s_ticker) + df_fa, _ = fd.get_balance_sheet_quarterly(symbol=ticker) else: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_balance_sheet_annual(symbol=s_ticker) + df_fa, _ = fd.get_balance_sheet_annual(symbol=ticker) df_fa = clean_fundamentals_df(df_fa, num=ns_parser.n_num) print(df_fa) @@ -380,7 +331,17 @@ def balance_sheet(l_args, s_ticker): return -def cash_flow(l_args, s_ticker): +def cash_flow(other_args: List[str], ticker: str): + """Alpha Vantage cash flow + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="cash", @@ -419,7 +380,7 @@ def cash_flow(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -431,10 +392,10 @@ def cash_flow(l_args, s_ticker): fd = FundamentalData(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas") if ns_parser.b_quarter: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_cash_flow_quarterly(symbol=s_ticker) + df_fa, _ = fd.get_cash_flow_quarterly(symbol=ticker) else: # pylint: disable=unbalanced-tuple-unpacking - df_fa, _ = fd.get_cash_flow_annual(symbol=s_ticker) + df_fa, _ = fd.get_cash_flow_annual(symbol=ticker) df_fa = clean_fundamentals_df(df_fa, num=ns_parser.n_num) print(df_fa) @@ -446,7 +407,17 @@ def cash_flow(l_args, s_ticker): return -def earnings(l_args, s_ticker): +def earnings(other_args: List[str], ticker: str): + """Alpha Vantage earnings + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="earnings", @@ -475,7 +446,7 @@ def earnings(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -487,13 +458,13 @@ def earnings(l_args, s_ticker): # Request EARNINGS data from Alpha Vantage API s_req = ( "https://www.alphavantage.co/query?function=EARNINGS&" - f"symbol={s_ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" + f"symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}" ) result = requests.get(s_req, stream=True) # If the returned data was successful if result.status_code == 200: - df_fa = json_normalize(result.json()) + df_fa = pd.json_normalize(result.json()) if ns_parser.b_quarter: df_fa = pd.DataFrame(df_fa["quarterlyEarnings"][0]) df_fa = df_fa[ diff --git a/gamestonk_terminal/fundamental_analysis/business_insider_api.py b/gamestonk_terminal/fundamental_analysis/business_insider_view.py similarity index 86% rename from gamestonk_terminal/fundamental_analysis/business_insider_api.py rename to gamestonk_terminal/fundamental_analysis/business_insider_view.py index 4811c2585be..bb0d4db3d6c 100644 --- a/gamestonk_terminal/fundamental_analysis/business_insider_api.py +++ b/gamestonk_terminal/fundamental_analysis/business_insider_view.py @@ -1,4 +1,8 @@ +""" Business Insider View """ +__docformat__ = "numpy" + import argparse +from typing import List import requests from bs4 import BeautifulSoup import pandas as pd @@ -6,7 +10,17 @@ from rapidfuzz import fuzz from gamestonk_terminal.helper_funcs import get_user_agent, parse_known_args_and_warn -def management(l_args, s_ticker): +def management(other_args: List[str], ticker: str): + """Display company's managers + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="mgmt", @@ -17,12 +31,12 @@ def management(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return url_market_business_insider = ( - f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" ) text_soup_market_business_insider = BeautifulSoup( requests.get( @@ -42,7 +56,7 @@ def management(l_args, s_ticker): found_h2s[next_h2.text] = next_table if found_h2s.get("Management") is None: - print(f"No management information in Business Insider for {s_ticker}") + print(f"No management information in Business Insider for {ticker}") print("") return @@ -73,7 +87,7 @@ def management(l_args, s_ticker): for s_name in df_management.index: df_management.loc[s_name][ "Info" - ] = f"http://www.google.com/search?q={s_name} {s_ticker.upper()}".replace( + ] = f"http://www.google.com/search?q={s_name} {ticker.upper()}".replace( " ", "%20" ) diff --git a/gamestonk_terminal/fundamental_analysis/fa_controller.py b/gamestonk_terminal/fundamental_analysis/fa_controller.py new file mode 100644 index 00000000000..2cad209fd82 --- /dev/null +++ b/gamestonk_terminal/fundamental_analysis/fa_controller.py @@ -0,0 +1,253 @@ +""" Fundamental Analysis Controller """ +__docformat__ = "numpy" + +import argparse +from typing import List +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal.fundamental_analysis import alpha_vantage_controller as avc +from gamestonk_terminal.fundamental_analysis import business_insider_view as biw +from gamestonk_terminal.fundamental_analysis import ( + financial_modeling_prep_controller as fmpc, +) +from gamestonk_terminal.fundamental_analysis import finviz_view +from gamestonk_terminal.fundamental_analysis import market_watch_view +from gamestonk_terminal.fundamental_analysis import yahoo_finance_view +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session + + +class FundamentalAnalysisController: + """ Fundamental Analysis Controller """ + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "screener", + "income", + "balance", + "cash", + "mgmt", + "info", + "shrs", + "sust", + "cal", + "av", + "fmp", + ] + + def __init__(self, ticker: str, start: str, interval: str): + """Constructor + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Stat date of the stock data + interval : str + Stock data interval + """ + + self.ticker = ticker + self.start = start + self.interval = interval + + self.fa_parser = argparse.ArgumentParser(add_help=False, prog="fa") + self.fa_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + def print_help(self): + """ Print help """ + + intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"] + + if self.start: + print( + f"\n{intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})" + ) + else: + print(f"\n{intraday} Stock: {self.ticker}") + + print( + "\nFundamental Analysis:" + ) # https://github.com/JerBouma/FundamentalAnalysis + print(" help show this fundamental analysis menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" screener screen info about the company [Finviz]") + print(" mgmt management team of the company [Business Insider]") + print("") + print("Market Watch API") + print(" income income statement of the company") + print(" balance balance sheet of the company") + print(" cash cash flow statement of the company") + print("") + print("Yahoo Finance API") + print(" info information scope of the company") + print(" shrs shareholders of the company") + print(" sust sustainability values of the company") + print(" cal calendar earnings and estimates of the company") + print("") + print("Other Sources:") + print("> av Alpha Vantage MENU") + print("> fmp Financial Modeling Prep MENU") + print("") + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + + (known_args, other_args) = self.fa_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_mgmt(self, other_args: List[str]): + """ Process mgmt command """ + biw.management(other_args, self.ticker) + + def call_screener(self, other_args: List[str]): + """ Process screener command """ + finviz_view.screener(other_args, self.ticker) + + def call_income(self, other_args: List[str]): + """ Process income command """ + market_watch_view.income(other_args, self.ticker) + + def call_balance(self, other_args: List[str]): + """ Process balance command """ + market_watch_view.balance(other_args, self.ticker) + + def call_cash(self, other_args: List[str]): + """ Process cash command """ + market_watch_view.cash(other_args, self.ticker) + + def call_info(self, other_args: List[str]): + """ Process info command """ + yahoo_finance_view.info(other_args, self.ticker) + + def call_shrs(self, other_args: List[str]): + """ Process shrs command """ + yahoo_finance_view.shareholders(other_args, self.ticker) + + def call_sust(self, other_args: List[str]): + """ Process sust command """ + yahoo_finance_view.sustainability(other_args, self.ticker) + + def call_cal(self, other_args: List[str]): + """ Process cal command """ + yahoo_finance_view.calendar_earnings(other_args, self.ticker) + + # pylint: disable=unused-argument + def call_av(self, other_args: List[str]): + """ Process av command """ + ret = avc.menu(self.ticker, self.start, self.interval) + + if ret is not True: + self.print_help() + + # pylint: disable=unused-argument + def call_fmp(self, other_args: List[str]): + """ Process fmp command """ + ret = fmpc.menu(self.ticker, self.start, self.interval) + + if ret is not True: + self.print_help() + + +def key_metrics_explained(other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="info", + description=""" + Provides information about main key metrics. Namely: EBITDA, + EPS, P/E, PEG, FCF, P/B, ROE, DPR, P/S, Dividend Yield Ratio, D/E, and Beta. + """, + ) + + try: + (_, l_unknown_args) = parser.parse_known_args(other_args) + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}") + + filepath = "fundamental_analysis/key_metrics_explained.txt" + with open(filepath) as fp: + line = fp.readline() + while line: + print(f"{line.strip()}") + line = fp.readline() + print("") + + except Exception as e: + print(e) + print("ERROR!\n") + return + + +def menu(ticker: str, start: str, interval: str): + """Fundamental Analysis menu + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + + fa_controller = FundamentalAnalysisController(ticker, start, interval) + fa_controller.call_help(None) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in fa_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (fa)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (fa)> ") + + try: + process_input = fa_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/fundamental_analysis/fa_menu.py b/gamestonk_terminal/fundamental_analysis/fa_menu.py deleted file mode 100644 index 426fa418526..00000000000 --- a/gamestonk_terminal/fundamental_analysis/fa_menu.py +++ /dev/null @@ -1,193 +0,0 @@ -import argparse - -from gamestonk_terminal.fundamental_analysis import alpha_vantage_api as av_api -from gamestonk_terminal.fundamental_analysis import business_insider_api as bi_api -from gamestonk_terminal.fundamental_analysis import ( - financial_modeling_prep_api as fmp_api, -) -from gamestonk_terminal.fundamental_analysis import finviz_api as fvz_api -from gamestonk_terminal.fundamental_analysis import market_watch_api as mw_api -from gamestonk_terminal.fundamental_analysis import yahoo_finance_api as yf_api -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import get_flair -from gamestonk_terminal.menu import session -from prompt_toolkit.completion import NestedCompleter - - -def print_fundamental_analysis(s_ticker, s_start, s_interval): - """ Print help """ - - s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] - - if s_start: - print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") - else: - print(f"\n{s_intraday} Stock: {s_ticker}") - - print("\nFundamental Analysis:") # https://github.com/JerBouma/FundamentalAnalysis - print(" help show this fundamental analysis menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" screener screen info about the company [Finviz]") - print(" mgmt management team of the company [Business Insider]") - print("") - print("Market Watch API") - print(" income income statement of the company") - print(" balance balance sheet of the company") - print(" cash cash flow statement of the company") - print("") - print("Yahoo Finance API") - print(" info information scope of the company") - print(" shrs shareholders of the company") - print(" sust sustainability values of the company") - print(" cal calendar earnings and estimates of the company") - print("") - print("Other Sources:") - print("> av Alpha Vantage MENU") - print("> fmp Financial Modeling Prep MENU") - print("") - return - - -def key_metrics_explained(l_args): - parser = argparse.ArgumentParser( - add_help=False, - prog="info", - description=""" - Provides information about main key metrics. Namely: EBITDA, - EPS, P/E, PEG, FCF, P/B, ROE, DPR, P/S, Dividend Yield Ratio, D/E, and Beta. - """, - ) - - try: - (_, l_unknown_args) = parser.parse_known_args(l_args) - - if l_unknown_args: - print(f"The following args couldn't be interpreted: {l_unknown_args}") - - filepath = "fundamental_analysis/key_metrics_explained.txt" - with open(filepath) as fp: - line = fp.readline() - while line: - print(f"{line.strip()}") - line = fp.readline() - print("") - - except Exception as e: - print(e) - print("ERROR!\n") - return - - -# pylint: disable=too-many-branches -def fa_menu(s_ticker, s_start, s_interval): - - # Add list of arguments that the fundamental analysis parser accepts - fa_parser = argparse.ArgumentParser(prog="fa", add_help=False) - choices = [ - "help", - "q", - "quit", - "screener", - "income", - "balance", - "cash", - "mgmt", - "info", - "shrs", - "sust", - "cal", - "av", - "fmp", - ] - fa_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - - should_print_help = True - - # Loop forever and ever - while True: - if should_print_help: - print_fundamental_analysis(s_ticker, s_start, s_interval) - should_print_help = False - - # Get input command from user - if session and gtff.USE_PROMPT_TOOLKIT: - as_input = session.prompt( - f"{get_flair()} (fa)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (fa)> ") - - # Parse fundamental analysis command of the list of possible commands - try: - (ns_known_args, l_args) = fa_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - should_print_help = True - - elif ns_known_args.cmd == "q": - # Just leave the FA menu - return False - - elif ns_known_args.cmd == "quit": - # Abandon the program - return True - - # BUSINESS INSIDER API - elif ns_known_args.cmd == "mgmt": - bi_api.management(l_args, s_ticker) - - # FINVIZ API - elif ns_known_args.cmd == "screener": - fvz_api.screener(l_args, s_ticker) - - # MARKET WATCH API - elif ns_known_args.cmd == "income": - mw_api.income(l_args, s_ticker) - - elif ns_known_args.cmd == "balance": - mw_api.balance(l_args, s_ticker) - - elif ns_known_args.cmd == "cash": - mw_api.cash(l_args, s_ticker) - - # YAHOO FINANCE API - elif ns_known_args.cmd == "info": - yf_api.info(l_args, s_ticker) - - elif ns_known_args.cmd == "shrs": - yf_api.shareholders(l_args, s_ticker) - - elif ns_known_args.cmd == "sust": - yf_api.sustainability(l_args, s_ticker) - - elif ns_known_args.cmd == "cal": - yf_api.calendar_earnings(l_args, s_ticker) - - # ALPHA VANTAGE API - elif ns_known_args.cmd == "av": - b_quit = av_api.menu(s_ticker, s_start, s_interval) - - if b_quit: - return True - else: - should_print_help = True - - # FINANCIAL MODELING PREP API - elif ns_known_args.cmd == "fmp": - b_quit = fmp_api.menu(s_ticker, s_start, s_interval) - - if b_quit: - return True - else: - should_print_help = True - - else: - print("Command not recognized!") diff --git a/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_controller.py b/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_controller.py new file mode 100644 index 00000000000..e68077aa0f9 --- /dev/null +++ b/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_controller.py @@ -0,0 +1,194 @@ +""" Financial Modeling Prep Controller """ +__docformat__ = "numpy" + +import argparse +from typing import List +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal.fundamental_analysis import financial_modeling_prep_view as fmpv +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import ( + get_flair, +) +from gamestonk_terminal.menu import session + + +class FinancialModelingPrepController: + """ Financial Modeling Prep Controller """ + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "profile", + "quote", + "enterprise", + "dcf", + "income", + "balance", + "cash", + "metrics", + "ratios", + "growth", + ] + + def __init__(self, ticker: str, start: str, interval: str): + """Constructor + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Stat date of the stock data + interval : str + Stock data interval + """ + + self.ticker = ticker + self.start = start + self.interval = interval + self.fmp_parser = argparse.ArgumentParser(add_help=False, prog="fmp") + self.fmp_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + def print_help(self): + """ Print help """ + + intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"] + + if self.start: + print( + f"\n{intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})" + ) + else: + print(f"\n{intraday} Stock: {self.ticker}") + + print("\nFinancial Modeling Prep API") + print(" help show this financial modeling prep menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" profile profile of the company") + print(" quote quote of the company") + print(" enterprise enterprise value of the company over time") + print(" dcf discounted cash flow of the company over time") + print(" income income statements of the company") + print(" balance balance sheet of the company") + print(" cash cash flow statement of the company") + print(" metrics key metrics of the company") + print(" ratios financial ratios of the company") + print(" growth financial statement growth of the company") + print("") + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + (known_args, other_args) = self.fmp_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_profile(self, other_args: List[str]): + """ Process profile command """ + fmpv.profile(other_args, self.ticker) + + def call_quote(self, other_args: List[str]): + """ Process quote command """ + fmpv.quote(other_args, self.ticker) + + def call_enterprise(self, other_args: List[str]): + """ Process income command """ + fmpv.enterprise(other_args, self.ticker) + + def call_dcf(self, other_args: List[str]): + """ Process dcf command """ + fmpv.discounted_cash_flow(other_args, self.ticker) + + def call_income(self, other_args: List[str]): + """ Process income command """ + fmpv.income_statement(other_args, self.ticker) + + def call_balance(self, other_args: List[str]): + """ Process balance command """ + fmpv.balance_sheet(other_args, self.ticker) + + def call_cash(self, other_args: List[str]): + """ Process cash command """ + fmpv.cash_flow(other_args, self.ticker) + + def call_metrics(self, other_args: List[str]): + """ Process metrics command """ + fmpv.key_metrics(other_args, self.ticker) + + def call_ratios(self, other_args: List[str]): + """ Process cash command """ + fmpv.financial_ratios(other_args, self.ticker) + + def call_growth(self, other_args: List[str]): + """ Process cash command """ + fmpv.financial_statement_growth(other_args, self.ticker) + + +def menu(ticker: str, start: str, interval: str): + """Financial Modeling Prep menu + + Parameters + ---------- + ticker : str + Fundamental analysis ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + + fmp_controller = FinancialModelingPrepController(ticker, start, interval) + fmp_controller.call_help(None) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in fmp_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (fa)>(fmp)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (fa)>(fmp)> ") + + try: + process_input = fmp_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_api.py b/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_view.py similarity index 77% rename from gamestonk_terminal/fundamental_analysis/financial_modeling_prep_api.py rename to gamestonk_terminal/fundamental_analysis/financial_modeling_prep_view.py index 7189fce3fd5..68e22a86281 100644 --- a/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_api.py +++ b/gamestonk_terminal/fundamental_analysis/financial_modeling_prep_view.py @@ -1,139 +1,31 @@ +""" Financial Modeling Prep View """ +__docformat__ = "numpy" + import argparse +from typing import List from datetime import datetime import pandas as pd import FundamentalAnalysis as fa # Financial Modeling Prep -from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.dataframe_helpers import clean_df_index from gamestonk_terminal.helper_funcs import ( long_number_format, check_positive, parse_known_args_and_warn, - get_flair, ) -from gamestonk_terminal.menu import session -def print_menu(s_ticker, s_start, s_interval): - """ Print help """ +def profile(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker profile - s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ - if s_start: - print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") - else: - print(f"\n{s_intraday} Stock: {s_ticker}") - - print("\nFinancial Modeling Prep API") - print(" help show this financial modeling prep menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" profile profile of the company") - print(" quote quote of the company") - print(" enterprise enterprise value of the company over time") - print(" dcf discounted cash flow of the company over time") - print(" income income statements of the company") - print(" balance balance sheet of the company") - print(" cash cash flow statement of the company") - print(" metrics key metrics of the company") - print(" ratios financial ratios of the company") - print(" growth financial statement growth of the company") - print("") - - -def menu(s_ticker, s_start, s_interval): - - # Add list of arguments that the fundamental analysis parser accepts - fmp_parser = argparse.ArgumentParser(prog="fmp", add_help=False) - choices = [ - "help", - "q", - "quit", - "profile", - "quote", - "enterprise", - "dcf", - "fmp_income", - "fmp_balance", - "fmp_cash", - "metrics", - "ratios", - "growth", - ] - fmp_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - - print_menu(s_ticker, s_start, s_interval) - - # Loop forever and ever - while True: - # Get input command from user - if session: - as_input = session.prompt( - f"{get_flair()} (fa)>(fmp)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (fa)>(av)> ") - - # Parse alpha vantage command of the list of possible commands - try: - (ns_known_args, l_args) = fmp_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - print_menu(s_ticker, s_start, s_interval) - - elif ns_known_args.cmd == "q": - # Just leave the menu - return False - - elif ns_known_args.cmd == "quit": - # Abandon the program - return True - - # Details: - elif ns_known_args.cmd == "profile": - profile(l_args, s_ticker) - - elif ns_known_args.cmd == "quote": - quote(l_args, s_ticker) - - elif ns_known_args.cmd == "enterprise": - enterprise(l_args, s_ticker) - - elif ns_known_args.cmd == "dcf": - discounted_cash_flow(l_args, s_ticker) - - # Financial statement: - elif ns_known_args.cmd == "income": - income_statement(l_args, s_ticker) - - elif ns_known_args.cmd == "balance": - balance_sheet(l_args, s_ticker) - - elif ns_known_args.cmd == "cash": - cash_flow(l_args, s_ticker) - - # Ratios: - elif ns_known_args.cmd == "metrics": - key_metrics(l_args, s_ticker) - - elif ns_known_args.cmd == "ratios": - financial_ratios(l_args, s_ticker) - - elif ns_known_args.cmd == "growth": - financial_statement_growth(l_args, s_ticker) - - else: - print("Command not recognized!") - - -def profile(l_args, s_ticker): parser = argparse.ArgumentParser( add_help=False, prog="profile", @@ -148,11 +40,11 @@ def profile(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_fa = fa.profile(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.profile(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) clean_df_index(df_fa) print(df_fa.drop(index=["Description", "Image"]).to_string(header=False)) @@ -166,7 +58,17 @@ def profile(l_args, s_ticker): return -def quote(l_args, s_ticker): +def quote(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker quote + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="quote", @@ -181,11 +83,11 @@ def quote(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_fa = fa.quote(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.quote(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) clean_df_index(df_fa) @@ -211,7 +113,17 @@ def quote(l_args, s_ticker): return -def enterprise(l_args, s_ticker): +def enterprise(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker enterprise + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="enterprise", @@ -242,7 +154,7 @@ def enterprise(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -253,10 +165,10 @@ def enterprise(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.enterprise( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.enterprise(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.enterprise(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num, mask=False) @@ -269,7 +181,17 @@ def enterprise(l_args, s_ticker): return -def discounted_cash_flow(l_args, s_ticker): +def discounted_cash_flow(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker discounted cash flow + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="dcf", @@ -298,7 +220,7 @@ def discounted_cash_flow(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -309,10 +231,10 @@ def discounted_cash_flow(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.discounted_cash_flow( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.discounted_cash_flow(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.discounted_cash_flow(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num, mask=False) @@ -325,7 +247,17 @@ def discounted_cash_flow(l_args, s_ticker): return -def income_statement(l_args, s_ticker): +def income_statement(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker income statement + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="inc", @@ -361,7 +293,7 @@ def income_statement(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -372,10 +304,10 @@ def income_statement(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.income_statement( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.income_statement(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.income_statement(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -394,7 +326,17 @@ def income_statement(l_args, s_ticker): return -def balance_sheet(l_args, s_ticker): +def balance_sheet(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker balance sheet + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="bal", @@ -435,7 +377,7 @@ def balance_sheet(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -446,11 +388,11 @@ def balance_sheet(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.balance_sheet_statement( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: df_fa = fa.balance_sheet_statement( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP + ticker, cfg.API_KEY_FINANCIALMODELINGPREP ) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -470,7 +412,17 @@ def balance_sheet(l_args, s_ticker): return -def cash_flow(l_args, s_ticker): +def cash_flow(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker cash flow + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="cash", @@ -509,7 +461,7 @@ def cash_flow(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -520,10 +472,10 @@ def cash_flow(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.cash_flow_statement( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.cash_flow_statement(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.cash_flow_statement(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -542,7 +494,17 @@ def cash_flow(l_args, s_ticker): return -def key_metrics(l_args, s_ticker): +def key_metrics(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker key metrics + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="metrics", @@ -560,7 +522,7 @@ def key_metrics(l_args, s_ticker): Inventory turnover, Market cap, Net current asset value, Net debt to EBITDA, Net income per share, Operating cash flow per share, Payables turnover, Payout ratio, Pb ratio, Pe ratio, Pfcf ratio, Pocfratio, Price to sales ratio, Ptb ratio, Receivables turnover, - Research and ddevelopement to revenue, Return on tangible assets, Revenue per share, + Research and development to revenue, Return on tangible assets, Revenue per share, Roe, Roic, Sales general and administrative to revenue, Shareholders equity per share, Stock based compensation to revenue, Tangible book value per share, and Working capital. [Source: Financial Modeling Prep] @@ -586,7 +548,7 @@ def key_metrics(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -597,10 +559,10 @@ def key_metrics(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.key_metrics( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.key_metrics(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.key_metrics(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -613,7 +575,17 @@ def key_metrics(l_args, s_ticker): return -def financial_ratios(l_args, s_ticker): +def financial_ratios(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker ratios + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="ratios", @@ -658,7 +630,7 @@ def financial_ratios(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -669,10 +641,10 @@ def financial_ratios(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.financial_ratios( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: - df_fa = fa.financial_ratios(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.financial_ratios(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -685,7 +657,17 @@ def financial_ratios(l_args, s_ticker): return -def financial_statement_growth(l_args, s_ticker): +def financial_statement_growth(other_args: List[str], ticker: str): + """Financial Modeling Prep ticker growth + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="growth", @@ -726,7 +708,7 @@ def financial_statement_growth(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -737,11 +719,11 @@ def financial_statement_growth(l_args, s_ticker): if ns_parser.b_quarter: df_fa = fa.financial_statement_growth( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" + ticker, cfg.API_KEY_FINANCIALMODELINGPREP, period="quarter" ) else: df_fa = fa.financial_statement_growth( - s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP + ticker, cfg.API_KEY_FINANCIALMODELINGPREP ) df_fa = clean_metrics_df(df_fa, num=ns_parser.n_num) @@ -756,6 +738,23 @@ def financial_statement_growth(l_args, s_ticker): def clean_metrics_df(df_fa: pd.DataFrame, num: int, mask: bool = True) -> pd.DataFrame: + """Clean metrics data frame + + Parameters + ---------- + df_fa : pd.DataFrame + Metrics data frame + num : int + Number of columns to clean + mask : bool, optional + Apply mask, by default True + + Returns + ------- + pd.DataFrame + Cleaned metrics data frame + """ + df_fa = df_fa.iloc[:, 0:num] if mask: df_fa = df_fa.mask(df_fa.astype(object).eq(num * ["None"])).dropna() diff --git a/gamestonk_terminal/fundamental_analysis/finviz_api.py b/gamestonk_terminal/fundamental_analysis/finviz_view.py similarity index 78% rename from gamestonk_terminal/fundamental_analysis/finviz_api.py rename to gamestonk_terminal/fundamental_analysis/finviz_view.py index 3bdc999ff62..59fa1679498 100644 --- a/gamestonk_terminal/fundamental_analysis/finviz_api.py +++ b/gamestonk_terminal/fundamental_analysis/finviz_view.py @@ -1,10 +1,25 @@ +""" FinViz View """ +__docformat__ = "numpy" + import argparse +from typing import List import finviz import pandas as pd + from gamestonk_terminal.helper_funcs import parse_known_args_and_warn -def screener(l_args, s_ticker): +def screener(other_args: List[str], ticker: str): + """FinViz ticker screener + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="screener", @@ -24,11 +39,11 @@ def screener(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - d_finviz_stock = finviz.get_stock(s_ticker) + d_finviz_stock = finviz.get_stock(ticker) df_fa = pd.DataFrame.from_dict( d_finviz_stock, orient="index", columns=["Values"] ) diff --git a/gamestonk_terminal/fundamental_analysis/market_watch_model.py b/gamestonk_terminal/fundamental_analysis/market_watch_model.py new file mode 100644 index 00000000000..fc1715949da --- /dev/null +++ b/gamestonk_terminal/fundamental_analysis/market_watch_model.py @@ -0,0 +1,111 @@ +""" Fundamental Analysis Market Watch Model """ +__docformat__ = "numpy" + +import requests +import pandas as pd +from bs4 import BeautifulSoup + +from gamestonk_terminal.helper_funcs import ( + get_user_agent, +) + + +def prepare_df_financials( + ticker: str, statement: str, quarter: bool = False +) -> pd.DataFrame: + """Builds a DataFrame with financial statements for a given company + + Parameters + ---------- + ticker : str + Company's stock ticker + statement : str + Either income, balance or cashflow + quarter : bool, optional + Return quarterly financial statements instead of annual, by default False + + Returns + ------- + pd.DataFrame + A DataFrame with financial info + + Raises + ------ + ValueError + If statement is not income, balance or cashflow + """ + financial_urls = { + "income": { + "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/income/quarter", + "annual": "https://www.marketwatch.com/investing/stock/{}/financials/income", + }, + "balance": { + "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/balance-sheet/quarter", + "annual": "https://www.marketwatch.com/investing/stock/{}/financials/balance-sheet", + }, + "cashflow": { + "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/cash-flow/quarter", + "annual": "https://www.marketwatch.com/investing/stock/{}/financials/cash-flow", + }, + } + + if statement not in financial_urls.keys(): + raise ValueError(f"type {statement} is not in {financial_urls.keys()}") + + if quarter: + period = "quarter" + else: + period = "annual" + + text_soup_financials = BeautifulSoup( + requests.get( + financial_urls[statement][period].format(ticker), + headers={"User-Agent": get_user_agent()}, + ).text, + "lxml", + ) + + # Define financials columns + a_financials_header = list() + for financials_header in text_soup_financials.findAll( + "th", {"class": "overflow__heading"} + ): + a_financials_header.append(financials_header.text.strip("\n").split("\n")[0]) + + s_header_end_trend = ("5-year trend", "5- qtr trend")[quarter] + df_financials = pd.DataFrame( + columns=a_financials_header[0 : a_financials_header.index(s_header_end_trend)] + ) + + find_table = text_soup_financials.findAll( + "div", {"class": "element element--table table--fixed financials"} + ) + + if not find_table: + return df_financials + + financials_rows = find_table[0].findAll( + "tr", {"class": ["table__row is-highlighted", "table__row"]} + ) + + for a_row in financials_rows: + constructed_row = list() + financial_columns = a_row.findAll( + "td", {"class": ["overflow__cell", "overflow__cell fixed--column"]} + ) + + if not financial_columns: + continue + + for a_column in financial_columns: + column_to_text = a_column.text.strip() + if "\n" in column_to_text: + column_to_text = column_to_text.split("\n")[0] + + if column_to_text == "": + continue + constructed_row.append(column_to_text) + + df_financials.loc[len(df_financials)] = constructed_row + + return df_financials diff --git a/gamestonk_terminal/fundamental_analysis/market_watch_api.py b/gamestonk_terminal/fundamental_analysis/market_watch_view.py similarity index 70% rename from gamestonk_terminal/fundamental_analysis/market_watch_api.py rename to gamestonk_terminal/fundamental_analysis/market_watch_view.py index db94fcf0fbe..795f88e7b7c 100644 --- a/gamestonk_terminal/fundamental_analysis/market_watch_api.py +++ b/gamestonk_terminal/fundamental_analysis/market_watch_view.py @@ -1,12 +1,12 @@ -""" Fundamental Analysis Market Watch API """ +""" Fundamental Analysis Market Watch View """ __docformat__ = "numpy" import argparse -import requests +from typing import List import pandas as pd -from bs4 import BeautifulSoup + +from gamestonk_terminal.fundamental_analysis import market_watch_model as mwm from gamestonk_terminal.helper_funcs import ( - get_user_agent, parse_known_args_and_warn, patch_pandas_text_adjustment, financials_colored_values, @@ -14,7 +14,17 @@ from gamestonk_terminal.helper_funcs import ( from gamestonk_terminal import feature_flags as gtff -def income(l_args, s_ticker): +def income(other_args: List[str], ticker: str): + """Market Watch ticker income statement + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="income", @@ -50,11 +60,11 @@ def income(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_financials = prepare_df_financials(s_ticker, "income", ns_parser.b_quarter) + df_financials = mwm.prepare_df_financials(ticker, "income", ns_parser.b_quarter) if gtff.USE_COLOR: df_financials = df_financials.applymap(financials_colored_values) @@ -72,7 +82,17 @@ def income(l_args, s_ticker): return -def balance(l_args, s_ticker): +def balance(other_args: List[str], ticker: str): + """Market Watch ticker balance statement + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="balance", @@ -120,11 +140,13 @@ def balance(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_financials = prepare_df_financials(s_ticker, "balance", ns_parser.b_quarter) + df_financials = mwm.prepare_df_financials( + ticker, "balance", ns_parser.b_quarter + ) if gtff.USE_COLOR: df_financials = df_financials.applymap(financials_colored_values) @@ -142,7 +164,17 @@ def balance(l_args, s_ticker): return -def cash(l_args, s_ticker): +def cash(other_args: List[str], ticker: str): + """Market Watch ticker cash flow statement + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="cash_flow", @@ -186,11 +218,13 @@ def cash(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_financials = prepare_df_financials(s_ticker, "cashflow", ns_parser.b_quarter) + df_financials = mwm.prepare_df_financials( + ticker, "cashflow", ns_parser.b_quarter + ) if gtff.USE_COLOR: df_financials = df_financials.applymap(financials_colored_values) @@ -206,104 +240,3 @@ def cash(l_args, s_ticker): print(e) print("") return - - -def prepare_df_financials( - ticker: str, statement: str, quarter: bool = False -) -> pd.DataFrame: - """Builds a DataFrame with financial statements for a given company - - Parameters - ---------- - ticker : str - Company's stock ticker - statement : str - Either income, balance or cashflow - quarter : bool, optional - Return quarterly financial statements instead of annual, by default False - - Returns - ------- - pd.DataFrame - A DataFrame with financial info - - Raises - ------ - ValueError - If statement is not income, balance or cashflow - """ - financial_urls = { - "income": { - "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/income/quarter", - "annual": "https://www.marketwatch.com/investing/stock/{}/financials/income", - }, - "balance": { - "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/balance-sheet/quarter", - "annual": "https://www.marketwatch.com/investing/stock/{}/financials/balance-sheet", - }, - "cashflow": { - "quarter": "https://www.marketwatch.com/investing/stock/{}/financials/cash-flow/quarter", - "annual": "https://www.marketwatch.com/investing/stock/{}/financials/cash-flow", - }, - } - - if statement not in financial_urls.keys(): - raise ValueError(f"type {statement} is not in {financial_urls.keys()}") - - if quarter: - period = "quarter" - else: - period = "annual" - - text_soup_financials = BeautifulSoup( - requests.get( - financial_urls[statement][period].format(ticker), - headers={"User-Agent": get_user_agent()}, - ).text, - "lxml", - ) - - # Define financials columns - a_financials_header = list() - for financials_header in text_soup_financials.findAll( - "th", {"class": "overflow__heading"} - ): - a_financials_header.append(financials_header.text.strip("\n").split("\n")[0]) - - s_header_end_trend = ("5-year trend", "5- qtr trend")[quarter] - df_financials = pd.DataFrame( - columns=a_financials_header[0 : a_financials_header.index(s_header_end_trend)] - ) - - find_table = text_soup_financials.findAll( - "div", {"class": "element element--table table--fixed financials"} - ) - - if not find_table: - return df_financials - - financials_rows = find_table[0].findAll( - "tr", {"class": ["table__row is-highlighted", "table__row"]} - ) - - for a_row in financials_rows: - constructed_row = list() - financial_columns = a_row.findAll( - "td", {"class": ["overflow__cell", "overflow__cell fixed--column"]} - ) - - if not financial_columns: - continue - - for a_column in financial_columns: - column_to_text = a_column.text.strip() - if "\n" in column_to_text: - column_to_text = column_to_text.split("\n")[0] - - if column_to_text == "": - continue - constructed_row.append(column_to_text) - - df_financials.loc[len(df_financials)] = constructed_row - - return df_financials diff --git a/gamestonk_terminal/fundamental_analysis/yahoo_finance_api.py b/gamestonk_terminal/fundamental_analysis/yahoo_finance_view.py similarity index 85% rename from gamestonk_terminal/fundamental_analysis/yahoo_finance_api.py rename to gamestonk_terminal/fundamental_analysis/yahoo_finance_view.py index f7eef62eb8a..d2192875f17 100644 --- a/gamestonk_terminal/fundamental_analysis/yahoo_finance_api.py +++ b/gamestonk_terminal/fundamental_analysis/yahoo_finance_view.py @@ -1,7 +1,12 @@ +""" Yahoo Finance View """ +__docformat__ = "numpy" + import argparse +from typing import List from datetime import datetime import yfinance as yf import pandas as pd + from gamestonk_terminal.dataframe_helpers import clean_df_index from gamestonk_terminal.helper_funcs import ( long_number_format, @@ -9,7 +14,17 @@ from gamestonk_terminal.helper_funcs import ( ) -def info(l_args, s_ticker): +def info(other_args: List[str], ticker: str): + """Yahoo Finance ticker info + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="info", @@ -36,11 +51,11 @@ def info(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - stock = yf.Ticker(s_ticker) + stock = yf.Ticker(ticker) df_info = pd.DataFrame(stock.info.items(), columns=["Metric", "Value"]) df_info = df_info.set_index("Metric") @@ -86,7 +101,17 @@ def info(l_args, s_ticker): return -def shareholders(l_args, s_ticker): +def shareholders(other_args: List[str], ticker: str): + """Yahoo Finance ticker shareholders + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="shrs", @@ -95,11 +120,11 @@ def shareholders(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - stock = yf.Ticker(s_ticker) + stock = yf.Ticker(ticker) pd.set_option("display.max_colwidth", None) # Major holders @@ -154,7 +179,17 @@ def shareholders(l_args, s_ticker): return -def sustainability(l_args, s_ticker): +def sustainability(other_args: List[str], ticker: str): + """Yahoo Finance ticker sustainability + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="sust", @@ -169,17 +204,17 @@ def sustainability(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - stock = yf.Ticker(s_ticker) + stock = yf.Ticker(ticker) pd.set_option("display.max_colwidth", None) df_sustainability = stock.sustainability if not df_sustainability: - print(f"No sustainability information in Yahoo for {s_ticker}") + print(f"No sustainability information in Yahoo for {ticker}") print("") return @@ -209,7 +244,17 @@ def sustainability(l_args, s_ticker): return -def calendar_earnings(l_args, s_ticker): +def calendar_earnings(other_args: List[str], ticker: str): + """Yahoo Finance ticker calendar earnings + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Fundamental analysis ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="cal", @@ -220,15 +265,15 @@ def calendar_earnings(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - stock = yf.Ticker(s_ticker) + stock = yf.Ticker(ticker) df_calendar = stock.calendar if df_calendar.empty: - print(f"No earnings calendar information in Yahoo for {s_ticker}") + print(f"No earnings calendar information in Yahoo for {ticker}") print("") return diff --git a/terminal.py b/terminal.py index 3db2b090158..6f637eba85e 100644 --- a/terminal.py +++ b/terminal.py @@ -16,7 +16,7 @@ from gamestonk_terminal import thought_of_the_day as thought from gamestonk_terminal import res_menu as rm from gamestonk_terminal.discovery import disc_controller from gamestonk_terminal.due_diligence import dd_controller -from gamestonk_terminal.fundamental_analysis import fa_menu as fam +from gamestonk_terminal.fundamental_analysis import fa_controller from gamestonk_terminal.helper_funcs import b_is_stock_market_open, get_flair from gamestonk_terminal.main_helper import clear, export, load, print_help, view, candle from gamestonk_terminal.menu import session @@ -211,7 +211,7 @@ def main(): b_quit = ca_controller.menu(df_stock, s_ticker, s_start, s_interval) elif ns_known_args.opt == "fa": - b_quit = fam.fa_menu( + b_quit = fa_controller.menu( s_ticker.split(".")[0] if "." in s_ticker else s_ticker, s_start, s_interval, @@ -329,7 +329,7 @@ def main(): df_stock_pred, s_ticker.split(".")[0] if "." in s_ticker else s_ticker, s_start, - s_interval="1440min", + interval="1440min", ) except Exception as e: print(e) diff --git a/tests/data/fa_market_watch_api.yaml b/tests/data/fa_market_watch_model.yaml similarity index 100% rename from tests/data/fa_market_watch_api.yaml rename to tests/data/fa_market_watch_model.yaml diff --git a/tests/test_fa_business_insider_api.py b/tests/test_fa_business_insider_view.py similarity index 69% rename from tests/test_fa_business_insider_api.py rename to tests/test_fa_business_insider_view.py index 35d7c23205c..b9d35af0af9 100644 --- a/tests/test_fa_business_insider_api.py +++ b/tests/test_fa_business_insider_view.py @@ -1,7 +1,7 @@ """ fundamental_analysis/business_insider_api.py tests """ import unittest -from gamestonk_terminal.fundamental_analysis.business_insider_api import management +from gamestonk_terminal.fundamental_analysis.business_insider_view import management class TestFaBusinessInsiderApi(unittest.TestCase): diff --git a/tests/test_fa_market_watch_api_unit.py b/tests/test_fa_market_watch_model.py similarity index 86% rename from tests/test_fa_market_watch_api_unit.py rename to tests/test_fa_market_watch_model.py index b48d03445d5..4e671236f00 100644 --- a/tests/test_fa_market_watch_api_unit.py +++ b/tests/test_fa_market_watch_model.py @@ -8,7 +8,7 @@ from gamestonk_terminal.test_helper import ( # noqa: F401 pytest_generate_tests, ) -from gamestonk_terminal.fundamental_analysis.market_watch_api import ( +from gamestonk_terminal.fundamental_analysis.market_watch_model import ( prepare_df_financials, ) @@ -16,10 +16,10 @@ assertions = unittest.TestCase("__init__") class TestFaMarketWatchApiUnit: - @mock.patch("gamestonk_terminal.fundamental_analysis.market_watch_api.requests") + @mock.patch("gamestonk_terminal.fundamental_analysis.market_watch_model.requests") @parameterize_from_file( "test_prepare_df_financials", - "../tests/data/fa_market_watch_api.yaml", + "../tests/data/fa_market_watch_model.yaml", ) # pylint: disable=too-many-arguments def test_prepare_df_financials( diff --git a/tests/test_fa_market_watch_api.py b/tests/test_fa_market_watch_view.py similarity index 69% rename from tests/test_fa_market_watch_api.py rename to tests/test_fa_market_watch_view.py index da8057c5d38..9ebe411fdb3 100644 --- a/tests/test_fa_market_watch_api.py +++ b/tests/test_fa_market_watch_view.py @@ -1,7 +1,7 @@ """ fundamental_analysis/market_watch_api.py tests """ import unittest -from gamestonk_terminal.fundamental_analysis.market_watch_api import income +from gamestonk_terminal.fundamental_analysis.market_watch_view import income class TestFaMarketWatchApi(unittest.TestCase): diff --git a/tests/test_fa_yahoo_finance_api.py b/tests/test_fa_yahoo_finance_view.py similarity index 84% rename from tests/test_fa_yahoo_finance_api.py rename to tests/test_fa_yahoo_finance_view.py index 211c2f006d4..fb068647595 100644 --- a/tests/test_fa_yahoo_finance_api.py +++ b/tests/test_fa_yahoo_finance_view.py @@ -2,7 +2,7 @@ import unittest # pylint: disable=unused-import -from gamestonk_terminal.fundamental_analysis.yahoo_finance_api import ( # noqa: F401 +from gamestonk_terminal.fundamental_analysis.yahoo_finance_view import ( # noqa: F401 info, sustainability, calendar_earnings,