mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 04:52:07 +00:00
Portfolio optimization controller/sdk fixes (#3604)
* fix plot and show * clean duplicated code * fix msg * fix if no portfolios * improve error msg * fix msg and add integration test * final fixes * Portfolio/po | alloc : fix paths * Linting Co-authored-by: Chavithra PARANA <chavithra@gmail.com>
This commit is contained in:
54
openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb
vendored
Normal file
54
openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb
vendored
Normal file
@@ -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
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user