From f47d15088fbe76533f2d29857b58f93fcd34c9d4 Mon Sep 17 00:00:00 2001 From: James Maslek Date: Fri, 2 Dec 2022 14:34:40 -0500 Subject: [PATCH] Bug fixing (#3680) * ycrv bug * fred search multiple, test fix * ba/interest to allow multiple words * Fixes #3642 * Date logic * debug --- .../due_diligence/dd_controller.py | 350 +++++++++--------- openbb_terminal/economy/economy_controller.py | 115 +----- openbb_terminal/economy/fred_model.py | 45 ++- openbb_terminal/economy/fred_view.py | 4 +- .../behavioural_analysis/ba_controller.py | 6 +- .../economy/test_economy_controller.py | 31 -- 6 files changed, 219 insertions(+), 332 deletions(-) diff --git a/openbb_terminal/cryptocurrency/due_diligence/dd_controller.py b/openbb_terminal/cryptocurrency/due_diligence/dd_controller.py index 371390d53dfc..be2cad10d986 100644 --- a/openbb_terminal/cryptocurrency/due_diligence/dd_controller.py +++ b/openbb_terminal/cryptocurrency/due_diligence/dd_controller.py @@ -229,42 +229,41 @@ def custom_reset(self): def call_nonzero(self, other_args: List[str]): """Process nonzero command""" - if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="nonzero", - description=""" - Display addresses with nonzero assets in a certain blockchain - [Source: https://glassnode.org] - Note that free api keys only allow fetching data with a 1y lag - """, - ) - - parser.add_argument( - "-s", - "--since", - dest="since", - type=valid_date, - help="Initial date. Default: 2 years ago", - default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), - ) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="nonzero", + description=""" + Display addresses with nonzero assets in a certain blockchain + [Source: https://glassnode.org] + Note that free api keys only allow fetching data with a 1y lag + """, + ) - parser.add_argument( - "-u", - "--until", - dest="until", - type=valid_date, - help="Final date. Default: 1 year ago", - default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-s", + "--since", + dest="since", + type=valid_date, + help="Initial date. Default: 2 years ago", + default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), + ) - ns_parser = self.parse_known_args_and_warn( - parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES - ) + parser.add_argument( + "-u", + "--until", + dest="until", + type=valid_date, + help="Final date. Default: 1 year ago", + default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), + ) - if ns_parser: + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: glassnode_view.display_non_zero_addresses( symbol=self.symbol.upper(), start_date=ns_parser.since.strftime("%Y-%m-%d"), @@ -272,8 +271,8 @@ def call_nonzero(self, other_args: List[str]): export=ns_parser.export, ) - else: - console.print("Glassnode source does not support this symbol\n") + else: + console.print(f"[red]{self.symbol} not supported on Glassnode.[/red]") @log_start_end(log=logger) def call_stats(self, other_args): @@ -308,52 +307,50 @@ def call_stats(self, other_args): @log_start_end(log=logger) def call_active(self, other_args: List[str]): """Process active command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="active", + description=""" + Display active blockchain addresses over time + [Source: https://glassnode.org] + """, + ) - if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="active", - description=""" - Display active blockchain addresses over time - [Source: https://glassnode.org] - """, - ) - - parser.add_argument( - "-i", - "--interval", - dest="interval", - type=str, - help="Frequency interval. Default: 24h", - default="24h", - choices=glassnode_model.INTERVALS_ACTIVE_ADDRESSES, - ) - - parser.add_argument( - "-s", - "--since", - dest="since", - type=valid_date, - help="Initial date. Default: 1 year ago", - default=(datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-i", + "--interval", + dest="interval", + type=str, + help="Frequency interval. Default: 24h", + default="24h", + choices=glassnode_model.INTERVALS_ACTIVE_ADDRESSES, + ) - parser.add_argument( - "-u", - "--until", - dest="until", - type=valid_date, - help="Final date. Default: Today", - default=(datetime.now()).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-s", + "--since", + dest="since", + type=valid_date, + help="Initial date. Default: 1 year ago", + default=(datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d"), + ) - ns_parser = self.parse_known_args_and_warn( - parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES - ) + parser.add_argument( + "-u", + "--until", + dest="until", + type=valid_date, + help="Final date. Default: Today", + default=(datetime.now()).strftime("%Y-%m-%d"), + ) - if ns_parser: + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: glassnode_view.display_active_addresses( symbol=self.symbol.upper(), interval=ns_parser.interval, @@ -361,64 +358,61 @@ def call_active(self, other_args: List[str]): end_date=ns_parser.until.strftime("%Y-%m-%d"), export=ns_parser.export, ) - - else: - console.print("Glassnode source does not support this symbol\n") + else: + console.print(f"[red]{self.symbol} not supported on Glassnode.[/red]") @log_start_end(log=logger) def call_change(self, other_args: List[str]): """Process change command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="change", + description=""" + Display active blockchain addresses over time + [Source: https://glassnode.org] + Note that free api keys only allow fetching data with a 1y lag + """, + ) - if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="change", - description=""" - Display active blockchain addresses over time - [Source: https://glassnode.org] - Note that free api keys only allow fetching data with a 1y lag - """, - ) - - parser.add_argument( - "-e", - "--exchange", - dest="exchange", - type=str, - help="Exchange to check change. Default: aggregated", - default="aggregated", - choices=glassnode_model.GLASSNODE_SUPPORTED_EXCHANGES, - ) - - parser.add_argument( - "-s", - "--since", - dest="since", - type=valid_date, - help="Initial date. Default: 2 years ago", - default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-e", + "--exchange", + dest="exchange", + type=str, + help="Exchange to check change. Default: aggregated", + default="aggregated", + choices=glassnode_model.GLASSNODE_SUPPORTED_EXCHANGES, + ) - parser.add_argument( - "-u", - "--until", - dest="until", - type=valid_date, - help="Final date. Default: 1 year ago", - default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-s", + "--since", + dest="since", + type=valid_date, + help="Initial date. Default: 2 years ago", + default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), + ) - if other_args: - if not other_args[0][0] == "-": - other_args.insert(0, "-e") + parser.add_argument( + "-u", + "--until", + dest="until", + type=valid_date, + help="Final date. Default: 1 year ago", + default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), + ) - ns_parser = self.parse_known_args_and_warn( - parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES - ) + if other_args: + if not other_args[0][0] == "-": + other_args.insert(0, "-e") - if ns_parser: + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: glassnode_view.display_exchange_net_position_change( symbol=self.symbol.upper(), exchange=ns_parser.exchange, @@ -426,71 +420,70 @@ def call_change(self, other_args: List[str]): end_date=ns_parser.until.strftime("%Y-%m-%d"), export=ns_parser.export, ) - else: - console.print("Glassnode source does not support this symbol\n") + else: + console.print(f"[red]{self.symbol} not supported on Glassnode.[/red]") @log_start_end(log=logger) def call_eb(self, other_args: List[str]): """Process eb command""" - if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="eb", - description=""" - Display active blockchain addresses over time - [Source: https://glassnode.org] - Note that free api keys only allow fetching data with a 1y lag - """, - ) - - parser.add_argument( - "-p", - "--pct", - dest="percentage", - action="store_true", - help="Show percentage instead of stacked value. Default: False", - default=False, - ) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="eb", + description=""" + Display active blockchain addresses over time + [Source: https://glassnode.org] + Note that free api keys only allow fetching data with a 1y lag + """, + ) - parser.add_argument( - "-e", - "--exchange", - dest="exchange", - type=str, - help="Exchange to check change. Default: aggregated", - default="aggregated", - choices=glassnode_model.GLASSNODE_SUPPORTED_EXCHANGES, - ) + parser.add_argument( + "-p", + "--pct", + dest="percentage", + action="store_true", + help="Show percentage instead of stacked value. Default: False", + default=False, + ) - parser.add_argument( - "-s", - "--since", - dest="since", - type=valid_date, - help="Initial date. Default: 2 years ago", - default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-e", + "--exchange", + dest="exchange", + type=str, + help="Exchange to check change. Default: aggregated", + default="aggregated", + choices=glassnode_model.GLASSNODE_SUPPORTED_EXCHANGES, + ) - parser.add_argument( - "-u", - "--until", - dest="until", - type=valid_date, - help="Final date. Default: 1 year ago", - default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), - ) + parser.add_argument( + "-s", + "--since", + dest="since", + type=valid_date, + help="Initial date. Default: 2 years ago", + default=(datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d"), + ) - if other_args and not other_args[0][0] == "-": - other_args.insert(0, "-e") + parser.add_argument( + "-u", + "--until", + dest="until", + type=valid_date, + help="Final date. Default: 1 year ago", + default=(datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d"), + ) - ns_parser = self.parse_known_args_and_warn( - parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES - ) + if other_args and not other_args[0][0] == "-": + other_args.insert(0, "-e") - if ns_parser: + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if self.symbol.upper() in glassnode_model.GLASSNODE_SUPPORTED_ASSETS: glassnode_view.display_exchange_balances( symbol=self.symbol.upper(), exchange=ns_parser.exchange, @@ -499,9 +492,8 @@ def call_eb(self, other_args: List[str]): percentage=ns_parser.percentage, export=ns_parser.export, ) - - else: - console.print("Glassnode source does not support this symbol\n") + else: + console.print(f"[red]{self.symbol} not supported on Glassnode.[/red]") @log_start_end(log=logger) def call_oi(self, other_args): diff --git a/openbb_terminal/economy/economy_controller.py b/openbb_terminal/economy/economy_controller.py index 152a705de899..b8f774afb18d 100644 --- a/openbb_terminal/economy/economy_controller.py +++ b/openbb_terminal/economy/economy_controller.py @@ -31,7 +31,6 @@ yfinance_model, yfinance_view, investingcom_model, - investingcom_view, plot_view, commodity_view, ) @@ -661,6 +660,7 @@ def call_fred(self, other_args: List[str]): "--query", type=str, action="store", + nargs="+", dest="query", help="Query the FRED database to obtain Series IDs given the query search term.", ) @@ -675,9 +675,8 @@ def call_fred(self, other_args: List[str]): ) if ns_parser: parameters = list_from_str(ns_parser.parameter.upper()) - if ns_parser.query: - query = ns_parser.query.replace(",", " ") + query = " ".join(ns_parser.query) df_search = fred_model.get_series_notes(search_query=query) if not df_search.empty: @@ -1005,16 +1004,6 @@ def call_ycrv(self, other_args: List[str]): description="Generate country yield curve. The yield curve shows the bond rates" " at different maturities.", ) - parser.add_argument( - "-c", - "--country", - action="store", - dest="country", - default="united_states", - choices=investingcom_model.BOND_COUNTRIES, - help="Yield curve for a country. Ex: united_states", - ) - parser.add_argument( "-d", "--date", @@ -1030,105 +1019,15 @@ def call_ycrv(self, other_args: List[str]): raw=True, ) if ns_parser: - country = ns_parser.country.lower().replace("_", " ") - if country == "united states": - fred_view.display_yield_curve( - date=ns_parser.date.strftime("%Y-%m-%d") - if ns_parser.date - else None, - raw=ns_parser.raw, - export=ns_parser.export, - ) - else: - console.print("Source FRED is only available for united states.\n") + fred_view.display_yield_curve( + date=ns_parser.date.strftime("%Y-%m-%d") if ns_parser.date else "", + raw=ns_parser.raw, + export=ns_parser.export, + ) # TODO: Add `Investing` to sources again when `investpy` is fixed - @log_start_end(log=logger) - def call_spread(self, other_args: List[str]): - """Process spread command""" - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="spread", - description="Generate bond spread matrix.", - ) - parser.add_argument( - "-g", - "--group", - action="store", - dest="group", - choices=investingcom_model.MATRIX_CHOICES, - default="G7", - help="Show bond spread matrix for group of countries.", - ) - parser.add_argument( - "-c", - "--countries", - action="store", - dest="countries", - type=str, - help="Show bond spread matrix for explicit list of countries.", - ) - parser.add_argument( - "-m", - "--maturity", - action="store", - dest="maturity", - type=str, - default="10Y", - help="Specify maturity to compare rates.", - ) - parser.add_argument( - "--change", - action="store", - dest="change", - type=bool, - default=False, - help="Get matrix of 1 day change in rates or spreads.", - ) - parser.add_argument( - "--color", - action="store", - dest="color", - type=str, - choices=investingcom_view.COLORS, - default="openbb", - help="Set color palette on heatmap.", - ) - - ns_parser = self.parse_known_args_and_warn( - parser, - other_args, - export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED, - raw=True, - ) - if ns_parser: - if ns_parser.countries: - countries_string = ns_parser.countries.replace("_", " ") - countries_list = investingcom_model.countries_string_to_list( - countries_string - ) - - investingcom_view.display_spread_matrix( - countries=countries_list, - maturity=ns_parser.maturity.upper(), - change=ns_parser.change, - color=ns_parser.color, - raw=ns_parser.raw, - export=ns_parser.export, - ) - elif ns_parser.group: - investingcom_view.display_spread_matrix( - countries=ns_parser.group, - maturity=ns_parser.maturity.upper(), - change=ns_parser.change, - color=ns_parser.color, - raw=ns_parser.raw, - export=ns_parser.export, - ) - @log_start_end(log=logger) def call_events(self, other_args: List[str]): """Process events command""" diff --git a/openbb_terminal/economy/fred_model.py b/openbb_terminal/economy/fred_model.py index 3f8dc3fba905..a62d251222f1 100644 --- a/openbb_terminal/economy/fred_model.py +++ b/openbb_terminal/economy/fred_model.py @@ -256,14 +256,16 @@ def get_aggregated_series_data( @log_start_end(log=logger) @check_api_key(["API_FRED_KEY"]) def get_yield_curve( - date: str = None, + date: str = "", return_date: bool = False ) -> Tuple[pd.DataFrame, str]: """Gets yield curve data from FRED Parameters ---------- date: str - Date to get curve for. If None, gets most recent date (format yyyy-mm-dd) + Date to get curve for. If empty, gets most recent date (format yyyy-mm-dd) + return_date: bool + If True, returns date of yield curve Returns ------- @@ -275,6 +277,9 @@ def get_yield_curve( -------- >>> from openbb_terminal.sdk import openbb >>> ycrv_df = openbb.economy.ycrv() + + Since there is a delay with the data, the most recent date is returned and can be accessed with return_date=True + >>> ycrv_df, ycrv_date = openbb.economy.ycrv(return_date=True) """ # Necessary for installer so that it can locate the correct certificates for @@ -298,9 +303,20 @@ def get_yield_curve( "30Year": "DGS30", } df = pd.DataFrame() - - if date is None: + # Check that the date is in the past + today = datetime.now().strftime("%Y-%m-%d") + if date and date >= today: + console.print("[red]Date cannot be today or in the future[/red]") + if return_date: + return pd.DataFrame(), date + return pd.DataFrame() + + # Add in logic that will get the most recent date. + get_last = False + + if not date: date = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") + get_last = True for key, s_id in fred_series.items(): df = pd.concat( @@ -310,10 +326,18 @@ def get_yield_curve( ], axis=1, ) - - if date is None: - date_of_yield = df.index[-1] - rates = pd.DataFrame(df.iloc[-1, :].values, columns=["Rate"]) + if df.empty: + if return_date: + return pd.DataFrame(), date + return pd.DataFrame() + # Drop rows with NaN -- corresponding to weekends typically + df = df.dropna() + + if date not in df.index or get_last: + # If the get_last flag is true, we want the first date, otherwise we want the last date. + idx = -1 if get_last else 0 + date_of_yield = df.index[idx].strftime("%Y-%m-%d") + rates = pd.DataFrame(df.iloc[idx, :].values, columns=["Rate"]) else: date_of_yield = date series = df[df.index == date] @@ -326,5 +350,6 @@ def get_yield_curve( "Maturity", [1 / 12, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30], ) - - return rates, date_of_yield + if return_date: + return rates, date_of_yield + return rates diff --git a/openbb_terminal/economy/fred_view.py b/openbb_terminal/economy/fred_view.py index 033a74b5bee9..24bf68a0a9f5 100644 --- a/openbb_terminal/economy/fred_view.py +++ b/openbb_terminal/economy/fred_view.py @@ -185,7 +185,7 @@ def format_data_to_plot(data: pd.DataFrame, detail: dict) -> Tuple[pd.DataFrame, @log_start_end(log=logger) @check_api_key(["API_FRED_KEY"]) def display_yield_curve( - date: str = None, + date: str = "", external_axes: Optional[List[plt.Axes]] = None, raw: bool = False, export: str = "", @@ -203,7 +203,7 @@ def display_yield_curve( export : str Export data to csv,json,xlsx or png,jpg,pdf,svg file """ - rates, date_of_yield = fred_model.get_yield_curve(date) + rates, date_of_yield = fred_model.get_yield_curve(date, True) if rates.empty: console.print(f"[red]Yield data not found for {date_of_yield}.[/red]\n") return diff --git a/openbb_terminal/stocks/behavioural_analysis/ba_controller.py b/openbb_terminal/stocks/behavioural_analysis/ba_controller.py index 4e025d83f6e7..5845f2844433 100644 --- a/openbb_terminal/stocks/behavioural_analysis/ba_controller.py +++ b/openbb_terminal/stocks/behavioural_analysis/ba_controller.py @@ -637,7 +637,8 @@ def call_interest(self, other_args: List[str]): "--words", help="Select multiple sentences/words separated by commas. E.g. COVID,WW3,NFT", dest="words", - type=lambda s: [str(item) for item in s.split(",")], + nargs="+", + type=str, default=None, ) if other_args and "-" not in other_args[0][0]: @@ -648,6 +649,7 @@ def call_interest(self, other_args: List[str]): if ns_parser: if self.ticker: if ns_parser.words: + words = " ".join(ns_parser.words).split(",") df_stock = yf.download( self.ticker, start=ns_parser.start.strftime("%Y-%m-%d"), @@ -658,7 +660,7 @@ def call_interest(self, other_args: List[str]): google_view.display_correlation_interest( symbol=self.ticker, data=df_stock, - words=ns_parser.words, + words=words, export=ns_parser.export, ) else: diff --git a/tests/openbb_terminal/economy/test_economy_controller.py b/tests/openbb_terminal/economy/test_economy_controller.py index 375cced43318..3b0e84900e95 100644 --- a/tests/openbb_terminal/economy/test_economy_controller.py +++ b/tests/openbb_terminal/economy/test_economy_controller.py @@ -572,37 +572,6 @@ def test_call_func_expect_queue(expected_queue, func, queue): # [], # dict(country="portugal", export="csv", raw=False), # ), - ( - "call_spread", - [ - "--countries=United states, United Kingdom, France", - "--export=csv", - ], - "investingcom_view.display_spread_matrix", - [], - dict( - countries=["united states", "united kingdom", "france"], - maturity="10Y", - change=False, - color="openbb", - raw=False, - export="csv", - ), - ), - ( - "call_spread", - ["--group=EZ", "--color=binary", "--maturity=5Y", "--change=True"], - "investingcom_view.display_spread_matrix", - [], - dict( - countries="EZ", - maturity="5Y", - change=True, - color="binary", - raw=False, - export="", - ), - ), ( "call_events", [