diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb new file mode 100644 index 00000000000..7146022d153 --- /dev/null +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb @@ -0,0 +1,54 @@ +portfolio +po +show +rpf +show -pf a,b +show -ct a,b +show -pf a,b -ct c,d +plot +plot -pf a,b +plot -ct a,b +plot -pf a,b -ct c,d +load --file 50_30_10_10_Portfolio.xlsx +show -pf a,b -ct c,d +show -pf a +show -ct c +plot -pf a,b -ct c,d +plot -pf a +plot -ct c +equal +show +show -pf name_0 +show -ct sector +show -pf name_0 -ct sector +plot +plot -pf name_0 +plot -ct sector +plot -pf name_0 -ct sector +plot -pf name_0 -ct sector -pi +plot -pf name_0 -ct sector -hi +plot -pf name_0 -ct sector -dd +plot -pf name_0 -ct sector -rc +plot -pf name_0 -ct sector -he +maxsharpe +show -pf name_0,maxsharpe_1 -ct sector,country +show -pf name_0,maxsharpe_1,a -ct c,sector,currency,current_invested_amount +plot -pf name_0,maxsharpe_1 -ct c,sector,country,current_invested_amount -pi +plot -pf name_0,maxsharpe_1 -ct c,sector,current_invested_amount -hi +plot -pf name_0,maxsharpe_1 -ct c,sector,current_invested_amount -dd +plot -pf name_0,maxsharpe_1 -ct c,sector,current_invested_amount -rc +plot -pf name_0,maxsharpe_1 -ct c,sector,current_invested_amount -he +.. +load --file market.csv +po +equal +show -pf name_0 +show -pf name_0 -ct a,b +plot -pf name_0 -ct sector -pi +plot -pf name_0 -ct sector -hi +plot -pf name_0 -ct sector -dd +plot -pf name_0 -ct sector -rc +plot -pf name_0 -ct sector -he +maxsharpe +rpf -pf a,b,c +rpf -pf name_0,maxsharpe_1 diff --git a/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py b/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py index 863d6b0ac40..282d267238d 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py +++ b/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py @@ -2904,17 +2904,15 @@ def get_categories( if not weights: return pd.DataFrame() + if column == "CURRENT_INVESTED_AMOUNT": + return pd.DataFrame() + df = pd.DataFrame.from_dict( data=weights, orient="index", columns=["value"], dtype=float ) categories_df = pd.DataFrame.from_dict(data=categories, dtype=float) - col = list(categories_df.columns).index(column) - col_inv_amt = list(categories_df.columns).index("CURRENT_INVESTED_AMOUNT") - col_crncy = list(categories_df.columns).index("CURRENCY") - categories_df = df.join( - categories_df.iloc[:, [col, col_inv_amt, col_crncy]], how="inner" - ) + categories_df = df.join(categories_df) categories_df.set_index(column, inplace=True) categories_df.groupby(level=0).sum() diff --git a/openbb_terminal/portfolio/portfolio_optimization/optimizer_view.py b/openbb_terminal/portfolio/portfolio_optimization/optimizer_view.py index 5bde7dcb45c..a6b39724142 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/optimizer_view.py +++ b/openbb_terminal/portfolio/portfolio_optimization/optimizer_view.py @@ -416,6 +416,10 @@ def display_categories( title to display """ + if column == "CURRENT_INVESTED_AMOUNT": + console.print(f"[yellow]'{column}' cannot be displayed as a category.[/yellow]") + return + df = optimizer_model.get_categories(weights, categories, column) if df.empty: @@ -452,12 +456,13 @@ def display_categories( ) ) - df.reset_index(inplace=True) - df.set_index("CURRENCY", inplace=True) + df.reset_index(level=1, inplace=True) + headers = [s.title() for s in list(df.columns)] + show_index = True + if column == "CURRENCY": + show_index = False - headers = list(df.columns) - headers = [s.title() for s in headers] - print_rich_table(df, headers=headers, show_index=True, title=title) + print_rich_table(df, headers=headers, show_index=show_index, title=title) @log_start_end(log=logger) @@ -3934,6 +3939,11 @@ def additional_plots( theme.visualize_output() if heat: + + if len(weights) == 1: + console.print(f"Heatmap needs at least two values for '{category}'.") + return + if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: @@ -4004,7 +4014,7 @@ def additional_plots( theme.visualize_output(force_tight_layout=True) -def display_show(weights: Dict, tables: List[str], categories: Dict[Any, Any]): +def display_show(weights: Dict, tables: List[str], categories_dict: Dict[Any, Any]): """Display the results of the optimization. Parameters @@ -4013,7 +4023,7 @@ def display_show(weights: Dict, tables: List[str], categories: Dict[Any, Any]): Dictionary of weights. tables : List[str] List of tables to display. - categories : Dict[Any, Any] + categories_dict : Dict[Any, Any] Dictionary of categories. """ @@ -4023,7 +4033,7 @@ def display_show(weights: Dict, tables: List[str], categories: Dict[Any, Any]): console.print("") display_categories( weights=weights, - categories=categories, + categories=categories_dict, column=t, title="Category - " + t.title(), ) diff --git a/openbb_terminal/portfolio/portfolio_optimization/po_controller.py b/openbb_terminal/portfolio/portfolio_optimization/po_controller.py index 6c6a08d7371..d71a68c62f9 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/po_controller.py +++ b/openbb_terminal/portfolio/portfolio_optimization/po_controller.py @@ -5,7 +5,7 @@ __docformat__ = "numpy" import argparse import logging -from typing import List, Dict +from typing import List, Dict, Tuple from openbb_terminal.custom_prompt_toolkit import NestedCompleter @@ -60,9 +60,106 @@ def add_arguments(parser_update, parser, not_in_list): ) +def check_input( + input_type: str, input_list: List[str], available_list: List[str] +) -> List[str]: + """Check if input is valid + + Parameters + ---------- + input_type : str + Type of input + input_list : List[str] + List of input + available_list : List[str] + List of available input + + Returns + ------- + List[str] + Valid categories + """ + + valid: List[str] = [] + + for i in input_list: + if i in available_list: + valid.append(i) + else: + console.print(f"[red]{input_type} '{i}' not available.[/red]\n") + return valid + + +def get_valid_portfolio_categories( + input_portfolios: List[str], + available_portfolios: Dict, + input_categories: List[str], + available_categories: Dict, +) -> Tuple[List[str], List[str]]: + """Get valid portfolios and categories + + Parameters + ---------- + input_portfolios : List[str] + List of input portfolios + available_portfolios : Dict + Dict of available portfolios + input_categories : List[str] + List of input categories + available_categories : Dict + Dict of available categories + + Returns + ------- + Tuple[List[str], List[str]] + Valid portfolios and categories + """ + portfolios_list = list(set(available_portfolios.keys())) + categories_list = list(set(available_categories.keys())) + + if not portfolios_list: + portfolio_msg = "None. Perform some optimization to build a portfolio." + else: + if not input_portfolios: + console.print("[yellow]Please select at least one portfolio.[/yellow]\n") + portfolio_msg = ", ".join(portfolios_list) + + if not categories_list: + categories_msg = "None. Attribute some categories in the loaded file." + else: + categories_msg = ", ".join(categories_list) + + console.print( + f"[yellow]Current Portfolios: [/yellow]{portfolio_msg}\n", + ) + + console.print( + f"[yellow]Current Categories: [/yellow]{categories_msg}\n", + ) + + valid_portfolios = check_input( + input_type="Portfolio", + input_list=input_portfolios, + available_list=portfolios_list, + ) + + valid_categories = check_input( + input_type="Category", + input_list=input_categories, + available_list=categories_list, + ) + + return valid_portfolios, valid_categories + + class PortfolioOptimizationController(BaseController): """Portfolio Optimization Controller class""" + DEFAULT_PORTFOLIO_DIRECTORY = MISCELLANEOUS_DIRECTORY / "portfolio_examples" + DEFAULT_ALLOCATION_DIRECTORY = DEFAULT_PORTFOLIO_DIRECTORY / "allocation" + DEFAULT_OPTIMIZATION_DIRECTORY = DEFAULT_PORTFOLIO_DIRECTORY / "optimization" + FILE_TYPE_LIST = ["xlsx", "ini"] + CHOICES_COMMANDS = [ "show", "rpf", @@ -94,6 +191,45 @@ class PortfolioOptimizationController(BaseController): files_available: List = list() + @classmethod + def build_allocation_file_map(cls) -> dict: + allocation_file_map = { + filepath.name: filepath + for file_type in cls.FILE_TYPE_LIST + for filepath in cls.DEFAULT_ALLOCATION_DIRECTORY.rglob(f"*.{file_type}") + } + allocation_file_map.update( + { + filepath.name: filepath + for file_type in cls.FILE_TYPE_LIST + for filepath in (USER_PORTFOLIO_DATA_DIRECTORY / "allocation").rglob( + f"*.{file_type}" + ) + } + ) + + return allocation_file_map + + @classmethod + def build_optimization_file_map(cls) -> dict: + optimization_file_map = { + filepath.name: filepath + for file_type in cls.FILE_TYPE_LIST + for filepath in cls.DEFAULT_OPTIMIZATION_DIRECTORY.rglob(f"*.{file_type}") + } + + optimization_file_map.update( + { + filepath.name: filepath + for file_type in cls.FILE_TYPE_LIST + for filepath in (USER_PORTFOLIO_DATA_DIRECTORY / "optimization").rglob( + f"*.{file_type}" + ) + } + ) + + return optimization_file_map + def __init__( self, tickers: List[str] = None, @@ -118,49 +254,18 @@ class PortfolioOptimizationController(BaseController): if categories: self.categories = dict(categories) + self.available_categories = list(self.categories.keys()) else: self.categories = dict() + self.available_categories = list() self.count = 0 self.current_portfolio = "" - self.file_types = ["xlsx", "ini"] - self.DEFAULT_ALLOCATION_PATH = USER_PORTFOLIO_DATA_DIRECTORY / "allocation" - - self.DATA_ALLOCATION_FILES = { - filepath.name: filepath - for file_type in self.file_types - for filepath in self.DEFAULT_ALLOCATION_PATH.rglob(f"*.{file_type}") - } - - self.DATA_ALLOCATION_FILES.update( - { - filepath.name: filepath - for file_type in self.file_types - for filepath in ( - MISCELLANEOUS_DIRECTORY / "portfolio_examples" / "allocation" - ).rglob(f"*.{file_type}") - } - ) + self.allocation_file_map = self.build_allocation_file_map() + self.optimization_file_map = self.build_optimization_file_map() self.current_file = "" - self.DEFAULT_OPTIMIZATION_PATH = USER_PORTFOLIO_DATA_DIRECTORY / "optimization" - - self.DATA_OPTIMIZATION_FILES = { - filepath.name: filepath - for file_type in self.file_types - for filepath in self.DEFAULT_OPTIMIZATION_PATH.rglob(f"*.{file_type}") - } - - self.DATA_OPTIMIZATION_FILES.update( - { - filepath.name: filepath - for file_type in self.file_types - for filepath in ( - MISCELLANEOUS_DIRECTORY / "portfolio_examples" / "optimization" - ).rglob(f"*.{file_type}") - } - ) self.params: Dict = {} @@ -185,7 +290,7 @@ class PortfolioOptimizationController(BaseController): mt.add_param("_loaded", self.current_portfolio or "") mt.add_raw("\n") mt.add_param("_tickers", ", ".join(self.tickers)) - mt.add_param("_categories", ", ".join(self.categories.keys())) + mt.add_param("_categories", ", ".join(self.available_categories)) mt.add_raw("\n") mt.add_cmd("file") mt.add_menu("params") @@ -302,7 +407,7 @@ class PortfolioOptimizationController(BaseController): "--categories", dest="categories", type=lambda s: [str(item).upper() for item in s.split(",")], - default=["ASSET_CLASS", "COUNTRY", "SECTOR", "INDUSTRY"], + default=self.available_categories, help="Show selected categories", ) if p: @@ -463,7 +568,7 @@ class PortfolioOptimizationController(BaseController): nargs="+", dest="file", help="Parameter file to be used", - choices=self.DATA_OPTIMIZATION_FILES, + choices=self.optimization_file_map.keys(), metavar="FILE", ) if other_args and "-" not in other_args[0][0]: @@ -474,8 +579,8 @@ class PortfolioOptimizationController(BaseController): if ns_parser: self.current_file = " ".join(ns_parser.file) - if self.current_file in self.DATA_OPTIMIZATION_FILES: - file_location = self.DATA_OPTIMIZATION_FILES[self.current_file] + if self.current_file in self.optimization_file_map: + file_location = self.optimization_file_map[self.current_file] else: file_location = self.current_file # type: ignore @@ -521,26 +626,22 @@ class PortfolioOptimizationController(BaseController): parser = self.po_parser(parser, ct=True) ns_parser = self.parse_known_args_and_warn(parser, other_args) + if ns_parser: - portfolios = set(self.portfolios.keys()) + portfolios, categories = get_valid_portfolio_categories( + input_portfolios=ns_parser.portfolios, + available_portfolios=self.portfolios, + input_categories=ns_parser.categories, + available_categories=self.categories, + ) - if not ns_parser.portfolios: - console.print( - f"[yellow]Current Portfolios:[/yellow] {('None', ', '.join(portfolios))[bool(portfolios)]}" + for p in portfolios: + console.print("[yellow]Portfolio[/yellow]: " + p + "\n") + optimizer_view.display_show( + weights=self.portfolios[p], + tables=categories, + categories_dict=self.categories, ) - c = ("None", ", ".join(ns_parser.categories))[ - bool(ns_parser.categories) - ] - console.print(f"\n[yellow]Current Categories:[/yellow] {c}") - - for portfolio in ns_parser.portfolios: - if portfolio in portfolios: - console.print("[yellow]Portfolio[/yellow]: " + portfolio + "\n") - optimizer_view.display_show( - weights=self.portfolios[portfolio], - tables=ns_parser.categories, - categories=self.categories, - ) @log_start_end(log=logger) def call_rpf(self, other_args: List[str]): @@ -569,10 +670,13 @@ class PortfolioOptimizationController(BaseController): if portfolio in portfolios: self.portfolios.pop(portfolio) portfolios.remove(portfolio) + console.print(f"[yellow]Removed '{portfolio}'.[/yellow]") + else: + console.print(f"[red]Portfolio '{portfolio}' does not exist.[/red]") if self.portfolios: console.print( - f"\nCurrent Portfolios: {('None', ', '.join(portfolios))[bool(portfolios)]}" + f"\n[yellow]Current Portfolios: [/yellow]{('None', ', '.join(portfolios))[bool(portfolios)]}" ) self.update_runtime_choices() @@ -592,7 +696,7 @@ class PortfolioOptimizationController(BaseController): nargs="+", dest="file", help="Allocation file to be used", - choices=self.DATA_OPTIMIZATION_FILES, + choices=self.allocation_file_map.keys(), metavar="FILE", ) if other_args and "-" not in other_args[0][0]: @@ -602,12 +706,15 @@ class PortfolioOptimizationController(BaseController): if ns_parser: filename = " ".join(ns_parser.file) - if filename in self.DATA_ALLOCATION_FILES: - file_location = self.DATA_ALLOCATION_FILES[filename] + if filename in self.allocation_file_map: + file_location = self.allocation_file_map[filename] else: file_location = filename # type: ignore self.tickers, self.categories = excel_model.load_allocation(file_location) + self.available_categories = list(self.categories.keys()) + if "CURRENT_INVESTED_AMOUNT" in self.available_categories: + self.available_categories.remove("CURRENT_INVESTED_AMOUNT") self.portfolios = dict() self.update_runtime_choices() self.current_portfolio = filename @@ -690,13 +797,28 @@ class PortfolioOptimizationController(BaseController): ns_parser = self.parse_known_args_and_warn(parser, other_args) if ns_parser: - if len(self.tickers) < 2: + portfolios, categories = get_valid_portfolio_categories( + input_portfolios=ns_parser.portfolios, + available_portfolios=self.portfolios, + input_categories=ns_parser.categories, + available_categories=self.categories, + ) + if not portfolios: + return + + if not ( + ns_parser.pie + or ns_parser.hist + or ns_parser.dd + or ns_parser.rc_chart + or ns_parser.heat + ): console.print( - "Please have at least 2 loaded tickers to calculate weights.\n" + "[yellow]Please select at least one chart to plot[/yellow]", + "[yellow]from the following: -pi, -hi, -dd, -rc, -he.[/yellow]", ) return - portfolios = ns_parser.portfolios stocks = [] for i in portfolios: stocks += list(self.portfolios[i].keys()) @@ -716,34 +838,46 @@ class PortfolioOptimizationController(BaseController): value=1, ) - categories = ns_parser.categories + if ns_parser.hist or ns_parser.dd: + for i in portfolios: + weights = self.portfolios[i] + weights = dict( + sorted(weights.items(), key=lambda x: x[1], reverse=True) + ) + stocks = list(weights.keys()) - for i in portfolios: - weights = self.portfolios[i] - weights = dict( - sorted(weights.items(), key=lambda x: x[1], reverse=True) - ) - stocks = list(weights.keys()) - optimizer_view.additional_plots( - weights=weights, - data=stock_returns[stocks], - category=None, - title_opt=i, - freq=ns_parser.return_frequency, - risk_measure=ns_parser.risk_measure.lower(), - risk_free_rate=ns_parser.risk_free, - alpha=ns_parser.significance_level, - a_sim=100, - beta=ns_parser.significance_level, - b_sim=100, - pie=ns_parser.pie, - hist=ns_parser.hist, - dd=ns_parser.dd, - rc_chart=ns_parser.rc_chart, - heat=ns_parser.heat, - external_axes=None, - ) - if ns_parser.pie or ns_parser.heat or ns_parser.rc_chart: + # hist and dd are transversal to all categories + optimizer_view.additional_plots( + weights=weights, + data=stock_returns[stocks], + title_opt=i, + freq=ns_parser.return_frequency, + risk_measure=ns_parser.risk_measure.lower(), + risk_free_rate=ns_parser.risk_free, + alpha=ns_parser.significance_level, + a_sim=100, + beta=ns_parser.significance_level, + b_sim=100, + hist=ns_parser.hist, + dd=ns_parser.dd, + ) + + if ns_parser.pie or ns_parser.rc_chart or ns_parser.heat: + + if not categories: + console.print( + "[yellow]Categories must be provided to use -pi, -rc or -he.[/yellow]" + ) + return + + for i in portfolios: + weights = self.portfolios[i] + weights = dict( + sorted(weights.items(), key=lambda x: x[1], reverse=True) + ) + stocks = list(weights.keys()) + + # pie, rc_chart and heat apply to each category for category in categories: filtered_categories = dict( filter( @@ -764,11 +898,8 @@ class PortfolioOptimizationController(BaseController): beta=ns_parser.significance_level, b_sim=100, pie=ns_parser.pie, - hist=ns_parser.hist, - dd=ns_parser.dd, rc_chart=ns_parser.rc_chart, heat=ns_parser.heat, - external_axes=None, ) @log_start_end(log=logger) diff --git a/openbb_terminal/portfolio/portfolio_optimization/po_engine.py b/openbb_terminal/portfolio/portfolio_optimization/po_engine.py index d46854f4c4c..ca1e1d782bf 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/po_engine.py +++ b/openbb_terminal/portfolio/portfolio_optimization/po_engine.py @@ -124,7 +124,12 @@ class PoEngine: List[str] Available categories """ - return list(self._categories.keys()) + available_categories = list(self._categories.keys()) + + if "CURRENT_INVESTED_AMOUNT" in available_categories: + available_categories.remove("CURRENT_INVESTED_AMOUNT") + + return available_categories def get_category(self, category: str = None) -> Dict[str, str]: """Get the category diff --git a/openbb_terminal/portfolio/portfolio_optimization/po_model.py b/openbb_terminal/portfolio/portfolio_optimization/po_model.py index fbe77182ef6..531ce966706 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/po_model.py +++ b/openbb_terminal/portfolio/portfolio_optimization/po_model.py @@ -88,16 +88,16 @@ def generate_portfolio( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.equal(portfolio_engine=p) @@ -385,16 +385,16 @@ def get_maxsharpe( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.maxsharpe(portfolio_engine=p) @@ -531,16 +531,16 @@ def get_minrisk( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.minrisk(portfolio_engine=p) @@ -677,16 +677,16 @@ def get_maxutil( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.maxutil(portfolio_engine=p) @@ -823,16 +823,16 @@ def get_maxret( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.maxret(portfolio_engine=p) @@ -946,16 +946,16 @@ def get_maxdiv( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.maxdiv(portfolio_engine=p) @@ -1047,16 +1047,16 @@ def get_maxdecorr( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.maxdecorr(portfolio_engine=p) @@ -1157,16 +1157,16 @@ def get_blacklitterman( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.blacklitterman(portfolio_engine=p) @@ -1283,16 +1283,16 @@ def get_ef( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.ef(portfolio_engine=p) @@ -1414,16 +1414,16 @@ def get_riskparity( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.riskparity(portfolio_engine=p) @@ -1558,16 +1558,16 @@ def get_relriskparity( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.relriskparity(portfolio_engine=p) @@ -1759,16 +1759,16 @@ def get_hrp(portfolio_engine: PoEngine = None, **kwargs) -> Tuple[pd.DataFrame, "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.hrp(portfolio_engine=p) @@ -1958,16 +1958,16 @@ def get_herc(portfolio_engine: PoEngine = None, **kwargs) -> Tuple[pd.DataFrame, "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.herc(portfolio_engine=p) @@ -2157,16 +2157,16 @@ def get_nco(portfolio_engine: PoEngine = None, **kwargs) -> Tuple[pd.DataFrame, "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.nco(portfolio_engine=p) @@ -2234,16 +2234,16 @@ def get_equal(portfolio_engine: PoEngine = None, **kwargs) -> Tuple[pd.DataFrame "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.equal(portfolio_engine=p) @@ -2315,16 +2315,16 @@ def get_mktcap( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.mktcap(portfolio_engine=p) @@ -2396,16 +2396,16 @@ def get_dividend( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.dividend(portfolio_engine=p) @@ -2482,16 +2482,16 @@ def get_property( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.property(portfolio_engine=p) @@ -2556,21 +2556,21 @@ def show( "MSFT": "INFORMATION TECHNOLOGY", "AMZN": "CONSUMER DISCRETIONARY", }, - "CURRENT_INVESTED_AMOUNT": { - "AAPL": "100000.0", - "MSFT": "200000.0", - "AMZN": "300000.0", - }, "CURRENCY": { "AAPL": "USD", "MSFT": "USD", "AMZN": "USD", }, + "CURRENT_INVESTED_AMOUNT": { + "AAPL": "100000.0", + "MSFT": "200000.0", + "AMZN": "300000.0", + }, } >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.equal(portfolio_engine=p) >>> p.get_available_categories() - ['SECTOR'] + ['SECTOR', 'CURRENCY'] >>> weights_df, category_df = openbb.portfolio.po.show(portfolio_engine=p, category="SECTOR") >>> from openbb_terminal.sdk import openbb @@ -2581,7 +2581,6 @@ def show( 'SECTOR', 'INDUSTRY', 'COUNTRY', - 'CURRENT_INVESTED_AMOUNT', 'CURRENCY'] >>> weights_df, category_df = openbb.portfolio.po.show(portfolio_engine=p, category="ASSET_CLASS") """ diff --git a/openbb_terminal/portfolio/portfolio_optimization/po_view.py b/openbb_terminal/portfolio/portfolio_optimization/po_view.py index 78adb3df1f2..8e4d972ceab 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/po_view.py +++ b/openbb_terminal/portfolio/portfolio_optimization/po_view.py @@ -280,7 +280,7 @@ def display_plot(portfolio_engine: PoEngine = None, chart_type: str = "pie", **k >>> p = openbb.portfolio.po.load(symbols_categories=d) >>> weights, performance = openbb.portfolio.po.equal(portfolio_engine=p) >>> p.get_available_categories() - ['SECTOR'] + ['SECTOR', 'CURRENCY'] >>> openbb.portfolio.po.plot(portfolio_engine=p, category="SECTOR", chart_type="pie") >>> openbb.portfolio.po.plot(portfolio_engine=p, category="SECTOR", chart_type="hist") >>> openbb.portfolio.po.plot(portfolio_engine=p, category="SECTOR", chart_type="dd") @@ -295,7 +295,6 @@ def display_plot(portfolio_engine: PoEngine = None, chart_type: str = "pie", **k 'SECTOR', 'INDUSTRY', 'COUNTRY', - 'CURRENT_INVESTED_AMOUNT', 'CURRENCY'] >>> openbb.portfolio.po.plot(portfolio_engine=p, category="ASSET_CLASS", chart_type="pie") >>> openbb.portfolio.po.plot(portfolio_engine=p, category="SECTOR", chart_type="hist") @@ -403,6 +402,10 @@ def display_heat(**kwargs): title = kwargs.get("title", "") external_axes = kwargs.get("external_axes", None) + if len(weights) == 1: + console.print(f"Heatmap needs at least two values for '{category}'.") + return + if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: