mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-29 21:42:22 +00:00
refactoring :)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
""" Portfolio Optimization Helper Functions"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
from typing import List
|
||||
import math
|
||||
import pandas as pd
|
||||
@@ -11,7 +12,9 @@ from pypfopt import risk_models
|
||||
from pypfopt import expected_returns
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn
|
||||
|
||||
title_maps = {}
|
||||
|
||||
|
||||
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
|
||||
@@ -29,7 +32,9 @@ def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFra
|
||||
stock_closes: DataFrame
|
||||
DataFrame containing daily (adjusted) close prices for each stock in list
|
||||
"""
|
||||
stock_prices = yf.download(list_of_stocks, period=period, group_by="ticker")
|
||||
stock_prices = yf.download(
|
||||
list_of_stocks, period=period, progress=False, group_by="ticker"
|
||||
)
|
||||
stock_closes = pd.DataFrame(index=stock_prices.index)
|
||||
for stock in list_of_stocks:
|
||||
stock_closes[stock] = stock_prices[stock]["Adj Close"]
|
||||
@@ -66,16 +71,19 @@ def display_weights(weights: dict):
|
||||
if not weights:
|
||||
return
|
||||
weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
|
||||
print(weight_df)
|
||||
weight_df["weight"] = (weight_df["value"] * 100).astype(str) + " %"
|
||||
print(weight_df["weight"])
|
||||
|
||||
|
||||
def pie_chart_weights(weights: dict):
|
||||
def pie_chart_weights(weights: dict, optimizer: str):
|
||||
"""
|
||||
Show a pie chart of holdings
|
||||
Parameters
|
||||
----------
|
||||
weights: dict
|
||||
weights to display. Keys are stocks. Values are either weights or values if -v specified
|
||||
optimzer: str
|
||||
Optmization technique used
|
||||
"""
|
||||
|
||||
if not weights:
|
||||
@@ -86,11 +94,15 @@ def pie_chart_weights(weights: dict):
|
||||
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
if math.isclose(sum(sizes), 1, rel_tol=0.1):
|
||||
wedges, _, autotexts = ax.pie(
|
||||
sizes, labels=stocks, autopct="%1.1f%%", textprops=dict(color="w")
|
||||
sizes,
|
||||
labels=stocks,
|
||||
autopct="%1.1f%%",
|
||||
textprops=dict(color="k"),
|
||||
labeldistance=5,
|
||||
)
|
||||
else:
|
||||
wedges, _, autotexts = ax.pie(
|
||||
sizes, labels=stocks, autopct="", textprops=dict(color="w")
|
||||
sizes, labels=stocks, autopct="", textprops=dict(color="k"), labeldistance=5
|
||||
)
|
||||
for i, a in enumerate(autotexts):
|
||||
a.set_text(f"{sizes[i]}")
|
||||
@@ -110,3 +122,52 @@ def pie_chart_weights(weights: dict):
|
||||
plt.ion()
|
||||
plt.show()
|
||||
print("")
|
||||
|
||||
|
||||
def parse_from_port_type(
|
||||
parser_in: argparse.ArgumentParser, port_type: str, other_args: List[str]
|
||||
):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parser_in: ArgumentParser
|
||||
parser to get data from
|
||||
port_type: str
|
||||
Type of optimization that will be done. One of ["max_sharpe","min_vol", "eff_risk", "eff_ret"]
|
||||
other_args: List[str]
|
||||
Arguments passed to function
|
||||
Returns
|
||||
-------
|
||||
|
||||
ns_parser:
|
||||
Parsed arguments
|
||||
"""
|
||||
|
||||
if port_type in ["max_sharpe", "min_volatility"]:
|
||||
ns_parser = parse_known_args_and_warn(parser_in, other_args)
|
||||
if not ns_parser:
|
||||
return None
|
||||
return ns_parser
|
||||
|
||||
elif port_type == "eff_risk":
|
||||
parser_in.add_argument(
|
||||
"-r", "--risk", type=float, dest="risk_level", default=0.1
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser_in, other_args)
|
||||
if not ns_parser:
|
||||
return None
|
||||
return ns_parser
|
||||
|
||||
elif port_type == "eff_ret":
|
||||
parser_in.add_argument(
|
||||
"-r", "--return", type=float, dest="target_return", default=0.1
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser_in, other_args)
|
||||
if not ns_parser:
|
||||
return None
|
||||
return ns_parser
|
||||
else:
|
||||
print("Incorrect portfolio optimizer type\n")
|
||||
return None
|
||||
@@ -11,10 +11,12 @@ from pypfopt import risk_models
|
||||
from pypfopt import expected_returns
|
||||
from pypfopt import EfficientFrontier
|
||||
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
|
||||
from gamestonk_terminal.portfolio_optimization.port_opt_helper import (
|
||||
from gamestonk_terminal.portfolio_optimization.optimizer_helper import (
|
||||
process_stocks,
|
||||
prepare_efficient_frontier,
|
||||
pie_chart_weights,
|
||||
display_weights,
|
||||
parse_from_port_type,
|
||||
)
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
@@ -34,13 +36,13 @@ period_choices = [
|
||||
]
|
||||
|
||||
|
||||
def equal_weight(list_of_stocks: List[str], other_args: List[str]):
|
||||
def equal_weight(stocks: List[str], other_args: List[str]):
|
||||
"""
|
||||
Equally weighted portfolio, where weight = 1/# of stocks
|
||||
|
||||
Parameters
|
||||
----------
|
||||
list_of_stocks: List[str]
|
||||
stocks: List[str]
|
||||
List of tickers to be included in optimization
|
||||
|
||||
Returns
|
||||
@@ -74,29 +76,28 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
weights = {}
|
||||
values = {}
|
||||
n_stocks = len(list_of_stocks)
|
||||
for stock in list_of_stocks:
|
||||
weights[stock] = round(1 / n_stocks, 5)
|
||||
n_stocks = len(stocks)
|
||||
for stock in stocks:
|
||||
values[stock] = ns_parser.value * round(1 / n_stocks, 5)
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(values)
|
||||
return values
|
||||
if n_stocks >= 1:
|
||||
print("Equal Weight Portfolio: ")
|
||||
display_weights(values)
|
||||
print("")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
|
||||
|
||||
def property_weighting(
|
||||
list_of_stocks: List[str], property_type: str, other_args: List[str]
|
||||
):
|
||||
def property_weighting(stocks: List[str], property_type: str, other_args: List[str]):
|
||||
"""
|
||||
Property weighted portfolio where each weight is the relative fraction.
|
||||
Parameters
|
||||
----------
|
||||
list_of_stocks: List[str]
|
||||
stocks: List[str]
|
||||
List of tickers to be included in optimization
|
||||
property_type: str
|
||||
Property to weight by. Can be anything in yfinance.Ticker().info. Examples:
|
||||
@@ -136,7 +137,7 @@ def property_weighting(
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
for stock in list_of_stocks:
|
||||
for stock in stocks:
|
||||
stock_prop = yf.Ticker(stock).info[property_type]
|
||||
if stock_prop is None:
|
||||
stock_prop = 0
|
||||
@@ -144,9 +145,19 @@ def property_weighting(
|
||||
prop_sum += stock_prop
|
||||
for k, v in prop.items():
|
||||
weights[k] = round(v / prop_sum, 5) * ns_parser.value
|
||||
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights)
|
||||
return weights
|
||||
|
||||
if property_type == "marketCap":
|
||||
print("Market Cap Weighted Portfolio: ")
|
||||
|
||||
elif property_type == "dividendYield":
|
||||
print("Dividend Yield Weighted Portfolio: ")
|
||||
|
||||
if len(stocks) >= 1:
|
||||
display_weights(weights)
|
||||
print("")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -154,7 +165,114 @@ def property_weighting(
|
||||
return
|
||||
|
||||
|
||||
def show_ef(list_of_stocks: List[str], other_args: List[str]):
|
||||
def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
|
||||
"""
|
||||
Return a portfolio based on condition in port_type Defaults to 3m of historical data
|
||||
Parameters
|
||||
----------
|
||||
stocks: List[str]
|
||||
List of the stocks to be included in the weights
|
||||
port_type: str
|
||||
Method to be used on ef object (example: max_sharpe, min_volatility)
|
||||
Returns
|
||||
-------
|
||||
weights: dict
|
||||
Dictionary of weights where keys are the tickers.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False, prog="port_type")
|
||||
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--period",
|
||||
default="3mo",
|
||||
dest="period",
|
||||
help="period to get yfinance data from",
|
||||
choices=period_choices,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--value",
|
||||
dest="value",
|
||||
help="Amount to allocate to portfolio",
|
||||
type=float,
|
||||
default=1.0,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pie",
|
||||
action="store_true",
|
||||
dest="pie",
|
||||
default=False,
|
||||
help="Display a pie chart for weights",
|
||||
)
|
||||
|
||||
try:
|
||||
ns_parser = parse_from_port_type(parser, port_type, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if len(stocks) < 2:
|
||||
print("Please have at least 2 loaded tickers to calculate weights.\n")
|
||||
return
|
||||
period = ns_parser.period
|
||||
stock_prices = process_stocks(stocks, period)
|
||||
ef = prepare_efficient_frontier(stock_prices)
|
||||
|
||||
if port_type == "max_sharpe":
|
||||
|
||||
ef_sharpe = dict(ef.max_sharpe())
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_sharpe.items()
|
||||
}
|
||||
print("Weights that maximize Sharpe Ratio:")
|
||||
|
||||
elif port_type == "min_volatility":
|
||||
|
||||
ef_min_vol = dict(ef.min_volatility())
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_min_vol.items()
|
||||
}
|
||||
print("Weights that minimize volatility")
|
||||
|
||||
elif port_type == "eff_risk":
|
||||
|
||||
ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_eff_risk.items()
|
||||
}
|
||||
print("Weights for maximizing returns at your risk level:")
|
||||
|
||||
elif port_type == "eff_ret":
|
||||
|
||||
ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_eff_risk.items()
|
||||
}
|
||||
print("Weights for minimizing risk at your return:")
|
||||
|
||||
else:
|
||||
raise ValueError("EF Method not found")
|
||||
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights, port_type)
|
||||
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return
|
||||
|
||||
|
||||
def show_ef(stocks: List[str], other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(add_help=False, prog="ef")
|
||||
|
||||
parser.add_argument(
|
||||
@@ -172,8 +290,12 @@ def show_ef(list_of_stocks: List[str], other_args: List[str]):
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return {}
|
||||
stock_prices = process_stocks(list_of_stocks, ns_parser.period)
|
||||
return
|
||||
if len(stocks) < 2:
|
||||
print("Please have at least 2 loaded tickers to calculate weights.\n")
|
||||
return
|
||||
|
||||
stock_prices = process_stocks(stocks, ns_parser.period)
|
||||
mu = expected_returns.mean_historical_return(stock_prices)
|
||||
S = risk_models.sample_cov(stock_prices)
|
||||
ef = EfficientFrontier(mu, S)
|
||||
@@ -209,152 +331,3 @@ def show_ef(list_of_stocks: List[str], other_args: List[str]):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
|
||||
|
||||
def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str]):
|
||||
"""
|
||||
Return a portfolio based on condition in port_type Defaults to 3m of historical data
|
||||
Parameters
|
||||
----------
|
||||
list_of_stocks: List[str]
|
||||
List of the stocks to be included in the weights
|
||||
port_type: str
|
||||
Method to be used on ef object (example: max_sharpe, min_volatility)
|
||||
Returns
|
||||
-------
|
||||
weights: dict
|
||||
Dictionary of weights where keys are the tickers.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False, prog="ef")
|
||||
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--period",
|
||||
default="3mo",
|
||||
dest="period",
|
||||
help="period to get yfinance data from",
|
||||
choices=period_choices,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--value",
|
||||
dest="value",
|
||||
help="Amount to allocate to portfolio",
|
||||
type=float,
|
||||
default=1.0,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pie",
|
||||
action="store_true",
|
||||
dest="pie",
|
||||
default=False,
|
||||
help="Display a pie chart for weights",
|
||||
)
|
||||
if port_type == "max_sharpe":
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return {}
|
||||
period = ns_parser.period
|
||||
stock_prices = process_stocks(list_of_stocks, period)
|
||||
ef = prepare_efficient_frontier(stock_prices)
|
||||
ef_sharpe = dict(ef.max_sharpe())
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_sharpe.items()
|
||||
}
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights)
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return {}
|
||||
|
||||
elif port_type == "min_volatility":
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return {}
|
||||
period = ns_parser.period
|
||||
stock_prices = process_stocks(list_of_stocks, period)
|
||||
ef = prepare_efficient_frontier(stock_prices)
|
||||
ef_min_vol = dict(ef.min_volatility())
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_min_vol.items()
|
||||
}
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights)
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return {}
|
||||
|
||||
elif port_type == "eff_risk":
|
||||
|
||||
parser.add_argument("-r", "--risk", type=float, dest="risk_level", default=0.1)
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if len(list_of_stocks) == 0:
|
||||
return {}
|
||||
stock_prices = process_stocks(list_of_stocks, ns_parser.period)
|
||||
ef = prepare_efficient_frontier(stock_prices)
|
||||
ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_eff_risk.items()
|
||||
}
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights)
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return {}
|
||||
|
||||
elif port_type == "eff_ret":
|
||||
|
||||
parser.add_argument(
|
||||
"-r", "--return", type=float, dest="target_return", default=0.1
|
||||
)
|
||||
try:
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
stock_prices = process_stocks(list_of_stocks, ns_parser.period)
|
||||
ef = prepare_efficient_frontier(stock_prices)
|
||||
ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
|
||||
weights = {
|
||||
key: ns_parser.value * round(value, 5)
|
||||
for key, value in ef_eff_risk.items()
|
||||
}
|
||||
if ns_parser.pie:
|
||||
pie_chart_weights(weights)
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return {}
|
||||
|
||||
else:
|
||||
raise ValueError("EF Method not found")
|
||||
@@ -9,8 +9,8 @@ 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 port_opt_api as po_api
|
||||
from gamestonk_terminal.portfolio_optimization.port_opt_helper import display_weights
|
||||
from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api
|
||||
from gamestonk_terminal.portfolio_optimization.optimizer_helper import display_weights
|
||||
|
||||
|
||||
class PortfolioOptimization:
|
||||
@@ -105,46 +105,25 @@ class PortfolioOptimization:
|
||||
self.add_stocks(self, other_args)
|
||||
|
||||
def call_equal_weight(self, other_args: List[str]):
|
||||
weights = po_api.equal_weight(self.tickers, other_args)
|
||||
print("Optimal Weights for Equal Weighting:")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.equal_weight(self.tickers, other_args)
|
||||
|
||||
def call_mkt_cap(self, other_args: List[str]):
|
||||
weights = po_api.property_weighting(self.tickers, "marketCap", other_args)
|
||||
print("Market Cap Weighting Weights:")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.property_weighting(self.tickers, "marketCap", other_args)
|
||||
|
||||
def call_div_yield(self, other_args: List[str]):
|
||||
weights = po_api.property_weighting(self.tickers, "dividendYield", other_args)
|
||||
print("Dividend Weighed Weights:")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.property_weighting(self.tickers, "dividendYield", other_args)
|
||||
|
||||
def call_max_sharpe(self, other_args: List[str]):
|
||||
weights = po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
|
||||
print("Maximum Sharpe Weights:")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
|
||||
|
||||
def call_min_vol(self, other_args: List[str]):
|
||||
weights = po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
|
||||
print("Minimum volatility Weights:")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
|
||||
|
||||
def call_eff_risk(self, other_args: List[str]):
|
||||
weights = po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
|
||||
print("Weights for max returns at risk level")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
|
||||
|
||||
def call_eff_ret(self, other_args: List[str]):
|
||||
weights = po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
|
||||
print("Weights for min risk at target returns")
|
||||
display_weights(weights)
|
||||
print("")
|
||||
po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
|
||||
|
||||
def call_show_ef(self, other_args):
|
||||
po_api.show_ef(self.tickers, other_args)
|
||||
|
||||
Reference in New Issue
Block a user