refactoring :)

This commit is contained in:
Jamie
2021-04-13 17:25:19 -04:00
parent c36df9bdab
commit aeaf6b4e16
3 changed files with 215 additions and 202 deletions

View File

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

View File

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

View File

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