diff --git a/README.md b/README.md index a21a43330506..591dfe4374c9 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ If you wish to install the OpenBB Terminal or the OpenBB SDK, please use one of |:-|:-| |[PyPI](https://docs.openbb.co/terminal/installation/pypi)|If you wish to use the OpenBB SDK in Python or Jupyter Notebooks| |[Source](https://docs.openbb.co/terminal/installation/source)|If you wish to contribute to the development of the OpenBB Terminal| -nbsp;| + ## 2. Contributing diff --git a/openbb_terminal/stocks/fundamental_analysis/business_insider_model.py b/openbb_terminal/stocks/fundamental_analysis/business_insider_model.py index bcca57968391..6fd7e28c0750 100644 --- a/openbb_terminal/stocks/fundamental_analysis/business_insider_model.py +++ b/openbb_terminal/stocks/fundamental_analysis/business_insider_model.py @@ -3,7 +3,6 @@ import json import logging -import re from typing import Tuple import pandas as pd @@ -193,120 +192,61 @@ def get_estimates(symbol: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame "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, + # Get all tables and convert them to list of pandas dataframes + tables = text_soup_market_business_insider.find_all("table") + list_df = pd.read_html(str(tables)) + + # Get year estimates + df_year_estimates = list_df[3] + l_year_estimates_columns = df_year_estimates.columns.tolist() + l_year_estimates_columns[0] = "YEARLY ESTIMATES" + df_year_estimates.columns = l_year_estimates_columns + df_year_estimates.set_index("YEARLY ESTIMATES", inplace=True) + + df_quarter = list_df[4] + date_row = dict() + + # Get quarter earnings estimates + df_quarter_earnings = df_quarter.iloc[0:5, :].reset_index(drop=True).copy() + df_quarter_earnings.drop(index=0, inplace=True) + l_quarter_earnings_columns = df_quarter_earnings.columns.tolist() + l_quarter_earnings_columns[0] = "QUARTER EARNINGS ESTIMATES" + date_row["QUARTER EARNINGS ESTIMATES"] = "Date" + + # Adding Date info to add to dataframe + for col in l_quarter_earnings_columns[1:]: + key = col.split("ending")[0].strip() + value = col[col.find("ending") :].strip() + date_row[key] = value + + df_quarter_earnings.columns = date_row.keys() + date_row = pd.DataFrame(date_row, index=[0]) + df_quarter_earnings = pd.concat([date_row, df_quarter_earnings]).reset_index( + drop=True ) - # 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_earnings.set_index("QUARTER EARNINGS ESTIMATES", inplace=True) + + # Setting date_row to empty dict object + date_row = dict() + + # Get quarter revenues estimates + df_quarter_revenues = df_quarter.iloc[5:, :].reset_index(drop=True).copy() + df_quarter_revenues.drop(index=0, inplace=True) + l_quarter_revenues_columns = df_quarter_revenues.columns.tolist() + l_quarter_revenues_columns[0] = "QUARTER REVENUES ESTIMATES" + date_row["QUARTER REVENUES ESTIMATES"] = "Date" + + # Adding Date info to add to dataframe + for col in l_quarter_revenues_columns[1:]: + key = col.split("ending")[0].strip() + value = col[col.find("ending") :].strip() + date_row[key] = value + + df_quarter_revenues.columns = date_row.keys() + date_row = pd.DataFrame(date_row, index=[0]) + df_quarter_revenues = pd.concat([date_row, df_quarter_revenues]).reset_index( + drop=True ) - # df_quarter_revenues.index.name = 'Revenues' - - if not df_quarter_earnings.empty: - 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"] - ) - - if not df_quarter_revenues.empty: - 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"] - ) + df_quarter_revenues.set_index("QUARTER REVENUES ESTIMATES", inplace=True) return df_year_estimates, df_quarter_earnings, df_quarter_revenues diff --git a/openbb_terminal/stocks/fundamental_analysis/business_insider_view.py b/openbb_terminal/stocks/fundamental_analysis/business_insider_view.py index 7bb6111711b6..1aefd4fe36a3 100644 --- a/openbb_terminal/stocks/fundamental_analysis/business_insider_view.py +++ b/openbb_terminal/stocks/fundamental_analysis/business_insider_view.py @@ -215,7 +215,7 @@ def display_estimates( df_quarter_revenues, ) = business_insider_model.get_estimates(symbol) - if estimate == "annualearnings": + if estimate == "annual_earnings": print_rich_table( df_year_estimates, headers=list(df_year_estimates.columns), @@ -231,7 +231,7 @@ def display_estimates( sheet_name, ) - elif estimate == "quarterearnings": + elif estimate == "quarter_earnings": print_rich_table( df_quarter_earnings, headers=list(df_quarter_earnings.columns), @@ -247,7 +247,7 @@ def display_estimates( sheet_name, ) - elif estimate == "annualrevenue": + elif estimate == "quarter_revenues": print_rich_table( df_quarter_revenues, headers=list(df_quarter_revenues.columns), diff --git a/openbb_terminal/stocks/fundamental_analysis/fa_controller.py b/openbb_terminal/stocks/fundamental_analysis/fa_controller.py index d3919be85aa5..9dac76cbd5d6 100644 --- a/openbb_terminal/stocks/fundamental_analysis/fa_controller.py +++ b/openbb_terminal/stocks/fundamental_analysis/fa_controller.py @@ -92,7 +92,7 @@ class FundamentalAnalysisController(StockBaseController): PATH = "/stocks/fa/" SHRS_CHOICES = ["major", "institutional", "mutualfund"] - ESTIMATE_CHOICES = ["annualrevenue", "annualearnings", "quarterearnings"] + ESTIMATE_CHOICES = ["annual_earnings", "quarter_earnings", "quarter_revenues"] CHOICES_GENERATION = True def __init__( @@ -1858,7 +1858,7 @@ def call_est(self, other_args: List[str]): help="Estimates to get", dest="estimate", choices=self.ESTIMATE_CHOICES, - default="annualearnings", + default="annual_earnings", ) ns_parser = self.parse_known_args_and_warn( parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED diff --git a/tests/openbb_terminal/stocks/fundamental_analysis/csv/test_business_insider_model/test_get_estimates_year_estimates.csv b/tests/openbb_terminal/stocks/fundamental_analysis/csv/test_business_insider_model/test_get_estimates_year_estimates.csv index d9a00450d449..b345a1cfbe73 100644 --- a/tests/openbb_terminal/stocks/fundamental_analysis/csv/test_business_insider_model/test_get_estimates_year_estimates.csv +++ b/tests/openbb_terminal/stocks/fundamental_analysis/csv/test_business_insider_model/test_get_estimates_year_estimates.csv @@ -1,28 +1,28 @@ YEARLY ESTIMATES,2023,2024,2025,2026,2027 -Revenue,"103,351","135,186","168,507",-,- +Revenue,103351,135186,168507,-,- Dividend,0.00,0.00,0.00,-,- Dividend Yield (in %),-,-,-,-,- EPS,4.08,5.65,6.91,5.83,6.98 P/E Ratio,45.95,33.21,27.13,32.17,26.86 -EBIT,"14,600","21,973","27,740","30,032","44,029" -EBITDA,"19,429","27,963","38,063","37,754","38,937" -Net Profit,"13,681","19,370","23,763","21,385","26,126" -Net Profit Adjusted,"13,713","19,366","23,746","21,385","26,126" -Pre-Tax Profit,"14,437","22,015","28,932","29,404","31,787" -Net Profit (Adjusted),"14,363","22,392","29,878","29,404","31,787" +EBIT,14600,21973,27740,30032,44029 +EBITDA,19429,27963,38063,37754,38937 +Net Profit,13681,19370,23763,21385,26126 +Net Profit Adjusted,13713,19366,23746,21385,26126 +Pre-Tax Profit,14437,22015,28932,29404,31787 +Net Profit (Adjusted),14363,22392,29878,29404,31787 EPS (Non-GAAP) ex. SOE,4.08,5.65,6.91,5.83,6.98 EPS (GAAP),3.49,5.25,6.18,5.73,6.65 -Gross Income,"23,184","32,468","40,980","37,949","42,464" -Cash Flow from Investing,"-7,935","-9,482","-10,201","-12,681",- -Cash Flow from Operations,"19,523","24,129","27,935","29,376","35,875" +Gross Income,23184,32468,40980,37949,42464 +Cash Flow from Investing,-7935,-9482,-10201,-12681,- +Cash Flow from Operations,19523,24129,27935,29376,35875 Cash Flow from Financing,-478,-810,100,-,- Cash Flow per Share,4.92,7.39,6.94,-,- -Free Cash Flow,"10,236","14,300","15,203","18,405","22,501" +Free Cash Flow,10236,14300,15203,18405,22501 Free Cash Flow per Share,2.84,4.55,4.61,6.10,- Book Value per Share,17.00,22.50,28.90,31.25,- -Net Debt,"-24,814","-36,525","-52,052",-,- -Research & Development Exp.,"3,565","4,327","4,852","5,192","5,250" -Capital Expenditure,"7,692","8,522","8,950","9,832",- -"Selling, General & Admin. Exp.","4,614","5,569","6,495","6,145","5,725" -Shareholder’s Equity,"57,825","76,028","97,431","99,807",- -Total Assets,"99,781","126,117","153,798","155,216",- +Net Debt,-24814,-36525,-52052,-,- +Research & Development Exp.,3565,4327,4852,5192,5250 +Capital Expenditure,7692,8522,8950,9832,- +"Selling, General & Admin. Exp.",4614,5569,6495,6145,5725 +Shareholder’s Equity,57825,76028,97431,99807,- +Total Assets,99781,126117,153798,155216,- diff --git a/tests/openbb_terminal/stocks/fundamental_analysis/test_business_insider_view.py b/tests/openbb_terminal/stocks/fundamental_analysis/test_business_insider_view.py index ec7b8cbf3f21..43749382f9fc 100644 --- a/tests/openbb_terminal/stocks/fundamental_analysis/test_business_insider_view.py +++ b/tests/openbb_terminal/stocks/fundamental_analysis/test_business_insider_view.py @@ -83,5 +83,5 @@ def test_price_target_from_analysts_plt(): @pytest.mark.record_stdout def test_estimates(): business_insider_view.display_estimates( - symbol="TSLA", estimate="annualearnings", export=None + symbol="TSLA", estimate="annual_earnings", export=None ) diff --git a/tests/openbb_terminal/stocks/fundamental_analysis/test_fa_controller.py b/tests/openbb_terminal/stocks/fundamental_analysis/test_fa_controller.py index b940d1d34bb2..ca4fccfd322b 100644 --- a/tests/openbb_terminal/stocks/fundamental_analysis/test_fa_controller.py +++ b/tests/openbb_terminal/stocks/fundamental_analysis/test_fa_controller.py @@ -575,7 +575,7 @@ def test_call_func_expect_queue(expected_queue, queue, func): [], { "symbol": "TSLA", - "estimate": "annualearnings", + "estimate": "annual_earnings", "export": "", "sheet_name": None, }, diff --git a/tests/openbb_terminal/stocks/fundamental_analysis/txt/test_business_insider_view/test_estimates.txt b/tests/openbb_terminal/stocks/fundamental_analysis/txt/test_business_insider_view/test_estimates.txt index ab4e18a244f5..eb1148c34567 100644 --- a/tests/openbb_terminal/stocks/fundamental_analysis/txt/test_business_insider_view/test_estimates.txt +++ b/tests/openbb_terminal/stocks/fundamental_analysis/txt/test_business_insider_view/test_estimates.txt @@ -1,29 +1,29 @@ - 2023 2024 2025 2026 2027 -YEARLY ESTIMATES -Revenue 103,418 135,351 168,507 194,661 295,169 -Dividend 0.00 0.00 0.00 - - -Dividend Yield (in %) - - - - - -EPS 4.07 5.65 6.91 5.83 6.89 -P/E Ratio 50.51 36.42 29.74 35.29 29.83 -EBIT 14,555 21,972 27,741 30,025 43,704 -EBITDA 19,519 27,962 38,066 37,741 38,504 -Net Profit 13,506 19,372 23,768 21,372 25,801 -Net Profit Adjusted 13,533 19,368 23,752 21,372 25,801 -Pre-Tax Profit 14,454 22,034 28,932 29,404 31,787 -Net Profit (Adjusted) 14,363 22,392 29,878 29,404 31,787 -EPS (Non-GAAP) ex. SOE 4.07 5.65 6.91 5.83 6.89 -EPS (GAAP) 3.47 5.25 6.18 5.72 6.54 -Gross Income 23,115 32,466 40,982 37,942 41,972 -Cash Flow from Investing -7,935 -9,482 -10,201 -12,681 - -Cash Flow from Operations 19,523 24,129 27,935 29,376 35,875 -Cash Flow from Financing -478 -810 100 - - -Cash Flow per Share 4.92 7.39 6.94 - - -Free Cash Flow 10,541 14,300 15,203 18,405 22,501 -Free Cash Flow per Share 2.84 4.55 4.61 6.10 - -Book Value per Share 17.00 22.50 28.90 31.25 - -Net Debt -24,814 -36,525 -52,052 - - -Research & Development Exp. 3,555 4,325 4,846 5,179 5,162 -Capital Expenditure 7,672 8,522 8,950 9,832 - -Selling, General & Admin. Exp. 4,618 5,572 6,500 6,160 5,755 -Shareholder’s Equity 57,825 76,028 97,431 99,807 - -Total Assets 99,781 126,117 153,798 155,216 - + 2023 2024 2025 2026 2027 +YEARLY ESTIMATES +Revenue 103418 135351 168507 194661 295169 +Dividend 0.00 0.00 0.00 - - +Dividend Yield (in %) - - - - - +EPS 4.07 5.65 6.91 5.83 6.89 +P/E Ratio 50.51 36.42 29.74 35.29 29.83 +EBIT 14555 21972 27741 30025 43704 +EBITDA 19519 27962 38066 37741 38504 +Net Profit 13506 19372 23768 21372 25801 +Net Profit Adjusted 13533 19368 23752 21372 25801 +Pre-Tax Profit 14454 22034 28932 29404 31787 +Net Profit (Adjusted) 14363 22392 29878 29404 31787 +EPS (Non-GAAP) ex. SOE 4.07 5.65 6.91 5.83 6.89 +EPS (GAAP) 3.47 5.25 6.18 5.72 6.54 +Gross Income 23115 32466 40982 37942 41972 +Cash Flow from Investing -7935 -9482 -10201 -12681 - +Cash Flow from Operations 19523 24129 27935 29376 35875 +Cash Flow from Financing -478 -810 100 - - +Cash Flow per Share 4.92 7.39 6.94 - - +Free Cash Flow 10541 14300 15203 18405 22501 +Free Cash Flow per Share 2.84 4.55 4.61 6.10 - +Book Value per Share 17.00 22.50 28.90 31.25 - +Net Debt -24814 -36525 -52052 - - +Research & Development Exp. 3555 4325 4846 5179 5162 +Capital Expenditure 7672 8522 8950 9832 - +Selling, General & Admin. Exp. 4618 5572 6500 6160 5755 +Shareholder’s Equity 57825 76028 97431 99807 - +Total Assets 99781 126117 153798 155216 -