mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
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:
@@ -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
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user