From 1660fe55e5bfb3657d7da7bf3c43317c6fdba445 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Sat, 21 Aug 2021 19:17:12 +0100 Subject: [PATCH] Refactor due-diligence menu to new refactoring style (#681) * refactor finviz * start refactor business insider * fix business insider new refactoring * refactor FMP view * refactor market watch in due diligence * refactor finnhub on dd * CSI market in dd refactored * fix test * fix dd report * fix test * fix test * fix test * fix test * fix rating yaml * try to fix the test_rating * try for the 1000time * remove the test for now... * fix again requirements.txt * refactor code after review * merge main to ref-dd branch --- .../due_diligence/business_insider_model.py | 201 ++ .../due_diligence/business_insider_view.py | 339 +--- .../stocks/due_diligence/csimarket_model.py | 65 + .../stocks/due_diligence/csimarket_view.py | 101 +- .../stocks/due_diligence/dd_controller.py | 378 +++- .../financial_modeling_prep_view.py | 78 - .../stocks/due_diligence/finnhub_model.py | 28 + .../stocks/due_diligence/finnhub_view.py | 112 +- .../stocks/due_diligence/finviz_model.py | 43 + .../stocks/due_diligence/finviz_view.py | 157 +- .../stocks/due_diligence/fmp_model.py | 22 + .../stocks/due_diligence/fmp_view.py | 52 + .../stocks/due_diligence/marketwatch_model.py | 64 + .../stocks/due_diligence/marketwatch_view.py | 41 + notebooks/templates/due_diligence.ipynb | 47 +- .../test_financial_model/test_rating.yaml | 39 - .../test_main_helper/test_check_api_keys.yaml | 1621 +++++++++++++++++ .../test_dd_financial_modeling_prep_view.py | 14 - tests/test_dd/test_dd_finviz_view.py | 5 +- 19 files changed, 2713 insertions(+), 694 deletions(-) create mode 100644 gamestonk_terminal/stocks/due_diligence/business_insider_model.py create mode 100644 gamestonk_terminal/stocks/due_diligence/csimarket_model.py delete mode 100644 gamestonk_terminal/stocks/due_diligence/financial_modeling_prep_view.py create mode 100644 gamestonk_terminal/stocks/due_diligence/finnhub_model.py create mode 100644 gamestonk_terminal/stocks/due_diligence/finviz_model.py create mode 100644 gamestonk_terminal/stocks/due_diligence/fmp_model.py create mode 100644 gamestonk_terminal/stocks/due_diligence/fmp_view.py create mode 100644 gamestonk_terminal/stocks/due_diligence/marketwatch_model.py create mode 100644 gamestonk_terminal/stocks/due_diligence/marketwatch_view.py delete mode 100644 tests/cassettes/test_dd/test_financial_model/test_rating.yaml create mode 100644 tests/cassettes/test_main/test_main_helper/test_check_api_keys.yaml delete mode 100644 tests/test_dd/test_dd_financial_modeling_prep_view.py diff --git a/gamestonk_terminal/stocks/due_diligence/business_insider_model.py b/gamestonk_terminal/stocks/due_diligence/business_insider_model.py new file mode 100644 index 00000000000..980d74c13e3 --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/business_insider_model.py @@ -0,0 +1,201 @@ +""" Business Insider Model """ +__docformat__ = "numpy" + +import json +import re +from typing import Tuple +import requests +import pandas as pd +from bs4 import BeautifulSoup +from gamestonk_terminal.helper_funcs import ( + get_user_agent, +) + + +def get_price_target_from_analysts(ticker: str) -> pd.DataFrame: + """Get analysts' price targets for a given stock. [Source: Business Insider] + + Parameters + ---------- + ticker : str + Ticker symbol + + Returns + ------- + pd.DataFrame + Analysts data + """ + url_market_business_insider = ( + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" + ) + text_soup_market_business_insider = BeautifulSoup( + requests.get( + url_market_business_insider, headers={"User-Agent": get_user_agent()} + ).text, + "lxml", + ) + + d_analyst_data = None + for script in text_soup_market_business_insider.find_all("script"): + # Get Analyst data + if "window.analyseChartConfigs.push" in str(script): + # Extract config data: + s_analyst_data = str(script).split("config: ", 1)[1].split(",\r\n", 1)[0] + d_analyst_data = json.loads(s_analyst_data) + break + + df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"]) # type: ignore + df_analyst_data = df_analyst_data[ + ["DateLabel", "Company", "InternalRating", "PriceTarget"] + ] + df_analyst_data.columns = ["Date", "Company", "Rating", "Price Target"] + # df_analyst_data + df_analyst_data["Rating"].replace( + {"gut": "BUY", "neutral": "HOLD", "schlecht": "SELL"}, inplace=True + ) + df_analyst_data["Date"] = pd.to_datetime(df_analyst_data["Date"]) + df_analyst_data = df_analyst_data.set_index("Date") + + return df_analyst_data + + +def get_estimates(ticker: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: + """Get analysts' estimates for a given ticker. [Source: Business Insider] + + Parameters + ---------- + ticker : str + Ticker to get analysts' estimates + + Returns + ------- + df_year_estimates : pd.DataFrame + Year estimates + df_quarter_earnings : pd.DataFrame + Quarter earnings estimates + df_quarter_revenues : pd.DataFrame + Quarter revenues estimates + """ + url_market_business_insider = ( + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" + ) + text_soup_market_business_insider = BeautifulSoup( + requests.get( + url_market_business_insider, headers={"User-Agent": get_user_agent()} + ).text, + "lxml", + ) + + l_estimates_year_header = list() + l_estimates_quarter_header = list() + for estimates_header in text_soup_market_business_insider.findAll( + "th", {"class": "table__th text-right"} + ): + s_estimates_header = estimates_header.text.strip() + if s_estimates_header.isdigit(): + l_estimates_year_header.append(s_estimates_header) + elif ("in %" not in s_estimates_header) and ("Job" not in s_estimates_header): + l_estimates_quarter_header.append(s_estimates_header) + + l_estimates_year_metric = list() + for estimates_year_metric in text_soup_market_business_insider.findAll( + "td", {"class": "table__td black"} + ): + l_estimates_year_metric.append(estimates_year_metric.text) + + l_estimates_quarter_metric = list() + for estimates_quarter_metric in text_soup_market_business_insider.findAll( + "td", {"class": "table__td font-color-dim-gray"} + ): + l_estimates_quarter_metric.append(estimates_quarter_metric.text) + + d_metric_year = dict() + d_metric_quarter_earnings = dict() + d_metric_quarter_revenues = dict() + l_metrics = list() + n_metrics = 0 + b_year = True + for idx, metric_value in enumerate( + text_soup_market_business_insider.findAll( + "td", {"class": "table__td text-right"} + ) + ): + if b_year: + # YEAR metrics + l_metrics.append(metric_value.text.strip()) + + # Check if we have processed all year metrics + if n_metrics > len(l_estimates_year_metric) - 1: + b_year = False + n_metrics = 0 + l_metrics = list() + idx_y = idx + + # Add value to dictionary + if (idx + 1) % len(l_estimates_year_header) == 0: + d_metric_year[l_estimates_year_metric[n_metrics]] = l_metrics + l_metrics = list() + n_metrics += 1 + + if not b_year: + # QUARTER metrics + l_metrics.append(metric_value.text.strip()) + + # Check if we have processed all quarter metrics + if n_metrics > len(l_estimates_quarter_metric) - 1: + break + + # Add value to dictionary + if (idx - idx_y + 1) % len(l_estimates_quarter_header) == 0: + if n_metrics < 4: + d_metric_quarter_earnings[ + l_estimates_quarter_metric[n_metrics] + ] = l_metrics + else: + d_metric_quarter_revenues[ + l_estimates_quarter_metric[n_metrics - 4] + ] = l_metrics + l_metrics = list() + n_metrics += 1 + + df_year_estimates = pd.DataFrame.from_dict( + d_metric_year, orient="index", columns=l_estimates_year_header + ) + df_year_estimates.index.name = "YEARLY ESTIMATES" + df_quarter_earnings = pd.DataFrame.from_dict( + d_metric_quarter_earnings, + orient="index", + columns=l_estimates_quarter_header, + ) + # df_quarter_earnings.index.name = 'Earnings' + df_quarter_revenues = pd.DataFrame.from_dict( + d_metric_quarter_revenues, + orient="index", + columns=l_estimates_quarter_header, + ) + # df_quarter_revenues.index.name = 'Revenues' + + l_quarter = list() + l_date = list() + for quarter_title in df_quarter_earnings.columns: + l_quarter.append(re.split(" ending", quarter_title)[0]) + if len(re.split(" ending", quarter_title)) == 2: + l_date.append("ending " + re.split(" ending", quarter_title)[1].strip()) + else: + l_date.append("-") + + df_quarter_earnings.index.name = "QUARTER EARNINGS ESTIMATES" + df_quarter_earnings.columns = l_quarter + df_quarter_earnings.loc["Date"] = l_date + df_quarter_earnings = df_quarter_earnings.reindex( + ["Date", "No. of Analysts", "Average Estimate", "Year Ago", "Publish Date"] + ) + + df_quarter_revenues.index.name = "QUARTER REVENUES ESTIMATES" + df_quarter_revenues.columns = l_quarter + df_quarter_revenues.loc["Date"] = l_date + df_quarter_revenues = df_quarter_revenues.reindex( + ["Date", "No. of Analysts", "Average Estimate", "Year Ago", "Publish Date"] + ) + + return df_year_estimates, df_quarter_earnings, df_quarter_revenues diff --git a/gamestonk_terminal/stocks/due_diligence/business_insider_view.py b/gamestonk_terminal/stocks/due_diligence/business_insider_view.py index 5398af16321..978c3c143b2 100644 --- a/gamestonk_terminal/stocks/due_diligence/business_insider_view.py +++ b/gamestonk_terminal/stocks/due_diligence/business_insider_view.py @@ -1,20 +1,14 @@ """ Business Insider View """ __docformat__ = "numpy" -import argparse -from typing import List -import json -import re +import os from pandas.core.frame import DataFrame -import requests -import matplotlib.pyplot as plt -import pandas as pd from pandas.plotting import register_matplotlib_converters -from bs4 import BeautifulSoup +import matplotlib.pyplot as plt +from tabulate import tabulate +from gamestonk_terminal.stocks.due_diligence import business_insider_model from gamestonk_terminal.helper_funcs import ( - check_positive, - get_user_agent, - parse_known_args_and_warn, + export_data, ) from gamestonk_terminal import feature_flags as gtff @@ -22,78 +16,49 @@ register_matplotlib_converters() def price_target_from_analysts( - other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str + ticker: str, + start: str, + interval: str, + stock: DataFrame, + num: int, + raw: bool, + export: str, ): - """Display analysts' price targets for a given stock + """Display analysts' price targets for a given stock. [Source: Business Insider] Parameters ---------- - other_args : List[str] - argparse other args - ["-n", "10"] - stock : DataFrame - Due diligence stock dataframe ticker : str Due diligence ticker symbol start : str Start date of the stock data interval : str Stock data interval + stock : DataFrame + Due diligence stock dataframe + num : int + Number of latest price targets from analysts to print + raw : bool + Display raw data only + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - add_help=False, - prog="pt", - description="""Prints price target from analysts. [Source: Business Insider]""", - ) + df_analyst_data = business_insider_model.get_price_target_from_analysts(ticker) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="number of latest price targets from analysts to print.", - ) - - try: - 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/{ticker.lower()}-stock" - ) - text_soup_market_business_insider = BeautifulSoup( - requests.get( - url_market_business_insider, headers={"User-Agent": get_user_agent()} - ).text, - "lxml", + if raw: + df_analyst_data.index = df_analyst_data.index.strftime("%d/%m/%Y") + print( + tabulate( + df_analyst_data.sort_index(ascending=False).head(num), + headers=df_analyst_data.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ) ) - d_analyst_data = None - for script in text_soup_market_business_insider.find_all("script"): - # Get Analyst data - if "window.analyseChartConfigs.push" in str(script): - # Extract config data: - s_analyst_data = ( - str(script).split("config: ", 1)[1].split(",\r\n", 1)[0] - ) - d_analyst_data = json.loads(s_analyst_data) - break - - df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"]) # type: ignore - df_analyst_data = df_analyst_data[ - ["DateLabel", "Company", "InternalRating", "PriceTarget"] - ] - df_analyst_data.columns = ["Date", "Company", "Rating", "Price Target"] - # df_analyst_data - df_analyst_data["Rating"].replace( - {"gut": "BUY", "neutral": "HOLD", "schlecht": "SELL"}, inplace=True - ) - df_analyst_data["Date"] = pd.to_datetime(df_analyst_data["Date"]) - df_analyst_data = df_analyst_data.set_index("Date") - + else: # Slice start of ratings if start: df_analyst_data = df_analyst_data[start:] # type: ignore @@ -116,195 +81,85 @@ def price_target_from_analysts( plt.title(f"{ticker} (Time Series) and Price Target") plt.xlim(stock.index[0], stock.index[-1]) plt.xlabel("Time") - plt.ylabel("Share Price ($)") + plt.ylabel("Share Price") plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) if gtff.USE_ION: plt.ion() plt.show() - print("") + print("") - pd.set_option("display.max_colwidth", None) - print( - df_analyst_data.sort_index(ascending=False) - .head(ns_parser.n_num) - .to_string() - ) - print("") - - except Exception as e: - print(e) - print("") - return + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "pt", + df_analyst_data, + ) -def estimates(other_args: List[str], ticker: str): - """Display analysts' estimates for a given ticker +def estimates(ticker: str, export: str): + """Display analysts' estimates for a given ticker. [Source: Business Insider] Parameters ---------- - other_args : List[str] - argparse other args ticker : str - Due diligence ticker symbol + Ticker to get analysts' estimates + export : str + Export dataframe data to csv,json,xlsx file """ + ( + df_year_estimates, + df_quarter_earnings, + df_quarter_revenues, + ) = business_insider_model.get_estimates(ticker) - parser = argparse.ArgumentParser( - add_help=False, - prog="est", - description="""Yearly estimates and quarter earnings/revenues [Source: Business Insider]""", + print( + tabulate( + df_year_estimates, + headers=df_year_estimates.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ), ) - - try: - 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/{ticker.lower()}-stock" - ) - text_soup_market_business_insider = BeautifulSoup( - requests.get( - url_market_business_insider, headers={"User-Agent": get_user_agent()} - ).text, - "lxml", + print("") + print( + tabulate( + df_quarter_earnings, + headers=df_quarter_earnings.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ), + ) + print("") + print( + tabulate( + df_quarter_revenues, + headers=df_quarter_revenues.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", ) + ) + print("") - l_estimates_year_header = [] - l_estimates_quarter_header = [] - for estimates_header in text_soup_market_business_insider.findAll( - "th", {"class": "table__th text-right"} - ): - s_estimates_header = estimates_header.text.strip() - if s_estimates_header.isdigit(): - l_estimates_year_header.append(s_estimates_header) - elif ("in %" not in s_estimates_header) and ( - "Job" not in s_estimates_header - ): - l_estimates_quarter_header.append(s_estimates_header) - - l_estimates_year_metric = [] - for estimates_year_metric in text_soup_market_business_insider.findAll( - "td", {"class": "table__td black"} - ): - l_estimates_year_metric.append(estimates_year_metric.text) - - l_estimates_quarter_metric = [] - for estimates_quarter_metric in text_soup_market_business_insider.findAll( - "td", {"class": "table__td font-color-dim-gray"} - ): - l_estimates_quarter_metric.append(estimates_quarter_metric.text) - - d_metric_year = dict() - d_metric_quarter_earnings = dict() - d_metric_quarter_revenues = dict() - l_metrics = [] - n_metrics = 0 - b_year = True - for idx, metric_value in enumerate( - text_soup_market_business_insider.findAll( - "td", {"class": "table__td text-right"} - ) - ): - - if b_year: - # YEAR metrics - l_metrics.append(metric_value.text.strip()) - - # Check if we have processed all year metrics - if n_metrics > len(l_estimates_year_metric) - 1: - b_year = False - n_metrics = 0 - l_metrics = [] - idx_y = idx - - # Add value to dictionary - if (idx + 1) % len(l_estimates_year_header) == 0: - d_metric_year[l_estimates_year_metric[n_metrics]] = l_metrics - l_metrics = [] - n_metrics += 1 - - if not b_year: - # QUARTER metrics - l_metrics.append(metric_value.text.strip()) - - # Check if we have processed all quarter metrics - if n_metrics > len(l_estimates_quarter_metric) - 1: - break - - # Add value to dictionary - if (idx - idx_y + 1) % len(l_estimates_quarter_header) == 0: - if n_metrics < 4: - d_metric_quarter_earnings[ - l_estimates_quarter_metric[n_metrics] - ] = l_metrics - else: - d_metric_quarter_revenues[ - l_estimates_quarter_metric[n_metrics - 4] - ] = l_metrics - l_metrics = [] - n_metrics += 1 - - df_year_estimates = pd.DataFrame.from_dict( - d_metric_year, orient="index", columns=l_estimates_year_header - ) - df_year_estimates.index.name = "YEARLY ESTIMATES" - df_quarter_earnings = pd.DataFrame.from_dict( - d_metric_quarter_earnings, - orient="index", - columns=l_estimates_quarter_header, - ) - # df_quarter_earnings.index.name = 'Earnings' - df_quarter_revenues = pd.DataFrame.from_dict( - d_metric_quarter_revenues, - orient="index", - columns=l_estimates_quarter_header, - ) - # df_quarter_revenues.index.name = 'Revenues' - - l_quarter = [] - l_date = [] - for quarter_title in df_quarter_earnings.columns: - l_quarter.append(re.split(" ending", quarter_title)[0]) - if len(re.split(" ending", quarter_title)) == 2: - l_date.append( - "ending " + re.split(" ending", quarter_title)[1].strip() - ) - else: - l_date.append("-") - - df_quarter_earnings.columns = l_quarter - df_quarter_earnings.loc["Date"] = l_date - df_quarter_earnings = df_quarter_earnings.reindex( - ["Date", "No. of Analysts", "Average Estimate", "Year Ago", "Publish Date"] - ) - - df_quarter_revenues.columns = l_quarter - df_quarter_revenues.loc["Date"] = l_date - df_quarter_revenues = df_quarter_revenues.reindex( - ["Date", "No. of Analysts", "Average Estimate", "Year Ago", "Publish Date"] - ) - - print(df_year_estimates.to_string()) - print("") - print("QUARTER ESTIMATES EARNINGS") - print(df_quarter_earnings.to_string()) - print("") - print("QUARTER ESTIMATES REVENUES") - print(df_quarter_revenues.to_string()) - print("") - - print( - text_soup_market_business_insider.find( - "div", {"class": "text_right instrument-description"} - ).text.strip() - ) - print("") - - except Exception as e: - print(e) - print("") - return + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "pt_year", + df_year_estimates, + ) + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "pt_qtr_earnings", + df_quarter_earnings, + ) + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "pt_qtr_revenues", + df_quarter_revenues, + ) diff --git a/gamestonk_terminal/stocks/due_diligence/csimarket_model.py b/gamestonk_terminal/stocks/due_diligence/csimarket_model.py new file mode 100644 index 00000000000..1633622debb --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/csimarket_model.py @@ -0,0 +1,65 @@ +"""CSIMarket Model""" +__docformat__ = "numpy" + +from bs4 import BeautifulSoup +import requests + + +def get_suppliers(ticker: str) -> str: + """Get suppliers from ticker provided. [Source: CSIMarket] + + Parameters + ---------- + ticker: str + Ticker to select suppliers from + + Returns + ------- + str + Suppliers for ticker provided + """ + # TODO: This link has a lot more data that we can display + # TODO: We could at least sort the tickers based on market cap + url_supply_chain = ( + f"https://csimarket.com/stocks/competitionNO3.php?supply&code={ticker.upper()}" + ) + text_supplier_chain = BeautifulSoup(requests.get(url_supply_chain).text, "lxml") + + l_supplier = list() + for supplier in text_supplier_chain.findAll( + "td", {"class": "plavat svjetlirub dae al"} + ): + l_supplier.append(supplier.text) + + if l_supplier: + return f"List of Suppliers: {', '.join(l_supplier)}\n" + return "No suppliers found.\n" + + +def get_customers(ticker: str) -> str: + """Print customers from ticker provided + + Parameters + ---------- + ticker: str + Ticker to select customers from + + Returns + ------- + str + Customers for ticker provided + """ + # TODO: This link has a lot more data that we can display + # TODO: We could at least sort the tickers based on market cap + url_customer_chain = ( + f"https://csimarket.com/stocks/custexNO.php?markets&code={ticker.upper()}" + ) + text_customer_chain = BeautifulSoup(requests.get(url_customer_chain).text, "lxml") + + l_customer = list() + for customer in text_customer_chain.findAll("td", {"class": "plava svjetlirub"}): + l_customer.append(customer.text) + + if l_customer: + return f"List of Customers: {', '.join(l_customer)}\n" + return "No customers found.\n" diff --git a/gamestonk_terminal/stocks/due_diligence/csimarket_view.py b/gamestonk_terminal/stocks/due_diligence/csimarket_view.py index 1d815bf2d44..1ff4eb8bbea 100644 --- a/gamestonk_terminal/stocks/due_diligence/csimarket_view.py +++ b/gamestonk_terminal/stocks/due_diligence/csimarket_view.py @@ -1,94 +1,51 @@ -"""Stockgrid DD View""" +"""CSIMarket View""" __docformat__ = "numpy" -import argparse -from typing import List -from bs4 import BeautifulSoup -import requests +import os +import pandas as pd +from gamestonk_terminal.stocks.due_diligence import csimarket_model from gamestonk_terminal.helper_funcs import ( - parse_known_args_and_warn, + export_data, ) -def suppliers(ticker: str, other_args: List[str]): - """Print suppliers from ticker provided +def suppliers(ticker: str, export: str): + """Display suppliers from ticker provided. [Source: CSIMarket] Parameters ---------- ticker: str Ticker to select suppliers from - other_args : List[str] - Argparse arguments - + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - prog="supplier", - add_help=False, - description="List of suppliers from ticker provided. [Source: CSIMarket]", + tickers = csimarket_model.get_suppliers(ticker) + print(tickers) + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "supplier", + pd.DataFrame(tickers.split(",")), ) - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - # TODO: This link has a lot more data that we can display - url_supply_chain = f"https://csimarket.com/stocks/competitionNO3.php?supply&code={ticker.upper()}" - text_supplier_chain = BeautifulSoup(requests.get(url_supply_chain).text, "lxml") - - l_supplier = [] - for supplier in text_supplier_chain.findAll( - "td", {"class": "plavat svjetlirub dae al"} - ): - l_supplier.append(supplier.text) - - if l_supplier: - print("List of Suppliers: " + ", ".join(l_supplier) + "\n") - else: - print("No suppliers found.\n") - - except Exception as e: - print(e, "\n") -def customers(ticker: str, other_args: List[str]): - """Print customers from ticker provided +def customers(ticker: str, export: str): + """Display customers from ticker provided. [Source: CSIMarket] Parameters ---------- ticker: str Ticker to select customers from - other_args : List[str] - Argparse arguments - + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - prog="customer", - add_help=False, - description="List of customers from ticker provided. [Source: CSIMarket]", + tickers = csimarket_model.get_customers(ticker) + print(tickers) + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "customer", + pd.DataFrame(tickers.split(",")), ) - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - # TODO: This link has a lot more data that we can display - url_customer_chain = ( - f"https://csimarket.com/stocks/custexNO.php?markets&code={ticker.upper()}" - ) - text_customer_chain = BeautifulSoup( - requests.get(url_customer_chain).text, "lxml" - ) - - l_customer = [] - for customer in text_customer_chain.findAll( - "td", {"class": "plava svjetlirub"} - ): - l_customer.append(customer.text) - - if l_customer: - print("List of Customers: " + ", ".join(l_customer) + "\n") - else: - print("No customers found.\n") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/due_diligence/dd_controller.py b/gamestonk_terminal/stocks/due_diligence/dd_controller.py index 34f179c85b0..fd8e30f9013 100644 --- a/gamestonk_terminal/stocks/due_diligence/dd_controller.py +++ b/gamestonk_terminal/stocks/due_diligence/dd_controller.py @@ -8,15 +8,19 @@ from pandas.core.frame import DataFrame from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal.stocks.due_diligence import ( - financial_modeling_prep_view, + fmp_view, business_insider_view, finviz_view, - market_watch_view, + marketwatch_view, finnhub_view, csimarket_view, ) from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.helper_funcs import ( + get_flair, + parse_known_args_and_warn, + check_positive, +) from gamestonk_terminal.menu import session @@ -30,30 +34,34 @@ class DueDiligenceController: "help", "q", "quit", + ] + + CHOICES_COMMANDS = [ "sec", "rating", "pt", "rot", "est", "analyst", - "sec", "supplier", "customer", ] + CHOICES += CHOICES_COMMANDS + def __init__(self, ticker: str, start: str, interval: str, stock: DataFrame): """Constructor Parameters ---------- - stock : DataFrame - Due diligence stock dataframe ticker : str Due diligence ticker symbol start : str Start date of the stock data interval : str Stock data interval + stock : DataFrame + Due diligence stock dataframe """ self.ticker = ticker self.start = start @@ -68,39 +76,40 @@ class DueDiligenceController: def print_help(self): """Print help""" - print( - "https://github.com/GamestonkTerminal/GamestonkTerminal/tree/main/gamestonk_terminal/stocks/due_diligence" - ) - 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')})" - ) + s_intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"] + if self.ticker and self.start: + stock_text = f"{s_intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})" else: - print(f"\n{intraday} Stock: {self.ticker}") + stock_text = f"{s_intraday} Stock: {self.ticker}" - print("\nDue Diligence:") - print(" cls clear screen") - print(" ?/help show this menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" analyst analyst prices and ratings of the company [Finviz]") - print( - " rating rating of the company from strong sell to strong buy [FMP]" - ) - print(" pt price targets over time [Business Insider]") - print( - " rot rating over timefrom strong sell to strong buy [Finnhub]" - ) - print( - " est quarter and year analysts earnings estimates [Business Insider]" - ) - print(" sec SEC filings [Market Watch]") - print(" supplier list of suppliers [csimarket]") - print(" customer list of customers [csimarket]") - print("") + help_text = f"""https://github.com/GamestonkTerminal/GamestonkTerminal/tree/main/gamestonk_terminal/stocks/due_diligence + +Due Diligence: + cls clear screen + ?/help show this menu again + q quit this menu, and shows back to main menu + quit quit to abandon program + +{stock_text} + +Finviz: + analyst analyst prices and ratings of the company +FMP: + rating rating over time (daily) +Finnhub: + rot number of analysts ratings over time (monthly) +Business Insider: + pt price targets over time + est quarter and year analysts earnings estimates +Market Watch: + sec SEC filings +csimarket: + supplier list of suppliers + customer list of customers + """ + + print(help_text) def switch(self, an_input: str): """Process and dispatch input @@ -148,37 +157,308 @@ class DueDiligenceController: def call_analyst(self, other_args: List[str]): """Process analyst command""" - finviz_view.analyst(other_args, self.ticker) + parser = argparse.ArgumentParser( + add_help=False, + prog="analyst", + description=""" + Print analyst prices and ratings of the company. The following fields are expected: + date, analyst, category, price from, price to, and rating. [Source: Finviz] + """, + ) + 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 + + finviz_view.analyst(ticker=self.ticker, export=ns_parser.export) + + except Exception as e: + print(e, "\n") def call_pt(self, other_args: List[str]): """Process pt command""" - business_insider_view.price_target_from_analysts( - other_args, self.stock, self.ticker, self.start, self.interval + parser = argparse.ArgumentParser( + add_help=False, + prog="pt", + description="""Prints price target from analysts. [Source: Business Insider]""", + ) + parser.add_argument( + "--raw", + action="store_true", + dest="raw", + help="Only output raw data", + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="Number of latest price targets from analysts to print.", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", ) - def call_rot(self, other_args: List[str]): - """Process rot command""" - finnhub_view.rating_over_time(other_args, self.ticker) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + business_insider_view.price_target_from_analysts( + ticker=self.ticker, + start=self.start, + interval=self.interval, + stock=self.stock, + num=ns_parser.n_num, + raw=ns_parser.raw, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_est(self, other_args: List[str]): """Process est command""" - business_insider_view.estimates(other_args, self.ticker) + parser = argparse.ArgumentParser( + add_help=False, + prog="est", + description="""Yearly estimates and quarter earnings/revenues. [Source: Business Insider]""", + ) + 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 + + business_insider_view.estimates( + ticker=self.ticker, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_rot(self, other_args: List[str]): + """Process rot command""" + parser = argparse.ArgumentParser( + add_help=False, + prog="rot", + description=""" + Rating over time (monthly). [Source: Finnhub] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="number of last months", + ) + parser.add_argument( + "--raw", + action="store_true", + dest="raw", + help="Only output raw data", + ) + 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.rating_over_time( + ticker=self.ticker, + num=ns_parser.n_num, + raw=ns_parser.raw, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_rating(self, other_args: List[str]): """Process rating command""" - financial_modeling_prep_view.rating(other_args, self.ticker) + parser = argparse.ArgumentParser( + add_help=False, + prog="rating", + description=""" + Based on specific ratios, prints information whether the company + is a (strong) buy, neutral or a (strong) sell. The following fields are expected: + P/B, ROA, DCF, P/E, ROE, and D/E. [Source: Financial Modeling Prep] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=10, + help="number of last days to display ratings", + ) + 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 + + fmp_view.rating( + ticker=self.ticker, + num=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_sec(self, other_args: List[str]): """Process sec command""" - market_watch_view.sec_fillings(other_args, self.ticker) + parser = argparse.ArgumentParser( + add_help=False, + prog="sec", + description=""" + Prints SEC filings of the company. The following fields are expected: Filing Date, + Document Date, Type, Category, Amended, and Link. [Source: Market Watch] + """, + ) + parser.add_argument( + "-n", + "--num", + action="store", + dest="n_num", + type=check_positive, + default=5, + help="number of latest SEC filings.", + ) + 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 + + marketwatch_view.sec_filings( + ticker=self.ticker, + num=ns_parser.n_num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_supplier(self, other_args: List[str]): """Process supplier command""" - csimarket_view.suppliers(self.ticker, other_args) + parser = argparse.ArgumentParser( + prog="supplier", + add_help=False, + description="List of suppliers from ticker provided. [Source: CSIMarket]", + ) + 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 + + csimarket_view.suppliers( + ticker=self.ticker, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def call_customer(self, other_args: List[str]): """Process customer command""" - csimarket_view.customers(self.ticker, other_args) + parser = argparse.ArgumentParser( + prog="customer", + add_help=False, + description="List of customers from ticker provided. [Source: CSIMarket]", + ) + 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 + + csimarket_view.customers( + ticker=self.ticker, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") def menu(ticker: str, start: str, interval: str, stock: DataFrame): @@ -186,14 +466,14 @@ def menu(ticker: str, start: str, interval: str, stock: DataFrame): Parameters ---------- - stock : DataFrame - Due diligence stock dataframe ticker : str Due diligence ticker symbol start : str Start date of the stock data interval : str Stock data interval + stock : DataFrame + Due diligence stock dataframe """ dd_controller = DueDiligenceController(ticker, start, interval, stock) diff --git a/gamestonk_terminal/stocks/due_diligence/financial_modeling_prep_view.py b/gamestonk_terminal/stocks/due_diligence/financial_modeling_prep_view.py deleted file mode 100644 index 38b3a4d503f..00000000000 --- a/gamestonk_terminal/stocks/due_diligence/financial_modeling_prep_view.py +++ /dev/null @@ -1,78 +0,0 @@ -""" Financial Modeling Prep View """ -__docformat__ = "numpy" - -import argparse -from typing import List -from tabulate import tabulate -import FundamentalAnalysis as fa -from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, check_positive - - -def rating(other_args: List[str], ticker: str): - """Display ratings for a given ticker - - Parameters - ---------- - other_args : List[str] - argparse other args - ticker : str - Stock ticker - """ - parser = argparse.ArgumentParser( - add_help=False, - prog="rating", - description=""" - Based on specific ratios, prints information whether the company - is a (strong) buy, neutral or a (strong) sell. The following fields are expected: - P/B, ROA, DCF, P/E, ROE, and D/E. [Source: Financial Modeling Prep] - """, - ) - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=10, - help="number of regions to plot that show highest interest.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_fa = fa.rating(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) - - # TODO: This could be displayed in a nice rating plot over time - - l_recoms = [col for col in df_fa.columns if "Recommendation" in col] - l_recoms_show = [ - recom.replace("rating", "") - .replace("Details", "") - .replace("Recommendation", " Recommendation") - for recom in l_recoms - ] - print( - tabulate( - df_fa[l_recoms].head(ns_parser.n_num), - headers=l_recoms_show, - floatfmt=".2f", - showindex=True, - tablefmt="fancy_grid", - ) - ) - print("") - - except KeyError: - print( - f"Financialmodelingprep.com is returning empty response the ticker {ticker}." - ) - print("") - return - - except Exception as e: - print(e) - print("") - return diff --git a/gamestonk_terminal/stocks/due_diligence/finnhub_model.py b/gamestonk_terminal/stocks/due_diligence/finnhub_model.py new file mode 100644 index 00000000000..22fffa1a2fe --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/finnhub_model.py @@ -0,0 +1,28 @@ +""" Finnhub Model """ +__docformat__ = "numpy" + +import requests +import pandas as pd +from gamestonk_terminal import config_terminal as cfg + + +def get_rating_over_time(ticker: str) -> pd.DataFrame: + """Get rating over time data. [Source: Finnhub] + + Parameters + ---------- + ticker : str + Ticker to get ratings from + + Returns + ------- + pd.DataFrame + Get dataframe with ratings + """ + response = requests.get( + f"https://finnhub.io/api/v1/stock/recommendation?symbol={ticker}&token={cfg.API_FINNHUB_KEY}" + ) + if response.status_code == 200: + return pd.DataFrame(response.json()) + + return pd.DataFrame() diff --git a/gamestonk_terminal/stocks/due_diligence/finnhub_view.py b/gamestonk_terminal/stocks/due_diligence/finnhub_view.py index 112032ea462..2dcc75566e4 100644 --- a/gamestonk_terminal/stocks/due_diligence/finnhub_view.py +++ b/gamestonk_terminal/stocks/due_diligence/finnhub_view.py @@ -1,42 +1,19 @@ -import argparse -from typing import List -import requests +""" Finnhub View """ +__docformat__ = "numpy" + +import os +from tabulate import tabulate import pandas as pd from matplotlib import pyplot as plt from pandas.plotting import register_matplotlib_converters -from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal.helper_funcs import ( - parse_known_args_and_warn, - plot_autoscale, -) +from gamestonk_terminal.stocks.due_diligence import finnhub_model +from gamestonk_terminal.helper_funcs import plot_autoscale, export_data from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def get_rating_over_time(ticker: str) -> pd.DataFrame: - """Get rating over time - - Parameters - ---------- - ticker : str - Ticker to get ratings from - - Returns - ------- - pd.DataFrame - Get dataframe with economic calendar events - """ - response = requests.get( - f"https://finnhub.io/api/v1/stock/recommendation?symbol={ticker}&token={cfg.API_FINNHUB_KEY}" - ) - if response.status_code == 200: - return pd.DataFrame(response.json()) - - return pd.DataFrame() - - def plot_rating_over_time(rot: pd.DataFrame, ticker: str): """Plot rating over time @@ -46,7 +23,6 @@ def plot_rating_over_time(rot: pd.DataFrame, ticker: str): Rating over time ticker : str Ticker associated with ratings - """ plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) @@ -65,6 +41,7 @@ def plot_rating_over_time(rot: pd.DataFrame, ticker: str): plt.xlabel("Time") plt.ylabel("Rating") plt.legend(["Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"]) + plt.gcf().autofmt_xdate() if gtff.USE_ION: plt.ion() @@ -72,37 +49,56 @@ def plot_rating_over_time(rot: pd.DataFrame, ticker: str): plt.show() -def rating_over_time(other_args: List[str], ticker: str): - """Rating over time +def rating_over_time(ticker: str, num: int, raw: bool, export: str): + """Rating over time (monthly). [Source: Finnhub] Parameters ---------- - other_args : List[str] - Command line arguments to be processed with argparse ticker : str Ticker to get ratings from + num : int + Number of last months ratings to show + raw : bool + Display raw data only + export : str + Export dataframe data to csv,json,xlsx file """ - parser = argparse.ArgumentParser( - add_help=False, - prog="rot", - description=""" - Rating over time. [Source: https://finnhub.io] - """, + df_rot = finnhub_model.get_rating_over_time(ticker) + + if df_rot.empty: + print("No ratings over time found", "\n") + return + + if raw: + d_cols = { + "strongSell": "Strong Sell", + "sell": "Sell", + "hold": "Hold", + "buy": "Buy", + "strongBuy": "Strong Buy", + } + df_rot_raw = ( + df_rot[["period", "strongSell", "sell", "hold", "buy", "strongBuy"]] + .rename(columns=d_cols) + .head(num) + ) + print( + tabulate( + df_rot_raw, + headers=df_rot_raw.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ) + ) + else: + plot_rating_over_time(df_rot.head(num), ticker) + + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "rot", + df_rot, ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_rot = get_rating_over_time(ticker) - - if df_rot.empty: - print("No ratings over time found\n") - return - - plot_rating_over_time(df_rot, ticker) - print("") - - except Exception as e: - print(e, "\n") diff --git a/gamestonk_terminal/stocks/due_diligence/finviz_model.py b/gamestonk_terminal/stocks/due_diligence/finviz_model.py new file mode 100644 index 00000000000..4a457a3515e --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/finviz_model.py @@ -0,0 +1,43 @@ +""" Finviz Model """ +__docformat__ = "numpy" + +from typing import Dict +import finviz +import pandas as pd +from pandas.core.frame import DataFrame + + +def get_news(ticker: str) -> Dict: + """Get news from Finviz + + Parameters + ---------- + ticker : str + Stock ticker + + Returns + ------- + Dict + News + """ + return finviz.get_news(ticker) + + +def get_analyst_data(ticker: str) -> DataFrame: + """Get analyst data. [Source: Finviz] + + Parameters + ---------- + ticker : str + Stock ticker + + Returns + ------- + df_fa: DataFrame + Analyst price targets + """ + d_finviz_analyst_price = finviz.get_analyst_price_targets(ticker) + df_fa = pd.DataFrame.from_dict(d_finviz_analyst_price) + df_fa.set_index("date", inplace=True) + + return df_fa diff --git a/gamestonk_terminal/stocks/due_diligence/finviz_view.py b/gamestonk_terminal/stocks/due_diligence/finviz_view.py index 1329487f07d..0e5119e495a 100644 --- a/gamestonk_terminal/stocks/due_diligence/finviz_view.py +++ b/gamestonk_terminal/stocks/due_diligence/finviz_view.py @@ -1,17 +1,12 @@ -""" FinViz View """ +""" Finviz View """ __docformat__ = "numpy" -import argparse -from typing import List +import os from colorama import Fore, Style -import finviz -import pandas as pd -from pandas.core.frame import DataFrame -from gamestonk_terminal.helper_funcs import ( - check_positive, - patch_pandas_text_adjustment, - parse_known_args_and_warn, -) +from tabulate import tabulate +from gamestonk_terminal.stocks.due_diligence import finviz_model +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal import feature_flags as gtff def category_color_red_green(val: str) -> str: @@ -32,133 +27,63 @@ def category_color_red_green(val: str) -> str: return Fore.GREEN + val + Style.RESET_ALL if val == "Downgrade": return Fore.RED + val + Style.RESET_ALL + if val == "Reiterated": + return Fore.YELLOW + val + Style.RESET_ALL return val -def news(other_args: List[str], ticker: str): +def news(ticker: str, num: int): """Display news for a given stock ticker Parameters ---------- - other_args : List[str] - argparse other args - ["-n", "10"] ticker : str Stock ticker + num : int + Number of latest news being printed """ + d_finviz_news = finviz_model.get_news(ticker) + i = 0 + for s_news_title, s_news_link in {*d_finviz_news}: + print(f"-> {s_news_title}") + print(f"{s_news_link}\n") + i += 1 - parser = argparse.ArgumentParser( - add_help=False, - prog="news", - description=""" - Prints latest news about company, including title and web link. [Source: Finviz] - """, - ) + if i > (num - 1): + break - parser.add_argument( - "-n", - "--num", - action="store", - dest="n_num", - type=check_positive, - default=5, - help="Number of latest news being printed.", - ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - d_finviz_news = finviz.get_news(ticker) - i = 0 - for s_news_title, s_news_link in {*d_finviz_news}: - print(f"-> {s_news_title}") - print(f"{s_news_link}\n") - i += 1 - - if i > (ns_parser.n_num - 1): - break - - print("") - - except Exception as e: - print(e) - print("") - return + print("") -def analyst_df(ticker: str) -> DataFrame: - """[summary] +def analyst(ticker: str, export: str): + """Display analyst ratings. [Source: Finviz] Parameters ---------- ticker : str Stock ticker - - Returns - ------- - DataFrame - [description] + export : str + Export dataframe data to csv,json,xlsx file """ + df = finviz_model.get_analyst_data(ticker) - try: - d_finviz_analyst_price = finviz.get_analyst_price_targets(ticker) - df_fa = pd.DataFrame.from_dict(d_finviz_analyst_price) - df_fa.set_index("date", inplace=True) - except Exception as e: - print(e) - print("Encountered a potential connectivity issue trying to access Finviz.") + if gtff.USE_COLOR: + df["category"] = df["category"].apply(category_color_red_green) - return df_fa - - -def analyst(other_args, ticker): - """Display analyst ratings - - Parameters - ---------- - other_args : [type] - argparse other args - ticker : [type] - Stock ticker - """ - - parser = argparse.ArgumentParser( - add_help=False, - prog="analyst", - description=""" - Print analyst prices and ratings of the company. The following fields are expected: - date, analyst, category, price from, price to, and rating. [Source: Finviz] - """, + print( + tabulate( + df, + headers=df.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ), + "\n", ) - parser.add_argument( - "-c", - "--color", - action="store", - dest="n_color", - type=int, - choices=[0, 1], - default=1, - help="Add / remove color", + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "analyst", + df, ) - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - df_fa = analyst_df(ticker) - - if ns_parser.n_color == 1: - df_fa["category"] = df_fa["category"].apply(category_color_red_green) - - patch_pandas_text_adjustment() - - print(df_fa) - print("") - - except Exception as e: - print(e) - print("") - return diff --git a/gamestonk_terminal/stocks/due_diligence/fmp_model.py b/gamestonk_terminal/stocks/due_diligence/fmp_model.py new file mode 100644 index 00000000000..abbae39febc --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/fmp_model.py @@ -0,0 +1,22 @@ +""" Financial Modeling Prep Model """ +__docformat__ = "numpy" + +import pandas as pd +import FundamentalAnalysis as fa +from gamestonk_terminal import config_terminal as cfg + + +def get_rating(ticker: str) -> pd.DataFrame: + """Get ratings for a given ticker. [Source: Financial Modeling Prep] + + Parameters + ---------- + ticker : str + Stock ticker + + Returns + ------- + pd.DataFrame + Rating data + """ + return fa.rating(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) diff --git a/gamestonk_terminal/stocks/due_diligence/fmp_view.py b/gamestonk_terminal/stocks/due_diligence/fmp_view.py new file mode 100644 index 00000000000..14a7c3cba72 --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/fmp_view.py @@ -0,0 +1,52 @@ +""" Financial Modeling Prep View """ +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.stocks.due_diligence import fmp_model + + +def rating(ticker: str, num: int, export: str): + """Display ratings for a given ticker. [Source: Financial Modeling Prep] + + Parameters + ---------- + ticker : str + Stock ticker + num : int + Number of last days ratings to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df = fmp_model.get_rating(ticker) + + # TODO: This could be displayed in a nice rating plot over time + # TODO: Add coloring to table + + if not df.empty: + l_recoms = [col for col in df.columns if "Recommendation" in col] + l_recoms_show = [ + recom.replace("rating", "") + .replace("Details", "") + .replace("Recommendation", "") + for recom in l_recoms + ] + l_recoms_show[0] = "Rating" + print( + tabulate( + df[l_recoms].head(num), + headers=l_recoms_show, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ) + ) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "rot", + df, + ) diff --git a/gamestonk_terminal/stocks/due_diligence/marketwatch_model.py b/gamestonk_terminal/stocks/due_diligence/marketwatch_model.py new file mode 100644 index 00000000000..3dbfdf00315 --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/marketwatch_model.py @@ -0,0 +1,64 @@ +""" Market Watch Model """ +__docformat__ = "numpy" + +import requests +import pandas as pd +from bs4 import BeautifulSoup +from gamestonk_terminal.helper_funcs import get_user_agent + +# pylint: disable=too-many-branches + + +def get_sec_filings(ticker: str) -> pd.DataFrame: + """Get SEC filings for a given stock ticker. [Source: Market Watch] + + Parameters + ---------- + ticker : str + Stock ticker + + Returns + ------- + df_financials : pd.DataFrame + SEC filings data + """ + + pd.set_option("display.max_colwidth", None) + + url_financials = ( + f"https://www.marketwatch.com/investing/stock/{ticker}/financials/secfilings" + ) + + text_soup_financials = BeautifulSoup( + requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text, + "lxml", + ) + + # a_financials_header = list() + df_financials = None + b_ready_to_process_info = False + soup_financials = text_soup_financials.findAll("tr", {"class": "table__row"}) + for financials_info in soup_financials: + a_financials = financials_info.text.split("\n") + + # If header has been processed and dataframe created ready to populate the SEC information + if b_ready_to_process_info: + l_financials_info = [a_financials[2]] + l_financials_info.extend(a_financials[5:-1]) + l_financials_info.append(financials_info.a["href"]) + # Append data values to financials + df_financials.loc[len(df_financials.index)] = l_financials_info # type: ignore + + if "Filing Date" in a_financials: + l_financials_header = [a_financials[2]] + l_financials_header.extend(a_financials[5:-1]) + l_financials_header.append("Link") + + df_financials = pd.DataFrame(columns=l_financials_header) + df_financials.set_index("Filing Date") + b_ready_to_process_info = True + + # Set Filing Date as index + df_financials = df_financials.set_index("Filing Date") # type: ignore + + return df_financials diff --git a/gamestonk_terminal/stocks/due_diligence/marketwatch_view.py b/gamestonk_terminal/stocks/due_diligence/marketwatch_view.py new file mode 100644 index 00000000000..4d76e3d3ce9 --- /dev/null +++ b/gamestonk_terminal/stocks/due_diligence/marketwatch_view.py @@ -0,0 +1,41 @@ +""" Market Watch View """ +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.stocks.due_diligence import marketwatch_model + +# pylint: disable=too-many-branches + + +def sec_filings(ticker: str, num: int, export: str): + """Display SEC filings for a given stock ticker. [Source: Market Watch] + + Parameters + ---------- + ticker : str + Stock ticker + num : int + Number of ratings to display + export : str + Export dataframe data to csv,json,xlsx file + """ + df_financials = marketwatch_model.get_sec_filings(ticker) + print( + tabulate( + df_financials.head(num), + headers=df_financials.columns, + floatfmt=".2f", + showindex=True, + tablefmt="fancy_grid", + ) + ) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "sec", + df_financials, + ) diff --git a/notebooks/templates/due_diligence.ipynb b/notebooks/templates/due_diligence.ipynb index 92cc6acc336..16856b1e982 100644 --- a/notebooks/templates/due_diligence.ipynb +++ b/notebooks/templates/due_diligence.ipynb @@ -3,7 +3,7 @@ { "cell_type": "code", "execution_count": null, - "id": "collaborative-pharmacology", + "id": "phantom-facility", "metadata": { "collapsed": true }, @@ -27,7 +27,7 @@ { "cell_type": "code", "execution_count": null, - "id": "future-association", + "id": "compact-count", "metadata": { "collapsed": true, "tags": [ @@ -44,7 +44,7 @@ { "cell_type": "code", "execution_count": null, - "id": "oriental-wells", + "id": "possible-constraint", "metadata": { "collapsed": true }, @@ -56,7 +56,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fitted-affect", + "id": "local-preference", "metadata": { "collapsed": true }, @@ -75,20 +75,20 @@ { "cell_type": "code", "execution_count": null, - "id": "black-latvia", + "id": "undefined-stadium", "metadata": { "collapsed": true }, "outputs": [], "source": [ "from gamestonk_terminal.common.technical_analysis import trendline_api as trend\n", - "from gamestonk_terminal.stocks.due_diligence import finviz_view as finviz" + "from gamestonk_terminal.stocks.due_diligence import finviz_view" ] }, { "cell_type": "code", "execution_count": null, - "id": "loved-dietary", + "id": "competitive-membership", "metadata": { "collapsed": true }, @@ -109,7 +109,7 @@ }, { "cell_type": "markdown", - "id": "transparent-change", + "id": "temporal-clerk", "metadata": {}, "source": [ "## Candle View" @@ -118,7 +118,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ruled-marks", + "id": "transsexual-trance", "metadata": { "collapsed": true }, @@ -148,7 +148,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ahead-return", + "id": "convenient-great", "metadata": { "collapsed": true }, @@ -161,7 +161,7 @@ }, { "cell_type": "markdown", - "id": "amber-hayes", + "id": "political-intake", "metadata": {}, "source": [ "## Analyst Targets" @@ -170,20 +170,19 @@ { "cell_type": "code", "execution_count": null, - "id": "conservative-baking", + "id": "numerous-begin", "metadata": { "collapsed": true }, "outputs": [], "source": [ - "df_fa = finviz.analyst_df(ticker)\n", - "print(df_fa.to_string())" + "finviz_view.analyst(ticker=ticker, no_color=False)" ] }, { "cell_type": "code", "execution_count": null, - "id": "moderate-dealer", + "id": "nuclear-trailer", "metadata": { "collapsed": true }, @@ -194,7 +193,7 @@ }, { "cell_type": "markdown", - "id": "victorian-classic", + "id": "written-lease", "metadata": {}, "source": [ "## Annual Income Statement" @@ -203,7 +202,7 @@ { "cell_type": "code", "execution_count": null, - "id": "three-vegetation", + "id": "played-vancouver", "metadata": { "collapsed": true }, @@ -215,7 +214,7 @@ }, { "cell_type": "markdown", - "id": "collected-deployment", + "id": "smooth-forge", "metadata": {}, "source": [ "## Quarterly Income Statement" @@ -224,7 +223,7 @@ { "cell_type": "code", "execution_count": null, - "id": "grateful-minnesota", + "id": "juvenile-throw", "metadata": { "collapsed": true }, @@ -236,7 +235,7 @@ }, { "cell_type": "markdown", - "id": "angry-somerset", + "id": "passive-nerve", "metadata": {}, "source": [ "## Annual Balance Sheet" @@ -245,7 +244,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fifty-angola", + "id": "nominated-empire", "metadata": { "collapsed": true }, @@ -257,7 +256,7 @@ }, { "cell_type": "markdown", - "id": "typical-fundamentals", + "id": "special-bargain", "metadata": {}, "source": [ "## Annual Cash Flow" @@ -266,7 +265,7 @@ { "cell_type": "code", "execution_count": null, - "id": "magnetic-throat", + "id": "focused-thanks", "metadata": { "collapsed": true }, @@ -279,7 +278,7 @@ { "cell_type": "code", "execution_count": null, - "id": "instructional-skating", + "id": "sought-phase", "metadata": { "collapsed": true }, diff --git a/tests/cassettes/test_dd/test_financial_model/test_rating.yaml b/tests/cassettes/test_dd/test_financial_model/test_rating.yaml deleted file mode 100644 index fae400e4e72..00000000000 --- a/tests/cassettes/test_dd/test_financial_model/test_rating.yaml +++ /dev/null @@ -1,39 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - financialmodelingprep.com - User-Agent: - - Python-urllib/3.8 - method: GET - uri: https://financialmodelingprep.com/api/v3/historical-rating/PLTR?apikey=REPLACE_ME - response: - body: - string: '{"Error Message" : "Invalid API KEY. Please retry or visit our documentation - to create one FREE https://financialmodelingprep.com/developer/docs"}' - headers: - Access-Control-Allow-Headers: - - origin, content-type, accept, Authorization, authorization - Access-Control-Allow-Methods: - - GET, OPTIONS - Access-Control-Allow-Origin: - - '*' - Access-Control-Max-Age: - - '3600' - Connection: - - close - Content-Length: - - '146' - Content-Type: - - application/json;charset=UTF-8 - Date: - - Thu, 12 Aug 2021 19:14:33 GMT - Server: - - nginx/1.14.0 (Ubuntu) - status: - code: 200 - message: '' -version: 1 diff --git a/tests/cassettes/test_main/test_main_helper/test_check_api_keys.yaml b/tests/cassettes/test_main/test_main_helper/test_check_api_keys.yaml new file mode 100644 index 00000000000..6135eebed17 --- /dev/null +++ b/tests/cassettes/test_main/test_main_helper/test_check_api_keys.yaml @@ -0,0 +1,1621 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=AAPL&interval=15min&outputsize=compact&apikey=P6YGOQ6FL6OEIZ3B&datatype=json + response: + body: + string: !!binary | + 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 + AwC4FfaYaVYAAA== + headers: + Allow: + - GET, HEAD, OPTIONS + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 68131f53481053c2-LHR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 19 Aug 2021 11:43:06 GMT + Expect-CT: + - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + Server: + - cloudflare + Transfer-Encoding: + - chunked + Vary: + - Cookie + Via: + - 1.1 vegur + X-Frame-Options: + - SAMEORIGIN + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://financialmodelingprep.com/api/v3/profile/AAPL?apikey=845b22316eeec4168d249976bba94330 + response: + body: + string: !!binary | + H4sIAAAAAAAAA21WTXPbNhC951dgfI1Ek5JlSfFJld3WM4nj1k5y6PQAgZCIMQgwAChG7vS/9y1A + yrLTmYwj7Rfe7r7d1V/sn3eMnflDvbH6jH1gZ6vV/cezEQkbp4QkWXFxmU0vo2wjA4+ibJJP5st5 + FO6tXu13JJ7Pl9PLi8UkiuunsOYNiScXi8VsupjPlsXlIuo09+Fa7UmZZ4tZlDludvG9syKfZsW4 + mBXZ5SJhERUpPWnH02wxzfO8SJGErRtuDne8Tr6rptGS3RqR9Z6tc9KIQ1R+ebjupeopChAnn07y + YjlNcuWV6S3z6XwxnRZ5PhsCedUkp0GTFPJHQhd1d9yX/Dv7TdsN1+xBainCa7OHyrpwhHu3erhe + /dE/bsrWB5egrq3xbS0du6EQzholfDLr5MarkNyrEJoP5+dd12WcEs9QjmRVSi+caoKy5k1dGFRq + Z/yI1dy0Wy5C6yS+cVNC4p5k8MzjQ2gqa0jRSOetQTpU6zbg24gFvtEwHLFOckef+wBcCOm9dUp6 + 1lmny06VMmO3gXHtLfNSa8/23Cnbeuak5kGWkLo9uOYz9lhJ1neU2e0WTzF1TzAQnWllJKSn4K7Y + Jy5OdT9jvUIEXp7a1K0Oaty0rrFeDplcRfgn2VS2lj/nRGEdsWTHVsrd2xKWqbSPX4dP33gQ1Yj9 + Ijkq2ThbtoIq9TsCwmEEPLZkwbZkRPFtqNDn6DveYApKlITkoVKuHDdI9nCK4aWaiL1HeX3yXXMn + mW+bBvw6lvSKCW3blxIzHyyZHdURAKqGRrw0pkFfttbVQK2M0G1J+QIlPcQeKAIYUHGCoW3HMBoB + yaFZwbJSeWH3SIgil7Yz2nLkA4RKcKKjTxq1UyH2yQRpwgjIRcW4Zxtrn4ibGDd0lvKzI7bDvPQM + a2wpsD2oDIbxslQUcxTRvWHOkM2Q7MsbqU8rJ3gZmUXhodwcR2bwuepNPyU0XaUQoA/fevrLkb2L + LNbKIxOqlPyBgipsHck6FeBgxqWsCbzjpcIUhFSIIfqd7Px7wvEKgoG0H8odfybuvgH1+PX9G0jY + MRpI96C5Q33NS30HH5AkzoKwR6oJJ1FEJqBJdEiW9/wQDbmvMA/gBD/UCDSAGFEdO4wz/Y/WSuNB + IAXGKzyoaWm1eB4URS3C4fVoU4w4TGnH9Z3FXFM4yliVY6+eAW6DdDDnfqAqtVlioHjqOgBJRwPZ + j+qOmGcizn6VxWlJayfSTmqUh5h6Olyn5ATvYxrD4MLS2XZXveb/63xOdtv/upLQ4XSqlJ41cRXF + UexzL5VDxZjnqDVBoC6/3QECD7SaOzAjYLc+UctAMypfV1k4wpm+pJeOZcUb1A+HUpwcgY7TO21k + gDKsWM7n0Vp5Vklefm/xpHRJuW6ph8pgENdcK8Azig/3Vdp4Xz45lETV2GUHtsYIJ61HUtZFg0cp + KmO13R16Rzw+HLsvD0m2bbVGEHlTN9oeZDr4Z8XFPB+ObVz7vTRfLJeXRV70KuwCpNq7HEmMMn3j + w5Mq9Md1SKhHiXlMQderj7e/fv7z7naVNM/9yV/O8uKiP6xie622WxIvltlyMgjTL6VFNpss0zXH + 2L4caY8rvcVAGqG4ri3R0OwaJxu62OfRdgw+iKdz+gGWNWbX/yZo7PUArlgu8nExwb/hxG85Ttnt + 8NAWNJT9z5ibsH0rWokA6uvDIy0hE3+vBdfKd/+yv/8DQ6b5rgcKAAA= + headers: + Access-Control-Allow-Headers: + - origin, content-type, accept, Authorization, authorization + Access-Control-Allow-Methods: + - GET, OPTIONS + Access-Control-Allow-Origin: + - '*' + Access-Control-Max-Age: + - '3600' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Thu, 19 Aug 2021 11:43:07 GMT + Server: + - nginx/1.14.0 (Ubuntu) + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + status: + code: 200 + message: '' +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + request-source: + - python + request-source-version: + - 3.6.1 + x-api-token: + - yn5sTZkvDg9oSHnP6wZg + method: GET + uri: https://www.quandl.com/api/v3/datatables/ZACKS/FC?ticker%5B%5D=AAPL&ticker%5B%5D=MSFT&per_end_date.gte=2015-01-01&qopts.columns%5B%5D=ticker&qopts.columns%5B%5D=per_end_date + response: + body: + string: !!binary | + H4sIAAAAAAAA/5WQTwuCQBTEv8ucV1AXIr0J0akgsJvIYvoIaV1lXaGQ/e6t0R9KL/FOM/yYx8yI + qjDuTpIQjw+BOMuQJIcdGEI/WHt+5HEfOftxuceDubtaZBcTgvCVsE+3x3nCl8v/YKMl9/ktZyhb + OTSqdz1HqKJxxWHq8kLaoebWTTo1ulZnWPYmOtKCVCXcQvThNpOyuWVoaFrO4XQ1ohx032pRV4jV + IKW1d+5psDJnAQAA + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, OPTIONS + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-RateLimit-Limit, X-RateLimit-Remaining + Access-Control-Max-Age: + - '1728000' + Allow: + - GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH + Cache-Control: + - no-store + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Security-Policy: + - frame-ancestors 'self'; + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 19 Aug 2021 11:43:07 GMT + ETag: + - W/"d7cab2696c63f665357d438af9d4794f" + Location: + - https://www.quandl.com/api/v3/datatables/ZACKS/FC + Pragma: + - no-cache + Referrer-Policy: + - origin-when-cross-origin + Server: + - openresty + Set-Cookie: + - visid_incap_2261005=RoMcWHbSQUmDTyCY95dGQ8tDHmEAAAAAQUIPAAAAAAAxMDNYhzy1kUCQFquX/dCv; + expires=Fri, 19 Aug 2022 07:44:23 GMT; HttpOnly; path=/; Domain=.quandl.com; + Secure; SameSite=None + - nlbi_2261005=HLtrBUPYBhK1IcqKgSMXTwAAAAD9uvPY6t65x+yJEv5vgpD9; path=/; Domain=.quandl.com; + Secure; SameSite=None + - incap_ses_8218_2261005=GRwxCATo+BFRws29tjMMcstDHmEAAAAAFujqvHIzntjNJ/d8raXtqw==; + path=/; Domain=.quandl.com; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-CDN: + - Imperva + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Iinfo: + - 11-3739546-3732907 PNYN RT(1629373387200 46) q(0 0 0 1) r(2 2) U2 + X-RateLimit-Limit: + - '50000' + X-RateLimit-Remaining: + - '49999' + X-Request-Id: + - f46ded3e-f20e-42c4-bc6a-6cd3596fbecb + X-Runtime: + - '0.072128' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.polygon.io/v2/aggs/ticker/AAPL/range/1/day/2020-06-01/2020-06-17?apiKey=pTx4m2fILimhK7YpR49Oa0DAec2bpnSG + response: + body: + string: !!binary | + H4sIAAAAAAAA/1TUTYsbRxAG4P/SVw9FfVf13EyOCST3EIJWq8VOFi+WRg5h2f9u5qunpdPQNHp4 + 36qZ9zJ9Pf97uZaxfP78x29lKN/vl+v/v7zdv01lJBnK9XK7v0637uT0/M/9Nl2eyzhd75d2o4x/ + vpcfZUwgRAzjyyeMofz4r4yJwMQ2lLcyRgWVsKGcl3P1+fzL8mw5n78udwTn81m0ijUVl99QvpWR + hQLjY9i0cGWXR00SadESgdJl11Ialr5TIQdFGKgdxc6aG0WAqhUt9fIJc7MIMHWlCMi3XAScK0Rg + a6Y5XxwOmWfnWKZ3kcxRXB8ipcx/uTpYjwKtQYpNonpIrMJ9eUFBLZEEa6Jml4iBYy7nbWN3imEZ + z5flsUo0LLHLJVz90MTIyTatgrlh7VIxhDuvFIMtqzBTAuIbJaCrQ5DSFMvs23NilJZJI4iZ+0wG + 4rKNSQBlcwxq3TM56LJyr+uVbv3Iw7oGFTn82Al3V689FpAhW4EObVQJTCuV4HWnHIw7Kpy6+lRr + JG8UA5Jb9Yf1M4jQbdUDhKM12IIFhLdcGY1Kze6lsgiu9aCQ1IgfKSThPRblCinE3l9titm8lZtT + Rbs3ykgFj/YkEzO4YxQykvdRCXGbVWg3K94t0tYeU5V++TyP5SNwU87gx0kFZWsvvU0KeZ+U7GOi + loh5K24fkrt5Y0gl07BvLkF5Xt635TlyH1JAtikl5FZfQMSxESzuXX3ClFw//hrKbTpN91sZy++/ + lvkb/P1+uU1/f30uY6H6lEgvJ+ET14zTS56fXs4o+hIm/iRlKOf9c/7xMwAA//9XFUY9AgYAAA== + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '625' + Content-Type: + - application/json + Date: + - Thu, 19 Aug 2021 11:43:08 GMT + Server: + - nginx/1.19.2 + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept-Encoding + X-Request-Id: + - 19b801fa32a2987af8cbfc034f7536b3 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.stlouisfed.org/fred/series?series_id=GNPCA&api_key=b98e15f1fde39212bce9213f2e3e5afb + response: + body: + string: !!binary | + H4sIAAAAAAAEA7Kxr8jNUShLLSrOzM+zVTLUM1BSSM1Lzk/JzEu3VSotSdO1UFKwt+OyKU4tykwt + LlYoSk3MKcnMTY0vLkksKrFVMjIwMtQ1sNA1tFRCyKXmpaDI2HEpKEBNUMhMsVVy9wtwdkRST4pZ + CiWZJTmptkpBqYk5Cu5F+cXFCn6JJZn5eYk5CgFF+SmlySVKCvlJxalFZWBhmEMNLY0sdQ0MdQ0M + UaVhbjWASaYVpRaWpuYlV9oqOebllSbmKCnAheKLM/JBvnZUUijNyywptlVyyszJyczPK1bIT1Nw + zkjMzEtNUTAyMDRScMnPyUksKoYqhGl0yszRgyjN04OoU1FSKE5NLAa5Pz4xJau0uCQ3Na/EVskv + v0QhGCqRU6ngCJZKTcGqGma6X7CjkkJOYnFJfGlBSmJJKjwWzHWNLBUMzK1MTK1MLXQNTJUUCvIL + SnMSizJLKm2VDA2VFPLyS1JB3nF1VHBMTs4vzStRcM5PSbVScDQwMAyK4OJS0rfjstGHJgPA7LgA + P2h/CDkCAAA= + headers: + Accept-Ranges: + - bytes + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '407' + Content-Type: + - text/xml; charset=UTF-8 + Date: + - Thu, 19 Aug 2021 11:43:08 GMT + Expires: + - Thu, 19 Aug 2021 11:44:08 GMT + Last-Modified: + - Thu, 29 Jul 2021 12:45:58 GMT + Set-Cookie: + - cookiesession1=2B879F0F8JRHXHTYTLFM0XGZBUD81B83;Path=/;HttpOnly + Vary: + - Accept-Encoding + x-rate-limit-limit: + - '120' + x-rate-limit-remaining: + - '120' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://newsapi.org/v2/everything?q=keyword&apiKey=c22006cceb284307a6a050274b07a8d6 + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 68131f60482d40bf-LHR + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - br + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 19 Aug 2021 11:43:09 GMT + Expect-CT: + - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + Expires: + - '-1' + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Pragma: + - no-cache + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=esMYVEKFQLtbQGQgQG5oknla6OUhNc%2B0P6T8aZm3jh0%2F480%2BXQSfBSLYCrbJBh7d7v6DaImESVYnZ7VIdh3IPpCIu0wy16b1OuFFYPvPIORTDdau1llwCefvPLq3"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Cached-Result: + - 'false' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate, br + Authorization: + - Bearer C80IeE0SfnTGQpjrvofuW47XPIm1 + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://sandbox.tradier.com/v1/markets/quotes?symbols=AAPL + response: + body: + string: !!binary | + H4sIAAAAAAAAAF1Ry27CMBD8lWrPUWQnzosbx0o90C+IjLMiUUzsxgZKEf/ejYML6sHSzK5mZ3Z9 + g6+T8ehgc1vRAtz1uDcaNrDd7j4ggQ6dmgfrBzMtRWs1vr1Pijr4rXoqfRL0V0ticN6okaiWzsOG + izLNywRUL6cDtVnKWAJno09HYgXnLBcJGIs0eDppnUA/HPqItblEqLRxGMl+6JbRIi2zBKQbH0RE + n9birHDy8mkpzzgTbaN1VVV5JhbJEvSvLJpa0KNtZtlh28nlILzMmpyXNWNNQ6PsjOdHnLjeBXFs + i6xdw/OCp2X9rIY1aNOUh+hu+CFpFvDjfjsI7NWvykTdsBDdjauEB/xycmL/JUWQzMb4dv1FF7/x + fr//AsCv14HtAQAA + headers: + Access-Control-Allow-Headers: + - accept, authorization + Access-Control-Allow-Methods: + - GET, PUT, POST, DELETE + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Thu, 19 Aug 2021 11:43:09 GMT + Transfer-Encoding: + - chunked + X-Ratelimit-Allowed: + - '200' + X-Ratelimit-Available: + - '199' + X-Ratelimit-Expiry: + - '1629373440000' + X-Ratelimit-Used: + - '1' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - deflate, gzip + Accepts: + - application/json + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + X-CMC_PRO_API_KEY: + - 43e3d78c-06ba-4d1f-839f-d274702220bb + method: GET + uri: https://pro-api.coinmarketcap.com/v1/exchange/info + response: + body: + string: !!binary | + H4sIAAAAAAAAA2WOPQ+CMBCGd37FpYuLkBaJETZH4+LgootB2mgTaJvedTCE/+7Bosbb7n3yfowZ + 8AmklhKKBsblXzSyg2F9CCyLUpYql7tc1WelmmrTyLqot9VVrD8GE6OPt85rww4l5faPcSC2jxmL + i08R9qcDHM0LMN2xizaQ9Q5C3zrQ3qBbEZMQfCSgp0UwTgdvHRU/rX0b0GjOlF99XTTaEo9Jjma0 + rJyy6Q0L5ePO8AAAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 68131f66ef9740f6-LHR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 19 Aug 2021 11:43:10 GMT + Expect-CT: + - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + Server: + - cloudflare + Transfer-Encoding: + - chunked + cache-control: + - no-cache + vary: + - origin + status: + code: 403 + message: Forbidden +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://finnhub.io/api/v1/quote?symbol=AAPL&token=c23k26qad3ieeb1lftug + response: + body: + string: !!binary | + IXQBACBm21L/PHY8YxPWKgSDdxpsnsmQRCSVde0fFpI91EKwkcV0mGA/yNLUvVJwIulVDwquQLrg + RrJPHYJnocApeJGMNo0xqv0D + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 68131f686efc68dc-CDG + Connection: + - keep-alive + Content-Encoding: + - br + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 19 Aug 2021 11:43:10 GMT + Expect-CT: + - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=RKS29IzuFDH4jBIA195Ul1J%2FrbJyLr90LteVooyS5vlYk85prszD9GJyFk1qBKRr2idEqyzzun5kg%2F%2ByeQ%2FYuSzlgwfRDeehVZUu1XptBJQZcg2PcXskWeg%2BFcSXUlxtgsgjV53l93NM"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + access-control-allow-credentials: + - 'true' + access-control-allow-headers: + - Origin + access-control-allow-methods: + - POST, GET, OPTIONS, PUT, DELETE + access-control-allow-origin: + - '*' + access-control-expose-headers: + - Content-Length + alt-svc: + - h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; + ma=86400 + x-ratelimit-limit: + - '60' + x-ratelimit-remaining: + - '59' + x-ratelimit-reset: + - '1629373450' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://pypi.org/pypi/praw/json + response: + body: + string: !!binary | + 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 + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Headers: + - Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since + Access-Control-Allow-Methods: + - GET + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-PyPI-Last-Serial + Access-Control-Max-Age: + - '86400' + Cache-Control: + - max-age=900, public + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '32543' + Content-Security-Policy: + - base-uri 'self'; block-all-mixed-content; connect-src 'self' https://api.github.com/repos/ + *.fastly-insights.com sentry.io https://api.pwnedpasswords.com https://2p66nmmycsj3.statuspage.io; + default-src 'none'; font-src 'self' fonts.gstatic.com; form-action 'self'; + frame-ancestors 'none'; frame-src 'none'; img-src 'self' https://warehouse-camo.ingress.cmh1.psfhosted.org/ + www.google-analytics.com *.fastly-insights.com; script-src 'self' www.googletagmanager.com + www.google-analytics.com *.fastly-insights.com https://cdn.ravenjs.com; style-src + 'self' fonts.googleapis.com; worker-src *.fastly-insights.com + Content-Type: + - application/json + Date: + - Thu, 19 Aug 2021 11:43:10 GMT + ETag: + - '"Oemkh727wi/Q2d8T7TySgA"' + Referrer-Policy: + - origin-when-cross-origin + Server: + - nginx/1.13.9 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Cache: + - HIT, HIT + X-Cache-Hits: + - 2, 1 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Permitted-Cross-Domain-Policies: + - none + X-PyPI-Last-Serial: + - '11046813' + X-Served-By: + - cache-bwi5124-BWI, cache-lhr7338-LHR + X-Timer: + - S1629373390.373234,VS0,VE1 + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: grant_type=password&password=DiR3dit&username=SexyYear + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Authorization: + - Basic MmVjcjVhQ3E2dl9EVGc6dEhoelllVTJuX0tOMHF6WWtnTDFpcldvTmFPVHh3 + Connection: + - close + Content-Length: + - '54' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - stonks by /u/SexyYear PRAW/7.3.0 prawcore/2.3.0 + method: POST + uri: https://www.reddit.com/api/v1/access_token + response: + body: + string: '{"access_token": "772738493719-cv5-anpLByCKAIQlkxctlaHO2L-ryg", "token_type": + "bearer", "expires_in": 3600, "scope": "*"}' + headers: + Accept-Ranges: + - bytes + Connection: + - close + Content-Length: + - '121' + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 19 Aug 2021 11:43:10 GMT + Server: + - snooserv + Set-Cookie: + - edgebucket=GHQZbxcjNALA0rOz3N; Domain=reddit.com; Max-Age=63071999; Path=/; secure + Strict-Transport-Security: + - max-age=15552000; includeSubDomains; preload + Via: + - 1.1 varnish + X-Clacks-Overhead: + - GNU Terry Pratchett + X-Moose: + - majestic + cache-control: + - max-age=0, must-revalidate + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ratelimit-remaining: + - '299' + x-ratelimit-reset: + - '410' + x-ratelimit-used: + - '1' + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Authorization: + - bearer 772738493719-cv5-anpLByCKAIQlkxctlaHO2L-ryg + Connection: + - keep-alive + Cookie: + - edgebucket=GHQZbxcjNALA0rOz3N + User-Agent: + - stonks by /u/SexyYear PRAW/7.3.0 prawcore/2.3.0 + method: GET + uri: https://oauth.reddit.com/api/v1/me?raw_json=1 + response: + body: + string: !!binary | + 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 + MhAAAA== + headers: + Accept-Ranges: + - bytes + Connection: + - keep-alive + Content-Length: + - '1600' + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 19 Aug 2021 11:43:11 GMT + Server: + - snooserv + Set-Cookie: + - session_tracker=lqgaprciodlnbrgofj.0.1629373391106.Z0FBQUFBQmhIa1BQUGQzX2FHRDFwUDhBQndaRFA1azdENXlzbjNXYUFTazhDRE1nTUJEWW8wZzlvVWNCMnprT29yY3pHb3EwUnBCOGgxUWVvSmxRRzBrcmhIME9BMXppVE5FaE1jV3JzYmZ2bzNsMVc0VkdyMmY4b0czOHpZVXJybTMwX2paUGM2LXY; + Domain=reddit.com; Max-Age=7199; Path=/; expires=Thu, 19-Aug-2021 13:43:11 + GMT; secure; SameSite=None; Secure + - csv=1; Max-Age=63072000; Domain=.reddit.com; Path=/; Secure; SameSite=None + Strict-Transport-Security: + - max-age=15552000; includeSubDomains; preload + Vary: + - accept-encoding + Via: + - 1.1 varnish + X-Clacks-Overhead: + - GNU Terry Pratchett + X-Moose: + - majestic + cache-control: + - private, s-maxage=0, max-age=0, must-revalidate, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + expires: + - '-1' + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ratelimit-remaining: + - '599.0' + x-ratelimit-reset: + - '409' + x-ratelimit-used: + - '1' + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + authorization: + - Bearer AAAAAAAAAAAAAAAAAAAAABhAMwEAAAAAxgWCxKhLV5%2BgalZgKg5C4NTlklI%3DbH9APfN61fZezf9s7TOfHORYTjVCEDefOYzTJMNj2p3tW4lcWh + method: GET + uri: https://api.twitter.com/2/tweets/search/recent?query=%28%5C%24AAPL%29+%28lang%3Aen%29&max_results=10&tweet.fields=created_at%2Clang + response: + body: + string: !!binary | + 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 + headers: + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '1684' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 19 Aug 2021 11:43:11 UTC + server: + - tsa_f + set-cookie: + - personalization_id="v1_fFBezvTe9ZaMiClKuFmhEA=="; Max-Age=63072000; Expires=Sat, + 19 Aug 2023 11:43:11 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A162937339135682074; Max-Age=63072000; Expires=Sat, 19 Aug 2023 + 11:43:11 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + strict-transport-security: + - max-age=631138519 + x-access-level: + - read + x-connection-hash: + - 9e05f4a7bb151a7638aec3f171796749803e9db212b8dde8eabe22403cc919b5 + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '450' + x-rate-limit-remaining: + - '449' + x-rate-limit-reset: + - '1629374291' + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.sentimentinvestor.com/v4/account?token=0L4fHbKPoBqhoGZK2Ovx&key=452035089330795 + response: + body: + string: '{"success":true,"tier":0,"calls":5526,"period_end":null}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - private + Connection: + - keep-alive + Content-Length: + - '56' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 19 Aug 2021 11:43:14 GMT + Etag: + - W/"38-hdoEcCGV00qGRGFhtQhlY80ngwY" + Function-Execution-Id: + - vde9sexldi8b + Server: + - Google Frontend + Vary: + - cookie,need-authorization, x-fh-requested-host, accept-encoding + X-Cache: + - MISS + X-Cache-Hits: + - '0' + X-Cloud-Trace-Context: + - 8e5bd60b3dca41d1613d929b56445005;o=1 + X-Country-Code: + - GB + X-Powered-By: + - Express + X-Served-By: + - cache-lhr7351-LHR + X-Timer: + - S1629373392.712520,VS0,VE3073 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_dd/test_dd_financial_modeling_prep_view.py b/tests/test_dd/test_dd_financial_modeling_prep_view.py deleted file mode 100644 index 96b165649f7..00000000000 --- a/tests/test_dd/test_dd_financial_modeling_prep_view.py +++ /dev/null @@ -1,14 +0,0 @@ -""" fundamental_analysis/business_insider_api.py tests """ -import unittest - -import vcr - -from gamestonk_terminal.stocks.due_diligence.financial_modeling_prep_view import rating -from tests.helpers import check_print - - -class TestDdFinancialModelingPrepView(unittest.TestCase): - @check_print(assert_in="Invalid API KEY") - @vcr.use_cassette("tests/cassettes/test_dd/test_financial_model/test_rating.yaml") - def test_rating(self): - rating([], "PLTR") diff --git a/tests/test_dd/test_dd_finviz_view.py b/tests/test_dd/test_dd_finviz_view.py index 89304a8763a..9c4625bfa8a 100644 --- a/tests/test_dd/test_dd_finviz_view.py +++ b/tests/test_dd/test_dd_finviz_view.py @@ -3,7 +3,7 @@ import unittest import vcr -from gamestonk_terminal.stocks.due_diligence.finviz_view import analyst +from gamestonk_terminal.stocks.due_diligence.finviz_model import get_analyst_data from tests.helpers import check_print @@ -11,4 +11,5 @@ class TestDdFinvizApi(unittest.TestCase): @check_print(assert_in="category") @vcr.use_cassette("tests/cassettes/test_dd/test_finzin/test_analyst.yaml") def test_analyst(self): - analyst([], "PLTR") + df = get_analyst_data(ticker="PLTR") + print(df.to_string())