mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-01 15:01:18 +00:00
Improve portfolio optimization user experience - pedantic stuff
This commit is contained in:
@@ -27,7 +27,7 @@ title_maps = {
|
||||
|
||||
|
||||
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
|
||||
"""
|
||||
"""Get adjusted closing price for each stock in the list
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -51,8 +51,8 @@ def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFra
|
||||
|
||||
|
||||
def prepare_efficient_frontier(stock_prices: pd.DataFrame):
|
||||
"""
|
||||
Take in a dataframe of prices and return an efficient frontier object
|
||||
"""Take in a dataframe of prices and return an efficient frontier object
|
||||
|
||||
Parameters
|
||||
----------
|
||||
stock_prices : DataFrame
|
||||
@@ -82,11 +82,11 @@ def display_weights(weights: dict):
|
||||
weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
|
||||
if math.isclose(weight_df.sum()["value"], 1, rel_tol=0.1):
|
||||
weight_df["weight"] = (weight_df["value"] * 100).astype(str).apply(
|
||||
lambda s: s[:6]
|
||||
lambda s: " " + s[:4] if s.find(".") == 1 else "" + s[:5]
|
||||
) + " %"
|
||||
print(pd.DataFrame(weight_df["weight"]))
|
||||
print(pd.DataFrame(weight_df["weight"]).to_string(header=False))
|
||||
else:
|
||||
print(weight_df)
|
||||
print(weight_df.to_string(header=False))
|
||||
|
||||
|
||||
def my_autopct(x):
|
||||
@@ -107,7 +107,6 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float):
|
||||
optimzer: str
|
||||
Optimization technique used for title
|
||||
"""
|
||||
plt.close("all")
|
||||
if not weights:
|
||||
return
|
||||
|
||||
@@ -122,31 +121,28 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float):
|
||||
|
||||
total_size = np.sum(sizes)
|
||||
|
||||
leg_labels = [
|
||||
f"{str(a)}: {str(round(100*b/total_size,3))[:4]}%"
|
||||
for a, b in zip(stocks, sizes)
|
||||
]
|
||||
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
if math.isclose(sum(sizes), 1, rel_tol=0.1):
|
||||
wedges, _, autotexts = plt.pie(
|
||||
sizes,
|
||||
labels=stocks,
|
||||
autopct=my_autopct,
|
||||
textprops=dict(color="k"),
|
||||
explode=[s / (5 * total_size) for s in sizes],
|
||||
wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
|
||||
normalize=True,
|
||||
shadow=True,
|
||||
)
|
||||
plt.setp(autotexts, color="white", fontweight="bold")
|
||||
else:
|
||||
wedges, _, autotexts = plt.pie(
|
||||
sizes,
|
||||
labels=stocks,
|
||||
autopct="",
|
||||
textprops=dict(color="k"),
|
||||
explode=[s / (5 * total_size) for s in sizes],
|
||||
wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
|
||||
normalize=True,
|
||||
shadow=True,
|
||||
)
|
||||
plt.setp(autotexts, color="white", fontweight="bold")
|
||||
for i, a in enumerate(autotexts):
|
||||
if sizes[i] / total_size > 0.05:
|
||||
a.set_text(f"{sizes[i]:.2f}")
|
||||
@@ -155,24 +151,41 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float):
|
||||
|
||||
plt.axis("equal")
|
||||
|
||||
plt.legend(
|
||||
leg1 = plt.legend(
|
||||
wedges,
|
||||
leg_labels,
|
||||
title="Stocks",
|
||||
loc="center left",
|
||||
bbox_to_anchor=(0.85, 0, 0.5, 1),
|
||||
[str(s) for s in stocks],
|
||||
title=" Ticker",
|
||||
loc="upper left",
|
||||
bbox_to_anchor=(0.80, 0, 0.5, 1),
|
||||
frameon=False,
|
||||
)
|
||||
leg2 = plt.legend(
|
||||
wedges,
|
||||
[
|
||||
f"{' ' if ((100*s/total_size) < 10) else ''}{100*s/total_size:.2f}%"
|
||||
for s in sizes
|
||||
],
|
||||
title=" ",
|
||||
loc="upper left",
|
||||
handlelength=0,
|
||||
bbox_to_anchor=(0.91, 0, 0.5, 1),
|
||||
frameon=False,
|
||||
)
|
||||
plt.gca().add_artist(leg1)
|
||||
plt.gca().add_artist(leg2)
|
||||
|
||||
plt.setp(autotexts, size=8, weight="bold")
|
||||
|
||||
if optimizer in ["eff_ret", "eff_risk"]:
|
||||
plt.title(title_maps[optimizer].format(100 * value))
|
||||
plt.gca().set_title(title_maps[optimizer].format(100 * value), pad=20)
|
||||
else:
|
||||
plt.title(title_maps[optimizer])
|
||||
plt.gca().set_title(title_maps[optimizer], pad=20)
|
||||
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
plt.show()
|
||||
print("")
|
||||
|
||||
|
||||
@@ -76,14 +76,15 @@ def equal_weight(stocks: List[str], other_args: List[str]):
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if len(stocks) < 2:
|
||||
print("Please have at least 2 loaded tickers to calculate weights.\n")
|
||||
return
|
||||
values = {}
|
||||
n_stocks = len(stocks)
|
||||
for stock in stocks:
|
||||
values[stock] = ns_parser.value * round(1 / n_stocks, 5)
|
||||
values[stock] = ns_parser.value * round(1 / len(stocks), 5)
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(values, "equal", 0)
|
||||
if n_stocks >= 1:
|
||||
print("Equal Weight Portfolio: ")
|
||||
else:
|
||||
display_weights(values)
|
||||
print("")
|
||||
|
||||
@@ -137,6 +138,9 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if len(stocks) < 2:
|
||||
print("Please have at least 2 loaded tickers to calculate weights.\n")
|
||||
return
|
||||
for stock in stocks:
|
||||
stock_prop = yf.Ticker(stock).info[property_type]
|
||||
if stock_prop is None:
|
||||
@@ -148,14 +152,7 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
|
||||
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights, property_type, 0)
|
||||
|
||||
if property_type == "marketCap":
|
||||
print("Market Cap Weighted Portfolio: ")
|
||||
|
||||
elif property_type == "dividendYield":
|
||||
print("Dividend Yield Weighted Portfolio: ")
|
||||
|
||||
if len(stocks) >= 1:
|
||||
else:
|
||||
display_weights(weights)
|
||||
print("")
|
||||
|
||||
@@ -296,7 +293,7 @@ def show_ef(stocks: List[str], other_args: List[str]):
|
||||
if not ns_parser:
|
||||
return
|
||||
if len(stocks) < 2:
|
||||
print("Please have at least 2 loaded tickers to calculate weights.\n")
|
||||
print("Please have at least 2 loaded tickers to calculate weights.")
|
||||
return
|
||||
|
||||
stock_prices = process_stocks(stocks, ns_parser.period)
|
||||
|
||||
@@ -9,7 +9,7 @@ from prompt_toolkit.completion import NestedCompleter
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api
|
||||
from gamestonk_terminal.portfolio_optimization import optimizer_view
|
||||
|
||||
|
||||
class PortfolioOptimization:
|
||||
@@ -20,14 +20,14 @@ class PortfolioOptimization:
|
||||
"quit",
|
||||
"select",
|
||||
"add",
|
||||
"equal_weight",
|
||||
"mkt_cap",
|
||||
"div_yield",
|
||||
"max_sharpe",
|
||||
"min_vol",
|
||||
"eff_risk",
|
||||
"eff_ret",
|
||||
"show_ef",
|
||||
"equal",
|
||||
"mktcap",
|
||||
"dividend",
|
||||
"maxsharpe",
|
||||
"minvol",
|
||||
"maxret",
|
||||
"minrisk",
|
||||
"ef",
|
||||
"yolo",
|
||||
]
|
||||
|
||||
@@ -52,21 +52,19 @@ class PortfolioOptimization:
|
||||
print(" quit quit to abandon program")
|
||||
print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
|
||||
print("")
|
||||
print(" add add ticker to optimize")
|
||||
print(" select overwrite current tickers with new tickers")
|
||||
print(" add add ticker to optimize")
|
||||
print(" select overwrite current tickers with new tickers")
|
||||
print("")
|
||||
print("Optimization:")
|
||||
print(" equal equally weighted")
|
||||
print(" mktcap market cap weighted")
|
||||
print(" dividend dividend weighted")
|
||||
print(" maxsharpe maximum sharpe ratio")
|
||||
print(" minvol minimum volatility")
|
||||
print(" maxret maximizes returns at given risk")
|
||||
print(" minrisk minimizes risk at given return")
|
||||
print(" ef show the efficient frontier")
|
||||
print("")
|
||||
print(" equal_weight equally weighted portfolio")
|
||||
print(" mkt_cap marketcap weighted portfolio")
|
||||
print(" div_yield dividend weighted portfolio")
|
||||
print(" max_sharpe portfolio with maximum sharpe ratio")
|
||||
print(" min_vol portfolio with minimum volatility")
|
||||
print(" eff_risk portfolio that maximizes returns at given risk")
|
||||
print(" eff_ret portfolio that minimizes risk at given return")
|
||||
print(" show_ef show the efficient frontier")
|
||||
print("")
|
||||
plt.close("all")
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
@@ -97,36 +95,45 @@ class PortfolioOptimization:
|
||||
return True
|
||||
|
||||
def call_add(self, other_args: List[str]):
|
||||
"""Process add command"""
|
||||
self.add_stocks(self, other_args)
|
||||
|
||||
def call_select(self, other_args: List[str]):
|
||||
"""Process select command"""
|
||||
self.tickers = []
|
||||
self.add_stocks(self, other_args)
|
||||
|
||||
def call_equal_weight(self, other_args: List[str]):
|
||||
po_api.equal_weight(self.tickers, other_args)
|
||||
def call_equal(self, other_args: List[str]):
|
||||
"""Process equal command"""
|
||||
optimizer_view.equal_weight(self.tickers, other_args)
|
||||
|
||||
def call_mkt_cap(self, other_args: List[str]):
|
||||
po_api.property_weighting(self.tickers, "marketCap", other_args)
|
||||
def call_mktcap(self, other_args: List[str]):
|
||||
"""Process mktcap command"""
|
||||
optimizer_view.property_weighting(self.tickers, "marketCap", other_args)
|
||||
|
||||
def call_div_yield(self, other_args: List[str]):
|
||||
po_api.property_weighting(self.tickers, "dividendYield", other_args)
|
||||
def call_dividend(self, other_args: List[str]):
|
||||
"""Process dividend command"""
|
||||
optimizer_view.property_weighting(self.tickers, "dividendYield", other_args)
|
||||
|
||||
def call_max_sharpe(self, other_args: List[str]):
|
||||
po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
|
||||
def call_maxsharpe(self, other_args: List[str]):
|
||||
"""Process maxsharpe command"""
|
||||
optimizer_view.ef_portfolio(self.tickers, "max_sharpe", other_args)
|
||||
|
||||
def call_min_vol(self, other_args: List[str]):
|
||||
po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
|
||||
def call_minvol(self, other_args: List[str]):
|
||||
"""Process minvol command"""
|
||||
optimizer_view.ef_portfolio(self.tickers, "min_volatility", other_args)
|
||||
|
||||
def call_eff_risk(self, other_args: List[str]):
|
||||
po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
|
||||
def call_maxret(self, other_args: List[str]):
|
||||
"""Process maxret command"""
|
||||
optimizer_view.ef_portfolio(self.tickers, "eff_risk", other_args)
|
||||
|
||||
def call_eff_ret(self, other_args: List[str]):
|
||||
po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
|
||||
def call_minrisk(self, other_args: List[str]):
|
||||
"""Process minrisk command"""
|
||||
optimizer_view.ef_portfolio(self.tickers, "eff_ret", other_args)
|
||||
|
||||
def call_show_ef(self, other_args):
|
||||
po_api.show_ef(self.tickers, other_args)
|
||||
print("")
|
||||
def call_ef(self, other_args):
|
||||
"""Process ef command"""
|
||||
optimizer_view.show_ef(self.tickers, other_args)
|
||||
|
||||
def call_yolo(self, _):
|
||||
# Easter egg :)
|
||||
@@ -162,12 +169,14 @@ class PortfolioOptimization:
|
||||
tickers = set(self.tickers)
|
||||
for ticker in ns_parser.add_tickers:
|
||||
tickers.add(ticker)
|
||||
|
||||
if self.tickers:
|
||||
print(
|
||||
f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
|
||||
)
|
||||
|
||||
self.tickers = list(tickers)
|
||||
|
||||
print(
|
||||
f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}"
|
||||
)
|
||||
print("")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -196,6 +205,8 @@ def menu(tickers: List[str]):
|
||||
an_input = input(f"{get_flair()} (po)> ")
|
||||
|
||||
try:
|
||||
plt.close("all")
|
||||
|
||||
process_input = po_controller.switch(an_input)
|
||||
|
||||
if process_input is not None:
|
||||
|
||||
Reference in New Issue
Block a user