mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-30 14:02:06 +00:00
added a plot of ef and a couple other optimization techniques. + black
This commit is contained in:
@@ -3,11 +3,31 @@ __docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
from typing import List
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
from pypfopt.efficient_frontier import EfficientFrontier
|
||||
from pypfopt import plotting
|
||||
from pypfopt import risk_models
|
||||
from pypfopt import expected_returns
|
||||
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.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
|
||||
period_choices = [
|
||||
"1d",
|
||||
"5d",
|
||||
"1mo",
|
||||
"3mo",
|
||||
"6mo",
|
||||
"1y",
|
||||
"2y",
|
||||
"5y",
|
||||
"10y",
|
||||
"ytd",
|
||||
"max",
|
||||
]
|
||||
|
||||
|
||||
def equal_weight(list_of_stocks: List[str], other_args: List[str]):
|
||||
@@ -65,29 +85,166 @@ def property_weighting(
|
||||
return weights
|
||||
|
||||
|
||||
def max_sharpe(list_of_stocks: List[str], other_args: List[str]):
|
||||
def show_ef(list_of_stocks: List[str], other_args: List[str]):
|
||||
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(
|
||||
"-n", default=300, dest="n_port", help="number of portfolios to simulate"
|
||||
)
|
||||
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)
|
||||
mu = expected_returns.mean_historical_return(stock_prices)
|
||||
S = risk_models.sample_cov(stock_prices)
|
||||
ef = EfficientFrontier(mu, S)
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
# Generate random portfolios
|
||||
n_samples = ns_parser.n_port
|
||||
w = np.random.dirichlet(np.ones(len(mu)), n_samples)
|
||||
rets = w.dot(mu)
|
||||
stds = np.sqrt(np.diag(w @ S @ w.T))
|
||||
sharpes = rets / stds
|
||||
ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
|
||||
|
||||
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True)
|
||||
# Find the tangency portfolio
|
||||
ef.max_sharpe()
|
||||
ret_sharpe, std_sharpe, _ = ef.portfolio_performance()
|
||||
ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe")
|
||||
|
||||
ax.set_title("Efficient Frontier")
|
||||
ax.legend()
|
||||
plt.tight_layout()
|
||||
plt.grid(b=True, which="major", color="#666666", linestyle="-")
|
||||
plt.minorticks_on()
|
||||
plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
|
||||
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
|
||||
plt.show()
|
||||
print("")
|
||||
|
||||
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 that maximizes the sharpe ratio. Currently defaulting to 3m of historical data
|
||||
Return a portfolio based on condition in port_type Currently defaulting 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.
|
||||
"""
|
||||
|
||||
df = yf.download(list_of_stocks, period="3mo", group_by="ticker")
|
||||
df1 = pd.DataFrame(index=df.index)
|
||||
# process df
|
||||
for stock in list_of_stocks:
|
||||
df1[stock] = df[stock]["Adj Close"]
|
||||
parser = argparse.ArgumentParser(add_help=False, prog="ef")
|
||||
|
||||
mu = expected_returns.mean_historical_return(df1)
|
||||
S = risk_models.sample_cov(df1)
|
||||
ef = EfficientFrontier(mu, S)
|
||||
weights = ef.max_sharpe()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
return weights
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--period",
|
||||
default="3mo",
|
||||
dest="period",
|
||||
help="period to get yfinance data from",
|
||||
choices=period_choices,
|
||||
)
|
||||
|
||||
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: round(value, 5) for key, value in ef_sharpe.items()}
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
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: round(value, 5) for key, value in ef_min_vol.items()}
|
||||
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
|
||||
|
||||
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: round(value, 5) for key, value in ef_eff_risk.items()}
|
||||
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: round(value, 5) for key, value in ef_eff_risk.items()}
|
||||
print("")
|
||||
ef.portfolio_performance(verbose=True)
|
||||
print("")
|
||||
return weights
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("")
|
||||
return {}
|
||||
|
||||
else:
|
||||
raise ValueError("EF Method not found")
|
||||
return {}
|
||||
|
||||
51
gamestonk_terminal/portfolio_optimization/port_opt_helper.py
Normal file
51
gamestonk_terminal/portfolio_optimization/port_opt_helper.py
Normal file
@@ -0,0 +1,51 @@
|
||||
""" Portfolio Optimization Helper Functions"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
from typing import List
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
from pypfopt.efficient_frontier import EfficientFrontier
|
||||
from pypfopt import risk_models
|
||||
from pypfopt import expected_returns
|
||||
|
||||
|
||||
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
list_of_stocks: List[str]
|
||||
List of tickers to get historical data for
|
||||
period: str
|
||||
Period to get data from yfinance
|
||||
|
||||
Returns
|
||||
-------
|
||||
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_closes = pd.DataFrame(index=stock_prices.index)
|
||||
# process df
|
||||
for stock in list_of_stocks:
|
||||
stock_closes[stock] = stock_prices[stock]["Adj Close"]
|
||||
return stock_closes
|
||||
|
||||
|
||||
def prepare_efficient_frontier(stock_prices: pd.DataFrame):
|
||||
"""
|
||||
Take in a dataframe of prices and return an efficient frontier object
|
||||
Parameters
|
||||
----------
|
||||
stock_prices : DataFrame
|
||||
DataFrame where indices are DateTime and columns are stocks
|
||||
|
||||
Returns
|
||||
-------
|
||||
ef: EfficientFrontier
|
||||
EfficientFrontier object
|
||||
"""
|
||||
mu = expected_returns.mean_historical_return(stock_prices)
|
||||
S = risk_models.sample_cov(stock_prices)
|
||||
ef = EfficientFrontier(mu, S)
|
||||
return ef
|
||||
@@ -4,7 +4,7 @@ __docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
from typing import List, Set
|
||||
from datetime import datetime
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
|
||||
@@ -30,6 +30,11 @@ class PortfolioOptimization:
|
||||
"mkt_cap",
|
||||
"div_yield",
|
||||
"max_sharpe",
|
||||
"min_vol",
|
||||
"eff_risk",
|
||||
"eff_ret",
|
||||
"show_ef",
|
||||
"yolo",
|
||||
]
|
||||
|
||||
def __init__(self, tickers: Set[str] = None):
|
||||
@@ -65,7 +70,13 @@ class PortfolioOptimization:
|
||||
print(" mkt_cap marketcap weighted portfolio")
|
||||
print(" div_yield dividend weighted portfolio\n")
|
||||
print(" Mean Variance Optimization :")
|
||||
print(" max_sharpe portfolio with maximum sharpe ratio\n")
|
||||
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
|
||||
@@ -128,11 +139,39 @@ class PortfolioOptimization:
|
||||
print("")
|
||||
|
||||
def call_max_sharpe(self, other_args: List[str]):
|
||||
weights = po_api.max_sharpe(self.tickers, other_args)
|
||||
weights = po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
|
||||
print("Maximum Sharpe Weights:")
|
||||
print(weights)
|
||||
print("")
|
||||
|
||||
def call_min_vol(self, other_args: List[str]):
|
||||
weights = po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
|
||||
print("Minimum volatility Weights:")
|
||||
print(weights)
|
||||
print("")
|
||||
|
||||
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")
|
||||
print(weights)
|
||||
print("")
|
||||
|
||||
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")
|
||||
print(weights)
|
||||
print("")
|
||||
|
||||
def call_show_ef(self, other_args):
|
||||
po_api.show_ef(self.tickers, other_args)
|
||||
print("")
|
||||
|
||||
def call_yolo(self, _):
|
||||
# Easter egg :)
|
||||
print("DFV YOLO")
|
||||
print({"GME": 200})
|
||||
print("")
|
||||
|
||||
@staticmethod
|
||||
def add_stocks(self, other_args: List[str]):
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ from gamestonk_terminal.residuals_analysis import ra_controller
|
||||
from gamestonk_terminal.portfolio import port_controller
|
||||
from gamestonk_terminal.cryptocurrency import crypto_controller
|
||||
from gamestonk_terminal.screener import screener_controller
|
||||
from gamestonk_terminal.portfolio_optimization import port_optimization_controller as po_controller
|
||||
from gamestonk_terminal.portfolio_optimization import (
|
||||
port_optimization_controller as po_controller,
|
||||
)
|
||||
|
||||
# import warnings
|
||||
# warnings.simplefilter("always")
|
||||
@@ -89,7 +91,7 @@ def main():
|
||||
"pa",
|
||||
"crypto",
|
||||
"ra",
|
||||
"po"
|
||||
"po",
|
||||
]
|
||||
|
||||
menu_parser.add_argument("opt", choices=choices)
|
||||
|
||||
Reference in New Issue
Block a user