added a plot of ef and a couple other optimization techniques. + black

This commit is contained in:
Jamie
2021-04-12 13:44:04 -04:00
parent 811f568875
commit 9045706a34
4 changed files with 269 additions and 20 deletions

View File

@@ -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 {}

View 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

View File

@@ -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]):

View File

@@ -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)