Files
OpenBB/openbb_terminal/portfolio/portfolio_optimization/po_engine.py
montezdesousa faca7ab67d Portfolio optimization bug fixes (#3675)
* remove hcp

* add prams dict to statics

* little controller bug

* fix view bug

* avoid crash if one stock

* check params on setup

* create parameter class

* check and convert parameters using class

* change params name to avoid confusion

* create a parameter statics dict

* change some funcs names

* remove unused imports

* remove default dict

* optional type

* add multi choices

* cast only int and float

* fix completer

* fix bugs with parameter validation

* fix bugs with parameter validation

* add excel formatting

* sdk needs mapping as well, controller takes care of this in terminal

* small formating

* add some safe guard try except

* controller bugs

* oops

* change export path of parameters to portfolio folder

* add more commands to scripts

* catch optimization exception

* log errors

* black and exceptions

* add flag to test

* black

* flake8

* forgot this

* pylint

* change defaults

* fix ef default

* fix bl

* sync sdk defaults

* sync last defaults

* fix plot heat and add more choices to controller autocomplete

* patch weights

* fix wrong bool parsing

Co-authored-by: James Maslek <jmaslek11@gmail.com>
2022-12-02 23:56:41 +00:00

312 lines
8.3 KiB
Python

from typing import Dict, List, Tuple
import pandas as pd
from openbb_terminal.portfolio.portfolio_optimization import (
excel_model,
optimizer_model,
optimizer_helper,
)
from openbb_terminal.portfolio.portfolio_optimization.parameters import params_view
from openbb_terminal.rich_config import console
class PoEngine:
"""Portfolio Optimization Engine"""
def __init__(
self,
symbols_categories: Dict[str, Dict[str, str]] = None,
symbols_file_path: str = None,
parameters_file_path: str = None,
):
"""Initialize the engine
Parameters
----------
symbols_categories : Dict[str, float], optional
Categories, by default None
symbols_file_path : str, optional
Symbols file path, by default None
parameters_file_path : str, optional
Parameters file path, by default None
"""
self._categories: Dict[str, Dict[str, str]] = {}
self._symbols: List[str] = []
self._weights: Dict[str, float] = {}
self._returns: pd.DataFrame = None
self._params: Dict[str, float] = {}
self._current_model: str
if symbols_categories is not None:
self._symbols, self._categories = PoEngine.__parse_dictionary(
symbols_categories
)
elif symbols_file_path is not None:
self._symbols, self._categories = excel_model.load_allocation(
symbols_file_path
)
else:
raise ValueError("symbols or file_path must be provided")
if parameters_file_path is not None:
self._params, self._current_model = params_view.load_file(
parameters_file_path
)
@staticmethod
def __parse_dictionary(
symbols_categories: Dict[str, Dict[str, str]]
) -> Tuple[List[str], Dict[str, Dict[str, str]]]:
"""Parse the categories dictionary
Parameters
----------
symbols_categories : Dict[str, Dict[str, str]]
Categories
Returns
-------
Tuple[List[str], Dict[str, Dict[str, str]]]
Symbols and categories
"""
if isinstance(symbols_categories, dict):
symbols = PoEngine.__get_symbols_from_categories(symbols_categories)
else:
raise TypeError("'symbols_categories' must be a dictionary.")
for symbol in symbols:
symbols_categories["CURRENCY"].setdefault(symbol, "USD")
symbols_categories["CURRENT_INVESTED_AMOUNT"].setdefault(symbol, "0")
return symbols, symbols_categories
@staticmethod
def __get_symbols_from_categories(
symbols_categories: Dict[str, Dict[str, str]]
) -> List[str]:
"""Get the symbols from the categories dictionary
Parameters
----------
symbols_categories : Dict[str, Dict[str, str]], optional
Categories
Returns
-------
List[str]
List of symbols
"""
try:
symbols = []
for item in symbols_categories.items():
_, values = item
for v in values.keys():
symbols.append(v)
return list(set(symbols))
except Exception:
console.print(
"Unsupported dictionary format. See `portfolio.po.load` examples for correct format."
)
return []
def get_symbols(self):
return self._symbols
def get_available_categories(self) -> List[str]:
"""Get the available categories
Returns
-------
List[str]
Available categories
"""
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
Parameters
----------
category : str, optional
Category, by default None
Returns
-------
Dict[str, str]
Category
"""
if category is None:
console.print("No category provided. Please provide a category.")
return {}
d = self.get_categories_dict()
if category in d:
return d[category]
return {}
def get_categories_dict(self) -> Dict[str, Dict[str, str]]:
"""Get the categories
Returns
-------
Dict[str, Dict[str, str]]
Categories
"""
if not self._categories:
console.print("No categories found. Use 'load' to load a file.")
return {}
return self._categories
def get_category_df(self, category: str = None) -> pd.DataFrame:
"""Get the category df
Returns
-------
pd.DataFrame
Category DataFrame
"""
if category is None:
console.print("No category provided. Please provide a category.")
return pd.DataFrame()
if not self._categories:
console.print("No categories found. Use 'load' to load a file.")
return pd.DataFrame()
return optimizer_model.get_categories(
weights=self._weights, categories=self._categories, column=category
)
def set_weights(self, weights: Dict[str, float]):
"""Set the weights
Parameters
----------
weights : Dict[str, float]
Weights
"""
self._weights = weights
def get_weights(self, warning=True) -> Dict[str, float]:
"""Get the weights
Parameters
----------
warning : bool, optional
Display warning, by default True
Returns
-------
Dict[str, float]
Weights
"""
if not self._weights:
if warning:
console.print("No weights found. Please perform some optimization.")
return {}
return self._weights
def get_weights_df(self, warning=True) -> pd.DataFrame:
"""Get the weights
Parameters
----------
warning : bool, optional
Display warning, by default True
Returns
-------
pd.DataFrame
Weights
"""
if not self._weights:
if warning:
console.print("No weights found. Please perform some optimization.")
return pd.DataFrame()
return optimizer_helper.dict_to_df(self._weights)
def set_params(self, params: Dict[str, float], update=False):
"""Set the parameters
Parameters
----------
params : Dict[str, float]
Parameters
update : bool, optional
Update the current model, by default False
"""
if update:
self._params.update(params)
else:
self._params = params
def get_params(self) -> Dict:
"""Get the parameters
Returns
-------
Dict
Parameters
"""
return self._params
def set_params_from_file(self, file_path: str):
"""Set the parameters from a file
Parameters
----------
file_path : str
File path
"""
self._params, self._current_model = params_view.load_file(file_path)
def set_current_model(self, model: str):
"""Set the current model
Parameters
----------
model : str
Model
"""
self._current_model = model
def get_current_model(self) -> str:
"""Get the current model
Returns
-------
str
Current model
"""
return self._current_model
def set_returns(self, returns: pd.DataFrame):
"""Set the stock returns
Parameters
----------
returns : pd.DataFrame
Stock returns
"""
self._returns = returns
def get_returns(self) -> pd.DataFrame:
"""Get the stock returns
Returns
-------
pd.DataFrame
Stock returns
"""
if self._returns.empty:
console.print("No returns found. Please perform some optimization.")
return pd.DataFrame()
return self._returns