Fix portfolio optimization SDK bugs (#3582)

* Small improvement to text handling

* fix plot and engine

* change msg

* fix po engine

* fix rc

* fix error

* opt pytest

* bug fix

Co-authored-by: colin99d <colin99delahunty@gmail.com>
Co-authored-by: minhhoang1023 <40023817+minhhoang1023@users.noreply.github.com>
Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
montezdesousa
2022-11-25 23:10:48 +00:00
committed by GitHub
parent 79046608b6
commit 7480c5efe0
5 changed files with 562 additions and 320 deletions

View File

@@ -1,4 +1,4 @@
from typing import Dict, List
from typing import Dict, List, Tuple
import pandas as pd
from openbb_terminal.portfolio.portfolio_optimization import (
excel_model,
@@ -14,8 +14,7 @@ class PoEngine:
def __init__(
self,
symbols: List[str] = None,
categories: Dict[str, Dict[str, str]] = None,
symbols_categories: Dict[str, Dict[str, str]] = None,
symbols_file_path: str = None,
parameters_file_path: str = None,
):
@@ -23,9 +22,7 @@ class PoEngine:
Parameters
----------
symbols : List[str]
List of symbols
categories : Dict[str, float], optional
symbols_categories : Dict[str, float], optional
Categories, by default None
symbols_file_path : str, optional
Symbols file path, by default None
@@ -33,14 +30,18 @@ class PoEngine:
Parameters file path, by default None
"""
self._categories: Dict[str, Dict[str, str]] = categories or {}
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 is not None:
self._symbols: List[str] = symbols
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
@@ -53,6 +54,65 @@ class PoEngine:
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
@@ -66,16 +126,6 @@ class PoEngine:
"""
return list(self._categories.keys())
def set_categories_dict(self, categories: Dict[str, Dict[str, str]]):
"""Set the categories
Parameters
----------
categories : Dict[str, Dict[str, str]]
Categories
"""
self._categories = categories
def get_category(self, category: str = None) -> Dict[str, str]:
"""Get the category

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ __docformat__ = "numpy"
import logging
import math
import warnings
from typing import List
import matplotlib
import matplotlib.pyplot as plt
@@ -38,7 +37,7 @@ logger = logging.getLogger(__name__)
@log_start_end(log=logger)
def display_ef(portfolio_engine: PoEngine = None, symbols: List[str] = None, **kwargs):
def display_ef(portfolio_engine: PoEngine = None, **kwargs):
"""Display efficient frontier
Parameters
@@ -46,8 +45,6 @@ def display_ef(portfolio_engine: PoEngine = None, symbols: List[str] = None, **k
portfolio_engine : PoEngine, optional
Portfolio optimization engine, by default None
Use `portfolio.po.load` to load a portfolio engine
symbols : List[str], optional
List of symbols, by default None
interval : str, optional
Interval to get data, by default '3y'
start_date : str, optional
@@ -99,32 +96,45 @@ def display_ef(portfolio_engine: PoEngine = None, symbols: List[str] = None, **k
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> frontier = openbb.portfolio.po.ef_chart(symbols=["AAPL", "MSFT", "AMZN"])
>>> d = {
"SECTOR": {
"AAPL": "INFORMATION TECHNOLOGY",
"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",
},
}
>>> p = openbb.portfolio.po.load(symbols_categories=d)
>>> openbb.portfolio.po.ef_chart(portfolio_engine=p)
>>> from openbb_terminal.sdk import openbb
>>> p = openbb.portfolio.po.load(symbols_file_path="openbb_terminal/miscellaneous/portfolio_examples/allocation/60_40_Portfolio.xlsx")
>>> frontier = openbb.portfolio.po.ef_chart(portfolio_engine=p)
>>> openbb.portfolio.po.ef_chart(portfolio_engine=p)
"""
valid_symbols, valid_portfolio_engine, valid_kwargs = validate_inputs(
symbols, portfolio_engine, kwargs
)
n_portfolios = valid_kwargs.get("n_portfolios", 100)
freq = valid_kwargs.get("freq", "D")
risk_measure = valid_kwargs.get("risk_measure", "MV")
risk_free_rate = valid_kwargs.get("risk_free_rate", 0.0)
alpha = valid_kwargs.get("alpha", 0.05)
n_portfolios = kwargs.get("n_portfolios", 100)
freq = kwargs.get("freq", "D")
risk_measure = kwargs.get("risk_measure", "MV")
risk_free_rate = kwargs.get("risk_free_rate", 0.0)
alpha = kwargs.get("alpha", 0.05)
# Pop chart args
tangency = valid_kwargs.pop("tangency", False)
plot_tickers = valid_kwargs.pop("plot_tickers", False)
external_axes = valid_kwargs.pop("external_axes", None)
tangency = kwargs.pop("tangency", False)
plot_tickers = kwargs.pop("plot_tickers", False)
external_axes = kwargs.pop("external_axes", None)
frontier, mu, cov, stock_returns, weights, X1, Y1, port = get_ef(
valid_portfolio_engine,
valid_symbols,
**valid_kwargs,
portfolio_engine,
**kwargs,
)
risk_free_rate = risk_free_rate / TIME_FACTOR[freq.upper()]
@@ -250,25 +260,24 @@ def display_plot(portfolio_engine: PoEngine = None, chart_type: str = "pie", **k
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> p = openbb.portfolio.po.load(symbols=["AAPL", "MSFT", "AMZN"])
>>> d = {
"SECTOR": {
"AAPL": "INFORMATION TECHNOLOGY",
"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",
},
}
>>> p.set_categories_dict(categories=d)
"SECTOR": {
"AAPL": "INFORMATION TECHNOLOGY",
"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",
},
}
>>> p = openbb.portfolio.po.load(symbols_categories=d)
>>> weights, performance = openbb.portfolio.po.equal(portfolio_engine=p)
>>> p.get_available_categories()
['SECTOR']
@@ -370,15 +379,15 @@ def display_plot(portfolio_engine: PoEngine = None, chart_type: str = "pie", **k
valid_kwargs["colors"] = theme.get_colors()
if chart_type == "pie":
display_pie(**kwargs)
display_pie(**valid_kwargs)
elif chart_type == "hist":
display_hist(**kwargs)
display_hist(**valid_kwargs)
elif chart_type == "dd":
display_dd(**kwargs)
elif chart_type == "rc_chart":
display_rc(**kwargs)
display_dd(**valid_kwargs)
elif chart_type == "rc":
display_rc(**valid_kwargs)
elif chart_type == "heat":
display_heat(**kwargs)
display_heat(**valid_kwargs)
else:
console.print(
"Invalid chart type, please choose from the following: pie, hist, dd, rc, heat"

View File

@@ -312,6 +312,11 @@ def pytest_addoption(parser: Parser):
action="store_true",
help="To run tests with the marker : @pytest.mark.prediction",
)
parser.addoption(
"--optimization",
action="store_true",
help="To run tests with the marker : @pytest.mark.prediction",
)
parser.addoption(
"--rewrite-expected",
action="store_true",

View File

@@ -0,0 +1,7 @@
import pytest
from _pytest.nodes import Node
def pytest_runtest_setup(item: Node):
if not item.config.getoption("--optimization"):
pytest.skip(msg="Runs only with option : --optimization")