From 4f9669684b1053462dcf4de47f237ea64f354078 Mon Sep 17 00:00:00 2001 From: didier Date: Thu, 15 Apr 2021 22:12:45 +0100 Subject: [PATCH 01/20] Improve portfolio optimization user experience - pedantic stuff --- .../optimizer_helper.py | 57 ++++++----- .../portfolio_optimization/optimizer_view.py | 23 ++--- .../portfolio_optimization/po_controller.py | 95 +++++++++++-------- 3 files changed, 98 insertions(+), 77 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py index 3ca81f396a2..24db45eed91 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py @@ -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("") diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index e9d74a76138..6e779b0669a 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -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) diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index df2f8a55fa6..d4d2960adee 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -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: From 57d56b8b1e2903fada239270b2c3cb59f5ea3e5d Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 00:38:14 +0100 Subject: [PATCH 02/20] some more minor refactoring cuz im picky --- gamestonk_terminal/helper_funcs.py | 2 +- .../optimizer_helper.py | 176 +++++++----- .../portfolio_optimization/optimizer_view.py | 256 +++++++++++++----- .../portfolio_optimization/po_controller.py | 55 ++-- 4 files changed, 335 insertions(+), 154 deletions(-) diff --git a/gamestonk_terminal/helper_funcs.py b/gamestonk_terminal/helper_funcs.py index 11a3ffbe196..a2e00fd3d35 100644 --- a/gamestonk_terminal/helper_funcs.py +++ b/gamestonk_terminal/helper_funcs.py @@ -347,7 +347,7 @@ def patch_pandas_text_adjustment(): def parse_known_args_and_warn(parser, l_args): parser.add_argument( - "-h", "--help", action="store_true", dest="help", help="show this help message" + "-h", "--help", action="store_true", help="show this help message" ) (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py index 24db45eed91..19f9057bd76 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py @@ -15,15 +15,107 @@ from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn -title_maps = { - "max_sharpe": "Maximum Sharpe Portfolio", - "min_volatility": "Minimum Volatility Portfolio", - "eff_risk": "Maximum Return Portfolio at Risk = {:1f} %", - "eff_ret": "Minimum Volatility Portfolio at Target Return = {:.1f} %", - "equal": "Equally Weighted Portfolio", - "marketCap": "MarketCap Weighted Portfolio", - "dividendYield": "Dividend Yield Weighted Portfolio", -} +l_valid_property_infos = [ + "previousClose", + "regularMarketOpen", + "twoHundredDayAverage", + "trailingAnnualDividendYield", + "payoutRatio", + "volume24Hr", + "regularMarketDayHigh", + "navPrice", + "averageDailyVolume10Day", + "totalAssets", + "regularMarketPreviousClose", + "fiftyDayAverage", + "trailingAnnualDividendRate", + "open", + "toCurrency", + "averageVolume10days", + "expireDate", + "yield", + "algorithm", + "dividendRate", + "exDividendDate", + "beta", + "circulatingSupply", + "regularMarketDayLow", + "priceHint", + "currency", + "trailingPE", + "regularMarketVolume", + "lastMarket", + "maxSupply", + "openInterest", + "marketCap", + "volumeAllCurrencies", + "strikePrice", + "averageVolume", + "priceToSalesTrailing12Months", + "dayLow", + "ask", + "ytdReturn", + "askSize", + "volume", + "fiftyTwoWeekHigh", + "forwardPE", + "fromCurrency", + "fiveYearAvgDividendYield", + "fiftyTwoWeekLow", + "bid", + "dividendYield", + "bidSize", + "dayHigh", + "annualHoldingsTurnover", + "enterpriseToRevenue", + "beta3Year", + "profitMargins", + "enterpriseToEbitda", + "52WeekChange", + "morningStarRiskRating", + "forwardEps", + "revenueQuarterlyGrowth", + "sharesOutstanding", + "fundInceptionDate", + "annualReportExpenseRatio", + "bookValue", + "sharesShort", + "sharesPercentSharesOut", + "fundFamily", + "lastFiscalYearEnd", + "heldPercentInstitutions", + "netIncomeToCommon", + "trailingEps", + "lastDividendValue", + "SandP52WeekChange", + "priceToBook", + "heldPercentInsiders", + "shortRatio", + "sharesShortPreviousMonthDate", + "floatShares", + "enterpriseValue", + "threeYearAverageReturn", + "lastSplitFactor", + "legalType", + "lastDividendDate", + "morningStarOverallRating", + "earningsQuarterlyGrowth", + "pegRatio", + "lastCapGain", + "shortPercentOfFloat", + "sharesShortPriorMonth", + "impliedSharesOutstanding", + "fiveYearAverageReturn", + "regularMarketPrice", +] + + +def check_valid_property_type(property: str) -> str: + """ Check that the property selected is valid """ + if property in l_valid_property_infos: + return property + else: + raise argparse.ArgumentTypeError(f"{property} is not a valid info") def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame: @@ -97,15 +189,15 @@ def my_autopct(x): return "" -def pie_chart_weights(weights: dict, optimizer: str, value: float): - """ - Show a pie chart of holdings +def pie_chart_weights(weights: dict, title_opt: 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 - Optimization technique used for title + Weights to display, where keys are tickers, and values are either weights or values if -v specified + title: str + Title to be used on the plot title """ if not weights: return @@ -176,10 +268,7 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float): plt.setp(autotexts, size=8, weight="bold") - if optimizer in ["eff_ret", "eff_risk"]: - plt.gca().set_title(title_maps[optimizer].format(100 * value), pad=20) - else: - plt.gca().set_title(title_maps[optimizer], pad=20) + plt.gca().set_title(title_opt, pad=20) if gtff.USE_ION: plt.ion() @@ -188,52 +277,3 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float): 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 diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index 6e779b0669a..4f7170e1fd8 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -16,7 +16,7 @@ from gamestonk_terminal.portfolio_optimization.optimizer_helper import ( prepare_efficient_frontier, pie_chart_weights, display_weights, - parse_from_port_type, + check_valid_property_type, ) from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal import feature_flags as gtff @@ -35,6 +35,20 @@ period_choices = [ "max", ] +d_period = { + "1d": "[1 Day]", + "5d": "[5 Days]", + "1mo": "[1 Month]", + "3mo": "[3 Months]", + "6mo": "[6 Months]", + "1y": "[1 Year]", + "2y": "[2 Years]", + "5y": "[5 Years]", + "10y": "[10 Years]", + "ytd": "[Year-to-Date]", + "max": "[All-time]", +} + def equal_weight(stocks: List[str], other_args: List[str]): """ @@ -53,8 +67,8 @@ def equal_weight(stocks: List[str], other_args: List[str]): """ parser = argparse.ArgumentParser( add_help=False, - prog="equal_weight", - description="Return equally weighted portfolio holdings", + prog="equal", + description="Returns an equally weighted portfolio", ) parser.add_argument( @@ -83,7 +97,7 @@ def equal_weight(stocks: List[str], other_args: List[str]): for stock in stocks: values[stock] = ns_parser.value * round(1 / len(stocks), 5) if ns_parser.pie: - pie_chart_weights(values, "equal", 0) + pie_chart_weights(values, "Equally Weighted Portfolio") else: display_weights(values) print("") @@ -93,16 +107,15 @@ def equal_weight(stocks: List[str], other_args: List[str]): print("") -def property_weighting(stocks: List[str], property_type: str, other_args: List[str]): - """ - Property weighted portfolio where each weight is the relative fraction. +def property_weighting(stocks: List[str], other_args: List[str]): + """Weighted portfolio where each weight is the relative fraction of a specified info property. + Parameters ---------- 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: - "marketCap", "dividendYield", etc + other_args : List[str] + Command line arguments to be processed with argparse Returns ------- @@ -111,10 +124,32 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s """ parser = argparse.ArgumentParser( add_help=False, - prog="market_cap_weighted", - description="Return portfolio weights/values that are weighted by marketcap", + prog="property", + description="Returns a portfolio that is weighted based on a selected property info", + ) + parser.add_argument( + "-p", + "--property", + required=True, + type=check_valid_property_type, + dest="property", + help="""Property info to weigh. Use one of: + previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield, + payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets, + regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency, averageVolume10days, + expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply, regularMarketDayLow, + priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply, openInterest, marketCap, + volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months, dayLow, ask, ytdReturn, askSize, + volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield, fiftyTwoWeekLow, bid, dividendYield, + bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year, profitMargins, enterpriseToEbitda, + 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth, sharesOutstanding, fundInceptionDate, + annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut, fundFamily, lastFiscalYearEnd, + heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook, + heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue, + threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating, + earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth, + impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice.""", ) - parser.add_argument( "-v", "--value", @@ -130,19 +165,24 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s default=False, help="Display a pie chart for weights", ) - weights = {} - prop = {} - prop_sum = 0 try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-p") + 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 + + weights = {} + prop = {} + prop_sum = 0 for stock in stocks: - stock_prop = yf.Ticker(stock).info[property_type] + stock_prop = yf.Ticker(stock).info[ns_parser.property] if stock_prop is None: stock_prop = 0 prop[stock] = stock_prop @@ -151,7 +191,9 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s weights[k] = round(v / prop_sum, 5) * ns_parser.value if ns_parser.pie: - pie_chart_weights(weights, property_type, 0) + pie_chart_weights( + weights, "Weighted Portfolio based on " + ns_parser.property + ) else: display_weights(weights) print("") @@ -165,20 +207,37 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s 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) + Method to be used on ef object (example: max_sharpe, min_volatility, eff_risk, eff_ret) + Returns ------- weights: dict Dictionary of weights where keys are the tickers. """ - parser = argparse.ArgumentParser(add_help=False, prog=port_type) + d_descriptions = { + "max_sharpe": """Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio, + as it is the portfolio for which the capital market line is tangent to the efficient frontier. + This is a convex optimization problem after making a certain variable substitution. See + Cornuejols and Tutuncu (2006) for more.""", + "min_volatility": "optimizes for minimum volatility", + "max_quadratic_utility": "maximises the quadratic utility, given some risk aversion", + "efficient_risk": """Maximise return for a target risk. The resulting portfolio will have + a volatility less than the target (but not guaranteed to be equal).""", + "efficient_return": "Calculate the 'Markowitz portfolio', minimising volatility for a given target return.", + "max_quadratic_utility": "Maximise the given quadratic utility, i.e: max_w w^T \mu - \frac \delta 2 w^T \Sigma w", + } + + parser = argparse.ArgumentParser( + add_help=False, prog=port_type, description=d_descriptions[port_type] + ) parser.add_argument( "-p", "--period", @@ -187,7 +246,6 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): help="period to get yfinance data from", choices=period_choices, ) - parser.add_argument( "-v", "--value", @@ -204,68 +262,144 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): help="Display a pie chart for weights", ) + if port_type == "max_sharpe": + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-r") + parser.add_argument( + "-r", + "--risk_free_rate", + type=float, + dest="risk_free_rate", + default=0.02, + help="""Risk-free rate of borrowing/lending. The period of the risk-free rate + should correspond to the frequency of expected returns.""", + ) + + elif port_type == "efficient_risk": + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + parser.add_argument( + "-t", + "--target_volatility", + type=float, + dest="target_volatility", + default=0.1, + help="The desired maximum volatility of the resulting portfolio", + ) + parser.add_argument( + "-n", + "--market_neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + + elif port_type == "efficient_return": + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + parser.add_argument( + "-t", + "--target_return", + type=float, + dest="target_return", + default=0.1, + help="the desired return of the resulting portfolio", + ) + parser.add_argument( + "-n", + "--market_neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + + elif port_type == "max_quadratic_utility": + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-r") + parser.add_argument( + "-r", + "--risk_aversion", + type=float, + dest="risk_aversion", + default=1, + help="risk aversion parameter", + ) + parser.add_argument( + "-n", + "--market_neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + try: - ns_parser = parse_from_port_type(parser, port_type, other_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: - return + return None 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": + sp = d_period[ns_parser.period] - ef_sharpe = dict(ef.max_sharpe()) - weights = { - key: ns_parser.value * round(value, 5) - for key, value in ef_sharpe.items() - } - val = 0 - print("Weights that maximize Sharpe Ratio:") + if port_type == "max_sharpe": + ef_opt = dict(ef.max_sharpe(ns_parser.risk_free_rate)) + s_title = f"{sp} Weights that maximize Sharpe ration with risk free level of {ns_parser.risk_free_rate}" elif port_type == "min_volatility": + ef_opt = dict(ef.min_volatility()) + s_title = f"{sp} Weights that minimize volatility" - ef_min_vol = dict(ef.min_volatility()) - weights = { - key: ns_parser.value * round(value, 5) - for key, value in ef_min_vol.items() - } - val = 0 - print("Weights that minimize volatility") + elif port_type == "efficient_risk": + ef_opt = dict( + ef.efficient_risk(ns_parser.target_volatility, ns_parser.market_neutral) + ) + s_title = f"{sp} Weights that maximize return with a maximum volatility of {ns_parser.target_volatility}" - elif port_type == "eff_risk": + elif port_type == "efficient_return": + ef_opt = dict( + ef.efficient_return(ns_parser.target_return, ns_parser.market_neutral) + ) + s_title = f"{sp} Weights that minimise volatility for a given target return of {ns_parser.target_return}" - 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() - } - val = ns_parser.risk_level - print(f"Weights for maximizing returns at risk = {100*val:.1f} %") - - 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() - } - val = ns_parser.target_return - print(f"Weights for minimizing risk at target return = {100*val:.1f} %") + elif port_type == "max_quadratic_utility": + ef_opt = dict( + ef.max_quadratic_utility( + ns_parser.risk_aversion, ns_parser.market_neutral + ) + ) + s_title = f"{sp} Weights that maximise the quadratic utility with risk aversion of {ns_parser.risk_aversion}" else: raise ValueError("EF Method not found") - if ns_parser.pie: - pie_chart_weights(weights, port_type, val) + weights = { + key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() + } + + if ns_parser.pie: + pie_chart_weights(weights, s_title) + else: + print(s_title) + display_weights(weights) + print("") - print("") ef.portfolio_performance(verbose=True) print("") - display_weights(weights) - print("") except Exception as e: print(e) diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index d4d2960adee..24a93bcdd5e 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -21,12 +21,12 @@ class PortfolioOptimization: "select", "add", "equal", - "mktcap", - "dividend", + "property", "maxsharpe", "minvol", - "maxret", - "minrisk", + "effret", + "effrisk", + "maxquadutil", "ef", "yolo", ] @@ -57,12 +57,20 @@ class PortfolioOptimization: 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( + " property weight according to selected info property (e.g. marketCap)" + ) + print("") + print( + " maxsharpe optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)" + ) + print(" minvol optimizes for minimum volatility") + print( + " maxquadutil maximises the quadratic utility, given some risk aversion" + ) + print(" effret maximises return for a given target risk") + print(" effrisk minimises risk for a given target return") + print("") print(" ef show the efficient frontier") print("") @@ -107,13 +115,9 @@ class PortfolioOptimization: """Process equal command""" optimizer_view.equal_weight(self.tickers, other_args) - def call_mktcap(self, other_args: List[str]): - """Process mktcap command""" - optimizer_view.property_weighting(self.tickers, "marketCap", other_args) - - def call_dividend(self, other_args: List[str]): - """Process dividend command""" - optimizer_view.property_weighting(self.tickers, "dividendYield", other_args) + def call_property(self, other_args: List[str]): + """Process property command""" + optimizer_view.property_weighting(self.tickers, other_args) def call_maxsharpe(self, other_args: List[str]): """Process maxsharpe command""" @@ -123,13 +127,17 @@ class PortfolioOptimization: """Process minvol command""" optimizer_view.ef_portfolio(self.tickers, "min_volatility", 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_maxquadutil(self, other_args: List[str]): + """Process maxquadutil command""" + optimizer_view.ef_portfolio(self.tickers, "max_quadratic_utility", 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_effrisk(self, other_args: List[str]): + """Process effrisk command""" + optimizer_view.ef_portfolio(self.tickers, "efficient_risk", other_args) + + def call_effret(self, other_args: List[str]): + """Process effret command""" + optimizer_view.ef_portfolio(self.tickers, "efficient_return", other_args) def call_ef(self, other_args): """Process ef command""" @@ -158,7 +166,6 @@ class PortfolioOptimization: help="add tickers to optimzation.", ) try: - if other_args: if "-" not in other_args[0]: other_args.insert(0, "-t") From b19f0740b57bedd1eb9e172392ef95194910ee8c Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 00:59:42 +0100 Subject: [PATCH 03/20] more refactoring and stuff --- .../comparison_analysis/ca_controller.py | 2 +- gamestonk_terminal/screener/finviz_view.py | 8 ++++-- .../screener/screener_controller.py | 28 +++++++++++-------- .../screener/yahoo_finance_view.py | 12 ++++++-- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index f999ed092af..e0a339202c2 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -320,7 +320,7 @@ class ComparisonAnalysisController: finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar) def call_po(self, _): - """Open Portfolio Optimization menu with ticker and similar""" + """Call the portfolio optimization menu with selected tickers""" return po_controller.menu([self.ticker] + self.similar) diff --git a/gamestonk_terminal/screener/finviz_view.py b/gamestonk_terminal/screener/finviz_view.py index 4ca3bb96857..3c331be9649 100644 --- a/gamestonk_terminal/screener/finviz_view.py +++ b/gamestonk_terminal/screener/finviz_view.py @@ -214,12 +214,16 @@ def screener(other_args: List[str], loaded_preset: str, data_type: str): if isinstance(df_screen, pd.DataFrame): print(df_screen.to_string()) - print("") + print("") + return list(df_screen["Ticker"].values) + else: + print("") + return [] except Exception as e: print(e) print("") - return + return [] def view_signals(other_args: List[str]): diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py index 0fe364750b0..7edc438c501 100644 --- a/gamestonk_terminal/screener/screener_controller.py +++ b/gamestonk_terminal/screener/screener_controller.py @@ -41,6 +41,7 @@ class ScreenerController: def __init__(self): """Constructor""" self.preset = "template" + self.screen_tickers = [] self.scr_parser = argparse.ArgumentParser(add_help=False, prog="scr") self.scr_parser.add_argument( "cmd", @@ -71,8 +72,11 @@ class ScreenerController: print("") print(" signals view filter signals (e.g. -s top_gainers)") print("") - print(" > po go to the portfolio optimization menu") - print("") + if self.screen_tickers: + print(f"Last screened tickers: {', '.join(self.screen_tickers)}") + print("") + print(" > po portfolio optimization for last screened tickers") + print("") @staticmethod def view_available_presets(other_args: List[str]): @@ -191,39 +195,41 @@ class ScreenerController: def call_historical(self, other_args: List[str]): """Process historical command""" - yahoo_finance_view.historical(other_args, self.preset) + self.screen_tickers = yahoo_finance_view.historical(other_args, self.preset) def call_overview(self, other_args: List[str]): """Process overview command""" - finviz_view.screener(other_args, self.preset, "overview") + self.screen_tickers = finviz_view.screener(other_args, self.preset, "overview") def call_valuation(self, other_args: List[str]): """Process valuation command""" - finviz_view.screener(other_args, self.preset, "valuation") + self.screen_tickers = finviz_view.screener(other_args, self.preset, "valuation") def call_financial(self, other_args: List[str]): """Process financial command""" - finviz_view.screener(other_args, self.preset, "financial") + self.screen_tickers = finviz_view.screener(other_args, self.preset, "financial") def call_ownership(self, other_args: List[str]): """Process ownership command""" - finviz_view.screener(other_args, self.preset, "ownership") + self.screen_tickers = finviz_view.screener(other_args, self.preset, "ownership") def call_performance(self, other_args: List[str]): """Process performance command""" - finviz_view.screener(other_args, self.preset, "performance") + self.screen_tickers = finviz_view.screener( + other_args, self.preset, "performance" + ) def call_technical(self, other_args: List[str]): """Process technical command""" - finviz_view.screener(other_args, self.preset, "technical") + self.screen_tickers = finviz_view.screener(other_args, self.preset, "technical") def call_signals(self, other_args: List[str]): """Process signals command""" finviz_view.view_signals(other_args) def call_po(self, other_args: List[str]): - """Call the portfolio optimization menu""" - return po_controller.menu(other_args) + """Call the portfolio optimization menu with selected tickers""" + return po_controller.menu(self.screen_tickers) def menu(): diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py index 6ad1c6e93bb..2a05ddbeec1 100644 --- a/gamestonk_terminal/screener/yahoo_finance_view.py +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -1,5 +1,6 @@ import argparse from typing import List +import random from pandas.plotting import register_matplotlib_converters import matplotlib.pyplot as plt import datetime @@ -119,10 +120,12 @@ def historical(other_args: List[str], preset_loaded: str): if len(l_stocks) > 10: print( - "We limit stocks shown to 10, as after this the plot becomes too noisy. " - "Also, we ran out of different colors." + "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed.", + "\nThe selected list will be:", ) - l_stocks = l_stocks[:10] + random.shuffle(l_stocks) + l_stocks = sorted(l_stocks[:10]) + print(", ".join(l_stocks)) while l_stocks: l_parsed_stocks = list() @@ -179,8 +182,11 @@ def historical(other_args: List[str], preset_loaded: str): plt.show() print("") + return l_parsed_stocks except SystemExit: print("Similar companies need to be provided", "\n") + return [] except Exception as e: print(e, "\n") + return [] From 63e75010666e21dfc1523046f3a10e5cab3059d3 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 01:07:13 +0100 Subject: [PATCH 04/20] Update README.md --- gamestonk_terminal/README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 2b6d79c7b2e..e616a2ae873 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -9,6 +9,7 @@ * [Due Diligence](#Due-Diligence-) * [Prediction Techniques](#Prediction-Techniques-) * [Portfolio Analysis](#Portfolio-Analysis-) +* [Portfolio Optimization](#Portfolio-Optimization-) * [Cryptocurrencies](#Cryptocurrencies-) * [Comparison Analysis](#Comparison-Analysis-) * [Exploratory Data Analysis](#Exploratory-Data-Analysis-) @@ -242,16 +243,19 @@ Command|Description|Brokers   ## Portfolio Optimization [»](portfolio_optimization/README.md) -command|desciription + +Command|Description ------|------ -`equal_weight`|Returns equal weights for all selected stocks -`mkt_cap`| Returns weights that are weighted by market cap -`div_yield`|Returns weights that are weighted by dividend yield -`max_sharpe`| Returns portfolio of Efficient Frontier with maximum sharpe ratio -`min_vol`| Returns portfolio on Efficient Frontier with minimum volatility -`eff_risk`| Returns portfolio on Efficient Frontier that maximizes returns at a given risk -`eff_ret`| Returns portfolio on Efficient Frontier that minimizes risk at a given return level -`show_eff`|Plots random portfolios and shows the Efficient Frontier +`add`| add ticker to optimize +`select`| overwrite current tickers with new tickers +`equal`| equally weighted +`property`| weight according to selected info property (e.g. marketCap) +`maxsharpe`| optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio) +`minvol`| optimizes for minimum volatility +`maxquadutil`| maximises the quadratic utility, given some risk aversion +`effret`| maximises return for a given target risk +`effrisk`| minimises risk for a given target return +`ef`| show the efficient frontier   @@ -331,7 +335,9 @@ Command|Description Command|Description|Source ------ | --------|---- `exp` | see/set expiry date -`volume` | plot options trading volume / open interest |[Yahoo Finance](https://finance.yahoo.com/) +`volume` | volume + open interest options trading plot |[Yahoo Finance](https://finance.yahoo.com/) +`vcalls` | calls volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/) +`vputs` | puts volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/)   @@ -351,4 +357,4 @@ technical |technical (e.g. Beta, SMA50, 52W Low, RSI, Change) signals |view filter signals (e.g. -s top_gainers) -  \ No newline at end of file +  From 1c04f02da4b0e295fc4d7483eb884d72c0c896ee Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 01:10:56 +0100 Subject: [PATCH 05/20] fix linting --- gamestonk_terminal/portfolio_optimization/optimizer_helper.py | 2 +- gamestonk_terminal/portfolio_optimization/optimizer_view.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py index 19f9057bd76..f88ad80463c 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py @@ -13,7 +13,7 @@ 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, parse_known_args_and_warn +from gamestonk_terminal.helper_funcs import plot_autoscale l_valid_property_infos = [ "previousClose", diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index 4f7170e1fd8..92ee5417fb9 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -232,7 +232,6 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): "efficient_risk": """Maximise return for a target risk. The resulting portfolio will have a volatility less than the target (but not guaranteed to be equal).""", "efficient_return": "Calculate the 'Markowitz portfolio', minimising volatility for a given target return.", - "max_quadratic_utility": "Maximise the given quadratic utility, i.e: max_w w^T \mu - \frac \delta 2 w^T \Sigma w", } parser = argparse.ArgumentParser( From 446de338b05113ac401900d979f39a8816dfacf4 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 01:31:25 +0100 Subject: [PATCH 06/20] Update README.md --- .../portfolio_optimization/README.md | 95 +++++++++++++------ 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index bf96cf77629..c79fd3c9023 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -1,35 +1,76 @@ # Portfolio Optimization -This menu aims to discover optimal portfolios for selected stocks. +This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of the following commands along with an example will be exploited below. -Currently we support two methods: +* [add](#add) + * add ticker to optimize +* [select](#select) + * overwrite current tickers with new tickers +* [equal](#equal) + * equally weighted +* [property](#property) + * weight according to selected info property (e.g. marketCap) +* [maxsharpe](#maxsharpe) + * optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio) +* [minvol](#minvol) + * optimizes for minimum volatility +* [maxquadutil](#maxquadutil) + * maximises the quadratic utility, given some risk aversion +* [effret](#effret) + * maximises return for a given target risk +* [effrisk](#effrisk) + * minimises risk for a given target return +* [ef](#ef) + * show the efficient frontier -* [Property Weighting](#weighting) - * Equal Weights - * Market Cap Weighting - * Divident Yield Weighting -* [Mean-Variance-Optimization](#eff_front) - * Max sharpe ratio - * Minimum volatility - * Maximum returns at given risk level - * Minimum risk level at a given return -## Procedure -There are three ways to load stocks to be analyzed. -* add -* select -* from ca menu -###add -Adds selected tickers to the menu to be considered +## add + +```text +add [-t ADD_TICKERS] +``` + +Add/Select tickers for portfolio to be optimized. + +* -t : tickers to be used in the portfolio to optimize +* + +## select + +```text +select [-t ADD_TICKERS] +``` + +Add/Select tickers for portfolio to be optimized. + +* -t : tickers to be used in the portfolio to optimize + + +## equal + +```text +equal [-v VALUE] [--pie] +``` + +Returns an equally weighted portfolio + +* -v : Amount to allocate to portfolio. +* --pie : Display a pie chart for weights + + +## property + +```text +property [-p PROPERTY] [-v VALUE] [--pie] +``` + +Returns a portfolio that is weighted based on a selected property info + +* -p : Property info to weigh. Use one of: previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield, payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets, regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency, averageVolume10days, expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply, regularMarketDayLow, priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply, openInterest, marketCap, volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months, dayLow, ask, ytdReturn, askSize, volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield, fiftyTwoWeekLow, bid, dividendYield, bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year, profitMargins, enterpriseToEbitda, 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth, sharesOutstanding, fundInceptionDate, annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut, fundFamily, lastFiscalYearEnd, heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook, heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue, threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating, earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth, impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice. +* -v : Amount to allocate to portfolio. +* --pie : Display a pie chart for weights + -```` -usage: add ticker1,ticker2,ticker3,... -```` -###select -Clears current list and loads selected ticker -```` -usage: select ticker1,ticker2,ticker3,... -```` ### ca menu From the ca menu, the loaded ticker and the selected similar tickers can be loaded by entering the `> po` menu. @@ -166,4 +207,4 @@ And the pie chart: ![yummypie](https://user-images.githubusercontent.com/18151143/114740289-b9167f00-9d17-11eb-9c29-470785b21d09.png) -Note that since `AAPL` had zero allocation, it was omitted from the chart. \ No newline at end of file +Note that since `AAPL` had zero allocation, it was omitted from the chart. From a21195438b4370578b688d7774706e9587801504 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 02:01:17 +0100 Subject: [PATCH 07/20] Update README.md --- .../portfolio_optimization/README.md | 202 +++++++++--------- 1 file changed, 97 insertions(+), 105 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index c79fd3c9023..2825bf9311b 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -10,6 +10,9 @@ This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of th * equally weighted * [property](#property) * weight according to selected info property (e.g. marketCap) + +[Mean Variance Optimization](#Mean_Variance_Optimization) + * [maxsharpe](#maxsharpe) * optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio) * [minvol](#minvol) @@ -24,7 +27,7 @@ This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of th * show the efficient frontier -## add +### add ```text add [-t ADD_TICKERS] @@ -32,10 +35,10 @@ add [-t ADD_TICKERS] Add/Select tickers for portfolio to be optimized. -* -t : tickers to be used in the portfolio to optimize +* -t : Tickers to be used in the portfolio to optimize * -## select +### select ```text select [-t ADD_TICKERS] @@ -43,10 +46,10 @@ select [-t ADD_TICKERS] Add/Select tickers for portfolio to be optimized. -* -t : tickers to be used in the portfolio to optimize +* -t : Tickers to be used in the portfolio to optimize -## equal +### equal ```text equal [-v VALUE] [--pie] @@ -54,11 +57,11 @@ equal [-v VALUE] [--pie] Returns an equally weighted portfolio -* -v : Amount to allocate to portfolio. -* --pie : Display a pie chart for weights +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights -## property +### property ```text property [-p PROPERTY] [-v VALUE] [--pie] @@ -67,122 +70,111 @@ property [-p PROPERTY] [-v VALUE] [--pie] Returns a portfolio that is weighted based on a selected property info * -p : Property info to weigh. Use one of: previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield, payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets, regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency, averageVolume10days, expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply, regularMarketDayLow, priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply, openInterest, marketCap, volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months, dayLow, ask, ytdReturn, askSize, volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield, fiftyTwoWeekLow, bid, dividendYield, bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year, profitMargins, enterpriseToEbitda, 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth, sharesOutstanding, fundInceptionDate, annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut, fundFamily, lastFiscalYearEnd, heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook, heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue, threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating, earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth, impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice. -* -v : Amount to allocate to portfolio. -* --pie : Display a pie chart for weights +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights - -### ca menu -From the ca menu, the loaded ticker and the selected similar tickers can be loaded by entering the `> po` menu. - -From this `po` menu, you can return to `ca` using `ca`, which will keep your loaded stock, but reset the selected stocks from that menu. - -Once your stocks are listed, you can select one of the options. -## Property weighted -* equal weights -* market cap weighted -* dividend yield weighted -### equal weights -Returns an equally weighted portfolio where the weights are 1/number of stocks. -```` -equal_weight [-v --value VALUE] [--pie] -```` -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. - -### market cap weighted -Returns portfolio values that are weighted by their relative Market Cap. -```` -mkt_cap [-v --value VALUE] [--pie] -```` -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. - -###dividend yield weighted -Returns portfolio values that are weighted by relative Dividend Yield. -```` -div_yield [-v --value VALUE] [--pie] -```` -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. - -## Mean Variance Optimization +## Mean Variance Optimization These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem. - +Minimize: -With constraints: +Subject to: , and - +* Where S is the covariance matrix between stocks and R is the expected returns. +* The condition that all weights add up to 1 just implies that you want to have a net long portfolio (with no margin). +* A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy. -\ - +Currently, we do not allow for changing risk models or adding constraints. +If there is something specific, please submit a feature request, or if you can write it, feel free to add a PR! +All of these commands use [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package. -Where S is the covariance matrix between stocks and R is the expected returns. The condition that all weights add up to 1 -just implies that you want to have a net long portfolio (with no margin). -A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy. +### maxsharpe -Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can -write it, feel free to add a PR! - -All of our current implementations use the [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package. - -### max_sharpe -The sharpe ratio is defined as - -(Mean Returns - Risk Free Rate)/(Standard Deviation of Returns) - -The usage is: ```` -max_sharpe [-p PERIOD] [-v --value VALUE] [--pie] +usage: max_sharpe [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-r RISK_FREE_RATE] +```` + +Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio, as it is the portfolio for which the capital market line is tangent to the efficient frontier. This is a convex optimization problem after making a certain variable substitution. See Cornuejols and Tutuncu (2006) for more. The sharpe ratio is defined as (Mean Returns - Risk Free Rate)/(Standard Deviation of Returns). + +* -p : Amount of time to retrieve data from yfinance. Default: 3mo. +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights + + +### minvol + +```` +usage: minvol [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] ```` -* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. -### min_vol This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier. -The usage is: -```` -min_vol [-p PERIOD] [-v --value VALUE] [--pie] -```` -* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. -### eff_risk -This portfolio maximizes the returns at a given risk tolerance -The usage is: -```` -eff_risk [-p PERIOD] [-r --risk RISK_LEVEL] [-v --value VALUE] [--pie] -```` -* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -* -r/--risk Risk tolerance. Default is 0.1 (10%) -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. +* -p : Amount of time to retrieve data from yfinance. Default: 3mo. +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights -### eff_ret -This portfolio minimizes the risk at a given return level -The usage is: -```` -eff_ret [-p PERIOD] [-r --return] [-v --value VALUE] [--pie] -```` -* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -* -r/--return. Desired return. Default is 0.1 (10%) -* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. -* --pie Flag that displays a pie chart of the allocations. -### show_eff +### maxquadutil + +```` +usage: maxquadutil [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-r RISK_AVERSION] [-n] +```` + +Maximises the quadratic utility, given some risk aversion. + +* -p : Amount of time to retrieve data from yfinance. Default: 3mo. +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights +* -r : Risk aversion parameter +* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. + + +### effret + +```` +usage: effret [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-t TARGET_RETURN] [-n] +```` + +Calculate the 'Markowitz portfolio', minimising volatility for a given target return. + +* -p : Amount of time to retrieve data from yfinance. Default: 3mo. +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights +* -t : The desired return of the resulting portfolio +* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. + + +### effrisk + +```` +usage: effrisk [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-t TARGET_VOLATILITY] [-n] +```` + +Maximise return for a target risk. The resulting portfolio will have a volatility less than the target (but not guaranteed to be equal). + +* -p : Amount of time to retrieve data from yfinance. Default: 3mo. +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights +* -t : The desired maximum volatility of the resultingportfolio +* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. + + +### ef + +```` +usage: ef [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-n N_PORT] +```` + This function plots random portfolios based on their risk and returns and shows the efficient frontier. -The usage is: -```` -show_eff [-p PERIOD] [-n N_PORTFOLIOS] -```` -* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -* -n Number of portfolios to simulate. + +* -n : Number of portfolios to simulate. Default 300. +* -p : Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. -##Sample Usage + + +## Sample Usage In this example, we generate weights for a list of 6 stocks using the eff_ret command. This optimization looks to maximize returns at a given risk level. We start by adding the stocks we want to analyze: ```` From 1878d997869a2209587386707ceb40472d4042c2 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 02:24:53 +0100 Subject: [PATCH 08/20] Add nice plots and stuff --- .../portfolio_optimization/README.md | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index 2825bf9311b..d3591364c21 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -60,6 +60,8 @@ Returns an equally weighted portfolio * -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. * --pie : Display a pie chart for weights +![equal](https://user-images.githubusercontent.com/25267873/114958292-9771e080-9e5a-11eb-9de6-1c2dfbe1e27b.png) + ### property @@ -73,6 +75,8 @@ Returns a portfolio that is weighted based on a selected property info * -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. * --pie : Display a pie chart for weights +![marketCap](https://user-images.githubusercontent.com/25267873/114958294-980a7700-9e5a-11eb-9db4-d06e6a1e8981.png) + ## Mean Variance Optimization @@ -90,6 +94,7 @@ Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can write it, feel free to add a PR! All of these commands use [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package. + ### maxsharpe ```` @@ -102,6 +107,8 @@ Maximise the Sharpe Ratio. The result is also referred to as the tangency portfo * -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. * --pie : Display a pie chart for weights +maxsharpe + ### minvol @@ -115,6 +122,9 @@ This portfolio minimizes the total volatility, which also means it has the small * -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. * --pie : Display a pie chart for weights +![min_vol](https://user-images.githubusercontent.com/25267873/114958290-96d94a00-9e5a-11eb-9b7c-deba8a8e7249.png) +min_vol2 + ### maxquadutil @@ -130,6 +140,8 @@ Maximises the quadratic utility, given some risk aversion. * -r : Risk aversion parameter * -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. +maxquadutil + ### effret @@ -145,6 +157,8 @@ Calculate the 'Markowitz portfolio', minimising volatility for a given target re * -t : The desired return of the resulting portfolio * -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. +effret + ### effrisk @@ -160,6 +174,9 @@ Maximise return for a target risk. The resulting portfolio will have a volatilit * -t : The desired maximum volatility of the resultingportfolio * -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False. +![effrisk](https://user-images.githubusercontent.com/25267873/114958284-95a81d00-9e5a-11eb-8626-988505db8890.png) +effrisk2 + ### ef @@ -172,31 +189,4 @@ This function plots random portfolios based on their risk and returns and shows * -n : Number of portfolios to simulate. Default 300. * -p : Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. - - -## Sample Usage -In this example, we generate weights for a list of 6 stocks using the eff_ret command. This optimization looks to maximize returns -at a given risk level. We start by adding the stocks we want to analyze: -```` -select aapl,amzn,msft,f,gm,ge -```` -Which shows: -```` -Current Tickers: GE, GM, AMZN, AAPL, F, MSFT -```` -To perform the optimization, we will set a target return of 25% (.25). Given how stocks performed during COVID, -there is a lot of volatility, so many optimizations may not get low volatility. The module also returns annualized volatility, -so the number is your portfolio volatility * `sqrt(252`. This optimization will be (including a pie chart!). Note we could also supply a different -time period, which changes the expected returns and historical volatility, which changes the optimization. We could also specify a dollar -amount that you wish to allocate using the `-v` flag. -```` -eff_ret -r .25 --pie -```` -The console will show (numbers will vary based on when this is done). -![console](https://user-images.githubusercontent.com/18151143/114740311-bd429c80-9d17-11eb-90e2-97430781431a.png) - -And the pie chart: - -![yummypie](https://user-images.githubusercontent.com/18151143/114740289-b9167f00-9d17-11eb-9c29-470785b21d09.png) - -Note that since `AAPL` had zero allocation, it was omitted from the chart. +![ef](https://user-images.githubusercontent.com/25267873/114958281-93de5980-9e5a-11eb-967b-d37ddd4f3e73.png) From 64bdc1da0cd1180784b5062a069b2a6c98ac9ca8 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 02:26:33 +0100 Subject: [PATCH 09/20] more fixes here and there --- .../portfolio_optimization/optimizer_view.py | 45 +++++++++++++------ .../portfolio_optimization/po_controller.py | 9 ++-- .../technical_analysis/README.md | 2 +- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index 92ee5417fb9..af081ca4708 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -10,7 +10,11 @@ from pypfopt import plotting 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.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, + check_non_negative, +) from gamestonk_terminal.portfolio_optimization.optimizer_helper import ( process_stocks, prepare_efficient_frontier, @@ -130,7 +134,7 @@ def property_weighting(stocks: List[str], other_args: List[str]): parser.add_argument( "-p", "--property", - required=True, + required=True if "-h" not in other_args else False, type=check_valid_property_type, dest="property", help="""Property info to weigh. Use one of: @@ -267,7 +271,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): other_args.insert(0, "-r") parser.add_argument( "-r", - "--risk_free_rate", + "--risk-free-rate", type=float, dest="risk_free_rate", default=0.02, @@ -281,7 +285,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): other_args.insert(0, "-t") parser.add_argument( "-t", - "--target_volatility", + "--target-volatility", type=float, dest="target_volatility", default=0.1, @@ -289,7 +293,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): ) parser.add_argument( "-n", - "--market_neutral", + "--market-neutral", action="store_true", default=False, dest="market_neutral", @@ -303,7 +307,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): other_args.insert(0, "-t") parser.add_argument( "-t", - "--target_return", + "--target-return", type=float, dest="target_return", default=0.1, @@ -311,7 +315,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): ) parser.add_argument( "-n", - "--market_neutral", + "--market-neutral", action="store_true", default=False, dest="market_neutral", @@ -325,7 +329,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): other_args.insert(0, "-r") parser.add_argument( "-r", - "--risk_aversion", + "--risk-aversion", type=float, dest="risk_aversion", default=1, @@ -333,7 +337,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): ) parser.add_argument( "-n", - "--market_neutral", + "--market-neutral", action="store_true", default=False, dest="market_neutral", @@ -357,7 +361,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): if port_type == "max_sharpe": ef_opt = dict(ef.max_sharpe(ns_parser.risk_free_rate)) - s_title = f"{sp} Weights that maximize Sharpe ration with risk free level of {ns_parser.risk_free_rate}" + s_title = f"{sp} Weights that maximize Sharpe ratio with risk free level of {ns_parser.risk_free_rate}" elif port_type == "min_volatility": ef_opt = dict(ef.min_volatility()) @@ -407,7 +411,12 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): def show_ef(stocks: List[str], other_args: List[str]): - parser = argparse.ArgumentParser(add_help=False, prog="ef") + parser = argparse.ArgumentParser( + add_help=False, + prog="ef", + description="""This function plots random portfolios based + on their risk and returns and shows the efficient frontier.""", + ) parser.add_argument( "-p", @@ -418,15 +427,23 @@ def show_ef(stocks: List[str], other_args: List[str]): choices=period_choices, ) parser.add_argument( - "-n", default=300, dest="n_port", help="number of portfolios to simulate" + "-n", + "--number-portfolios", + default=300, + type=check_non_negative, + dest="n_port", + help="number of portfolios to simulate", ) try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") 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.") + print("Please have at least 2 loaded tickers to calculate weights.\n") return stock_prices = process_stocks(stocks, ns_parser.period) @@ -449,7 +466,7 @@ def show_ef(stocks: List[str], other_args: List[str]): 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.set_title(f"Efficient Frontier simulating {ns_parser.n_port} portfolios") ax.legend() plt.tight_layout() plt.grid(b=True, which="major", color="#666666", linestyle="-") diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 24a93bcdd5e..a7ef204f128 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -61,6 +61,7 @@ class PortfolioOptimization: " property weight according to selected info property (e.g. marketCap)" ) print("") + print("Mean Variance Optimization:") print( " maxsharpe optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)" ) @@ -151,11 +152,11 @@ class PortfolioOptimization: @staticmethod def add_stocks(self, other_args: List[str]): - """ Add ticker to current list for optimization""" + """ Add ticker or Select tickes for portfolio to be optimized """ parser = argparse.ArgumentParser( add_help=False, - prog="add", - description="""Add tickers for optimizing.""", + prog="add/select", + description="""Add/Select tickers for portfolio to be optimized.""", ) parser.add_argument( "-t", @@ -163,7 +164,7 @@ class PortfolioOptimization: dest="add_tickers", type=lambda s: [str(item).upper() for item in s.split(",")], default=[], - help="add tickers to optimzation.", + help="tickers to be used in the portfolio to optimize.", ) try: if other_args: diff --git a/gamestonk_terminal/technical_analysis/README.md b/gamestonk_terminal/technical_analysis/README.md index c35ebb6bdaa..0e5c511d850 100644 --- a/gamestonk_terminal/technical_analysis/README.md +++ b/gamestonk_terminal/technical_analysis/README.md @@ -43,7 +43,7 @@ This menu aims to perform a technical analysis on a pre-loaded ticker chart, and * [obv](#obv) - on balance volume -**S/O to https://github.com/twopirllc/pandas-ta** _Owing to this library, it is fairly easy to add other technical indicators. So, let me know if there's any that you would like. Personally I use mostly these ones, hence why I didn't add more._ +**S/O to https://github.com/twopirllc/pandas-ta** _Owing to this library, it is fairly easy to add other technical indicators. So, let us know if there's any other that you would like._ ## view ``` From 31959452c7a43d681d9af4264e73d48510c8d08a Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 02:29:29 +0100 Subject: [PATCH 10/20] Update README.md --- gamestonk_terminal/comparison_analysis/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gamestonk_terminal/comparison_analysis/README.md b/gamestonk_terminal/comparison_analysis/README.md index f00b902183d..c60dfd305d0 100644 --- a/gamestonk_terminal/comparison_analysis/README.md +++ b/gamestonk_terminal/comparison_analysis/README.md @@ -32,6 +32,8 @@ This menu aims to compare a pre-loaded stock with similar companies, and the usa * brief performance (e.g. Perf Week, Perf YTD, Volatility M) [Finviz] * [technical](#technical) * brief technical (e.g. Beta, SMA50, 52W Low, RSI, Change) [Finviz] +* [> po](portfolio_optimization/README.md) + * portfolio optimization for selected tickers ## get From 7b94769b793b486f0b81b3eaaa21587467ec5c09 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 02:30:15 +0100 Subject: [PATCH 11/20] Update README.md --- gamestonk_terminal/screener/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gamestonk_terminal/screener/README.md b/gamestonk_terminal/screener/README.md index 6271a7684db..3135fb3f981 100644 --- a/gamestonk_terminal/screener/README.md +++ b/gamestonk_terminal/screener/README.md @@ -23,8 +23,8 @@ This menu aims to filter stocks based on pre-specified preset filters, and the u * contains [Oversold (-s) signal example](#signal-oversold) * [signals](#signals) * view filter signals (e.g. -s top_gainers) [Finviz] -* [po](#port_opt) - * go to the portfolio optimization menu +* [> po](portfolio_optimization/README.md) + * portfolio optimization for last screened tickers ## view @@ -190,4 +190,4 @@ usage: po ticker1 ticker2 ... ```` example: po aapl msft tsla gme -```` \ No newline at end of file +```` From 5d9ee5c8974fe4a0f10d3114a8cdab9e636c7c71 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 02:42:43 +0100 Subject: [PATCH 12/20] Update ROADMAP.md --- ROADMAP.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f5c466f3a9a..073316e0213 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,6 +14,7 @@ * [Exploratory Data Analysis](#Exploratory-Data-Analysis) * [Residual Analysis](#Residual-Analysis) * [Portfolio Analysis](#Portfolio-Analysis) +* [Portfolio Optimization](#Portfolio-Optimization) * [FRED](#FRED) * [Options](#Options) * [Credit Analysis](#Credit-Analysis) @@ -110,6 +111,7 @@ * [x] Add multi-ticker sentiment comparison (@didier) - [PR #250](https://github.com/DidierRLopes/GamestonkTerminal/pull/250) * [x] Get similar companies from Finviz based on Industry and Sector (and possibly country) from Finviz (@didier) - [PR #323](https://github.com/DidierRLopes/GamestonkTerminal/pull/323) * [x] Output brief screen (overview, performance, financial, valuation) across similar companies (@didier) - [PR #323](https://github.com/DidierRLopes/GamestonkTerminal/pull/323) +* [x] Add command to take tickers to portfolio optimization (@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329) **NEXT** @@ -142,13 +144,17 @@ * [ ] Add Brokers (td, webull, etc) (@jmaslek) * [ ] Refactoring * [ ] Summaries / tear sheets (@jmaslek) + ___ -##Portfolio Optimization + +## Portfolio Optimization * [x] Basic Optimization through PyPortFolioOpt(@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329) +* [x] Add command to maximise the quadratic utility(@didier) - [PR #349](https://github.com/DidierRLopes/GamestonkTerminal/pull/349) **NEXT** -* [ ] Decide what features should be present * [ ] Allow for more custom optimization constrains + + --- ## FRED @@ -189,6 +195,7 @@ ___ * [x] Add README with explanation of how presets are stored and can be added by experienced users. (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314) * [x] Add screener signals (e.g. top gainers, new highs, most volatile, oversold, major news, ...) from Finviz (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314) * [x] Plot screener historical using Yahoo Finance data (@didier) - [PR #319](https://github.com/DidierRLopes/GamestonkTerminal/pull/319) +* [x] Add command to take tickers to portfolio optimization (@didier) - [PR #349](https://github.com/DidierRLopes/GamestonkTerminal/pull/349) **NEXT** From 886e1830c0e4a163677cc4df34521f46d90f30a7 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 02:58:12 +0100 Subject: [PATCH 13/20] im a picky guy, sorry --- gamestonk_terminal/comparison_analysis/ca_controller.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index e0a339202c2..499b6c9cd39 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -84,8 +84,6 @@ class ComparisonAnalysisController: if self.similar: print(f"[{self.user}] Similar Companies: {', '.join(self.similar)}") - # else: - # print(f"No similar companies [{self.user}]") print("\nComparison Analysis Mode:") print(" help show this comparison analysis menu again") @@ -110,8 +108,9 @@ class ComparisonAnalysisController: print(" performance brief performance comparison") print(" technical brief technical comparison") print("") - print(" > po portfolio optimization for selected tickers") - print("") + if self.similar: + print(" > po portfolio optimization for selected tickers") + print("") return @staticmethod From 72a2337bbbce32ecd34ec973686835b0a8ea09e6 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 21:51:46 +0100 Subject: [PATCH 14/20] Refactor commands because OCD --- .../portfolio_optimization/optimizer_view.py | 556 ++++++++++++++---- .../portfolio_optimization/po_controller.py | 16 +- 2 files changed, 439 insertions(+), 133 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index af081ca4708..f54ccdc3ea3 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -94,9 +94,11 @@ 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 = {} for stock in stocks: values[stock] = ns_parser.value * round(1 / len(stocks), 5) @@ -208,38 +210,22 @@ def property_weighting(stocks: List[str], other_args: List[str]): return -def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): +def max_sharpe(stocks: List[str], other_args: List[str]): """ - Return a portfolio based on condition in port_type Defaults to 3m of historical data + Return a portfolio that maximises the Sharpe Ratio 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, eff_risk, eff_ret) Returns ------- weights: dict Dictionary of weights where keys are the tickers. """ - - d_descriptions = { - "max_sharpe": """Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio, - as it is the portfolio for which the capital market line is tangent to the efficient frontier. - - This is a convex optimization problem after making a certain variable substitution. See - Cornuejols and Tutuncu (2006) for more.""", - "min_volatility": "optimizes for minimum volatility", - "max_quadratic_utility": "maximises the quadratic utility, given some risk aversion", - "efficient_risk": """Maximise return for a target risk. The resulting portfolio will have - a volatility less than the target (but not guaranteed to be equal).""", - "efficient_return": "Calculate the 'Markowitz portfolio', minimising volatility for a given target return.", - } - parser = argparse.ArgumentParser( - add_help=False, prog=port_type, description=d_descriptions[port_type] + add_help=False, prog="maxsharpe", description="Maximise the Sharpe Ratio" ) parser.add_argument( "-p", @@ -265,90 +251,24 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): help="Display a pie chart for weights", ) - if port_type == "max_sharpe": - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-r") - parser.add_argument( - "-r", - "--risk-free-rate", - type=float, - dest="risk_free_rate", - default=0.02, - help="""Risk-free rate of borrowing/lending. The period of the risk-free rate - should correspond to the frequency of expected returns.""", - ) - - elif port_type == "efficient_risk": - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-t") - parser.add_argument( - "-t", - "--target-volatility", - type=float, - dest="target_volatility", - default=0.1, - help="The desired maximum volatility of the resulting portfolio", - ) - parser.add_argument( - "-n", - "--market-neutral", - action="store_true", - default=False, - dest="market_neutral", - help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. - Requires negative lower weight bound.""", - ) - - elif port_type == "efficient_return": - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-t") - parser.add_argument( - "-t", - "--target-return", - type=float, - dest="target_return", - default=0.1, - help="the desired return of the resulting portfolio", - ) - parser.add_argument( - "-n", - "--market-neutral", - action="store_true", - default=False, - dest="market_neutral", - help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. - Requires negative lower weight bound.""", - ) - - elif port_type == "max_quadratic_utility": - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-r") - parser.add_argument( - "-r", - "--risk-aversion", - type=float, - dest="risk_aversion", - default=1, - help="risk aversion parameter", - ) - parser.add_argument( - "-n", - "--market-neutral", - action="store_true", - default=False, - dest="market_neutral", - help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. - Requires negative lower weight bound.""", - ) + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-r") + parser.add_argument( + "-r", + "--risk-free-rate", + type=float, + dest="risk_free_rate", + default=0.02, + help="""Risk-free rate of borrowing/lending. The period of the risk-free rate + should correspond to the frequency of expected returns.""", + ) try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return None + if len(stocks) < 2: print("Please have at least 2 loaded tickers to calculate weights.\n") return @@ -359,36 +279,8 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): sp = d_period[ns_parser.period] - if port_type == "max_sharpe": - ef_opt = dict(ef.max_sharpe(ns_parser.risk_free_rate)) - s_title = f"{sp} Weights that maximize Sharpe ratio with risk free level of {ns_parser.risk_free_rate}" - - elif port_type == "min_volatility": - ef_opt = dict(ef.min_volatility()) - s_title = f"{sp} Weights that minimize volatility" - - elif port_type == "efficient_risk": - ef_opt = dict( - ef.efficient_risk(ns_parser.target_volatility, ns_parser.market_neutral) - ) - s_title = f"{sp} Weights that maximize return with a maximum volatility of {ns_parser.target_volatility}" - - elif port_type == "efficient_return": - ef_opt = dict( - ef.efficient_return(ns_parser.target_return, ns_parser.market_neutral) - ) - s_title = f"{sp} Weights that minimise volatility for a given target return of {ns_parser.target_return}" - - elif port_type == "max_quadratic_utility": - ef_opt = dict( - ef.max_quadratic_utility( - ns_parser.risk_aversion, ns_parser.market_neutral - ) - ) - s_title = f"{sp} Weights that maximise the quadratic utility with risk aversion of {ns_parser.risk_aversion}" - - else: - raise ValueError("EF Method not found") + ef_opt = dict(ef.max_sharpe(ns_parser.risk_free_rate)) + s_title = f"{sp} Weights that maximize Sharpe ratio with risk free level of {ns_parser.risk_free_rate}" weights = { key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() @@ -410,6 +302,414 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): return +def min_volatility(stocks: List[str], other_args: List[str]): + """ + Return a portfolio that optimizes for minimum volatility + + Parameters + ---------- + stocks: List[str] + List of the stocks to be included in the weights + + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers. + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="min_volatility", + description="Optimizes for minimum volatility", + ) + 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_known_args_and_warn(parser, other_args) + if not ns_parser: + return None + + 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) + + sp = d_period[ns_parser.period] + + ef_opt = dict(ef.min_volatility()) + s_title = f"{sp} Weights that minimize volatility" + + weights = { + key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() + } + + if ns_parser.pie: + pie_chart_weights(weights, s_title) + else: + print(s_title) + display_weights(weights) + print("") + + ef.portfolio_performance(verbose=True) + print("") + + except Exception as e: + print(e) + print("") + return + + +def max_quadratic_utility(stocks: List[str], other_args: List[str]): + """ + Return a portfolio that maximises the quadratic utility, given some risk aversion + + Parameters + ---------- + stocks: List[str] + List of the stocks to be included in the weights + + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers. + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="max_quadratic_utility", + description="Maximises the quadratic utility, given some risk aversion", + ) + 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( + "-n", + "--market-neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + if "-n" not in other_args: + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights. Only if neutral flag is left False.", + ) + + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-r") + + parser.add_argument( + "-r", + "--risk-aversion", + type=float, + dest="risk_aversion", + default=1, + help="risk aversion parameter", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return None + + 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) + + sp = d_period[ns_parser.period] + + ef_opt = dict( + ef.max_quadratic_utility(ns_parser.risk_aversion, ns_parser.market_neutral) + ) + s_title = f"{sp} Weights that maximise the quadratic utility with risk aversion of {ns_parser.risk_aversion}" + + weights = { + key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() + } + + if not ns_parser.market_neutral: + if ns_parser.pie: + pie_chart_weights(weights, s_title) + ef.portfolio_performance(verbose=True) + print("") + return + + print(s_title) + display_weights(weights) + print("") + ef.portfolio_performance(verbose=True) + print("") + + except Exception as e: + print(e) + print("") + return + + +def efficient_risk(stocks: List[str], other_args: List[str]): + """ + Return a portfolio that maximises return for a target risk. The resulting portfolio will have + a volatility less than the target (but not guaranteed to be equal) + + Parameters + ---------- + stocks: List[str] + List of the stocks to be included in the weights + + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers. + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="effrisk", + description="""Maximise return for a target risk. The resulting portfolio will have + a volatility less than the target (but not guaranteed to be equal)""", + ) + 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( + "-n", + "--market-neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + if "-n" not in other_args: + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights. Only if neutral flag is left False.", + ) + + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + parser.add_argument( + "-t", + "--target-volatility", + type=float, + dest="target_volatility", + default=0.1, + help="The desired maximum volatility of the resulting portfolio", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return None + + 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) + + sp = d_period[ns_parser.period] + + ef_opt = dict( + ef.efficient_risk(ns_parser.target_volatility, ns_parser.market_neutral) + ) + s_title = f"{sp} Weights that maximize return with a maximum volatility of {ns_parser.target_volatility}" + + weights = { + key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() + } + + if not ns_parser.market_neutral: + if ns_parser.pie: + pie_chart_weights(weights, s_title) + ef.portfolio_performance(verbose=True) + print("") + return + + print(s_title) + display_weights(weights) + print("") + ef.portfolio_performance(verbose=True) + print("") + + except Exception as e: + print(e) + print("") + return + + +def efficient_return(stocks: List[str], other_args: List[str]): + """ + Return a portfolio that minimises volatility for a given target return ('Markowitz portfolio') + + Parameters + ---------- + stocks: List[str] + List of the stocks to be included in the weights + + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers. + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="effret", + description="Calculate the 'Markowitz portfolio', minimising volatility for a given target return", + ) + 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( + "-n", + "--market-neutral", + action="store_true", + default=False, + dest="market_neutral", + help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False. + Requires negative lower weight bound.""", + ) + if "-n" not in other_args: + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights. Only if neutral flag is left False.", + ) + + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + parser.add_argument( + "-t", + "--target-return", + type=float, + dest="target_return", + default=0.1, + help="the desired return of the resulting portfolio", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return None + + 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) + + sp = d_period[ns_parser.period] + + ef_opt = dict( + ef.efficient_return(ns_parser.target_return, ns_parser.market_neutral) + ) + s_title = f"{sp} Weights that minimise volatility for a given target return of {ns_parser.target_return}" + + weights = { + key: ns_parser.value * round(value, 5) for key, value in ef_opt.items() + } + + if not ns_parser.market_neutral: + if ns_parser.pie: + pie_chart_weights(weights, s_title) + ef.portfolio_performance(verbose=True) + print("") + return + + print(s_title) + display_weights(weights) + print("") + ef.portfolio_performance(verbose=True) + 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, diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index a7ef204f128..398cbc883ab 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -5,11 +5,13 @@ __docformat__ = "numpy" import argparse from typing import List import matplotlib.pyplot as plt +from datetime import datetime 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 +from gamestonk_terminal.comparison_analysis import ca_controller class PortfolioOptimization: @@ -28,6 +30,7 @@ class PortfolioOptimization: "effrisk", "maxquadutil", "ef", + "ca", "yolo", ] @@ -74,6 +77,9 @@ class PortfolioOptimization: print("") print(" ef show the efficient frontier") print("") + if tickers: + print(" > ca comparison analysis for selected tickers") + print("") def switch(self, an_input: str): """Process and dispatch input @@ -122,23 +128,23 @@ class PortfolioOptimization: def call_maxsharpe(self, other_args: List[str]): """Process maxsharpe command""" - optimizer_view.ef_portfolio(self.tickers, "max_sharpe", other_args) + optimizer_view.max_sharpe(self.tickers, other_args) def call_minvol(self, other_args: List[str]): """Process minvol command""" - optimizer_view.ef_portfolio(self.tickers, "min_volatility", other_args) + optimizer_view.min_volatility(self.tickers, other_args) def call_maxquadutil(self, other_args: List[str]): """Process maxquadutil command""" - optimizer_view.ef_portfolio(self.tickers, "max_quadratic_utility", other_args) + optimizer_view.max_quadratic_utility(self.tickers, other_args) def call_effrisk(self, other_args: List[str]): """Process effrisk command""" - optimizer_view.ef_portfolio(self.tickers, "efficient_risk", other_args) + optimizer_view.efficient_risk(self.tickers, other_args) def call_effret(self, other_args: List[str]): """Process effret command""" - optimizer_view.ef_portfolio(self.tickers, "efficient_return", other_args) + optimizer_view.efficient_return(self.tickers, other_args) def call_ef(self, other_args): """Process ef command""" From 26923bfa07bf7245542af9948f6dd3a86b1c04e2 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 22:01:19 +0100 Subject: [PATCH 15/20] Add dividend and mktcap back as commands. Fix not finding property different than None --- .../portfolio_optimization/optimizer_view.py | 6 ++++++ .../portfolio_optimization/po_controller.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py index f54ccdc3ea3..6c61666d5bf 100644 --- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -193,6 +193,12 @@ def property_weighting(stocks: List[str], other_args: List[str]): stock_prop = 0 prop[stock] = stock_prop prop_sum += stock_prop + + if prop_sum == 0: + print(f"No {ns_parser.property} was found on list of tickers provided") + print("") + return + for k, v in prop.items(): weights[k] = round(v / prop_sum, 5) * ns_parser.value diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 398cbc883ab..98fb0cf4557 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -23,6 +23,8 @@ class PortfolioOptimization: "select", "add", "equal", + "mktcap", + "dividend", "property", "maxsharpe", "minvol", @@ -60,6 +62,10 @@ class PortfolioOptimization: print("") print("Optimization:") print(" equal equally weighted") + print(" mktcap weighted according to market cap (property marketCap)") + print( + " dividend weighted according to dividend yield (property dividendYield)" + ) print( " property weight according to selected info property (e.g. marketCap)" ) @@ -122,6 +128,16 @@ class PortfolioOptimization: """Process equal command""" optimizer_view.equal_weight(self.tickers, other_args) + def call_mktcap(self, other_args: List[str]): + """Process mktcap command""" + other_args.insert(0, "marketCap") + optimizer_view.property_weighting(self.tickers, other_args) + + def call_dividend(self, other_args: List[str]): + """Process dividend command""" + other_args.insert(0, "dividendYield") + optimizer_view.property_weighting(self.tickers, other_args) + def call_property(self, other_args: List[str]): """Process property command""" optimizer_view.property_weighting(self.tickers, other_args) From c8a5fa81deed66f0eef5414ed85510a591650af5 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 22:03:38 +0100 Subject: [PATCH 16/20] Refactor po_controller helper --- gamestonk_terminal/portfolio_optimization/po_controller.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 98fb0cf4557..0913f2b50d4 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -66,9 +66,7 @@ class PortfolioOptimization: print( " dividend weighted according to dividend yield (property dividendYield)" ) - print( - " property weight according to selected info property (e.g. marketCap)" - ) + print(" property weight according to selected info property") print("") print("Mean Variance Optimization:") print( From 6b85a2bcd38477d8fdccf1573b4ee3a857755a66 Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 22:12:18 +0100 Subject: [PATCH 17/20] Update README.md --- .../portfolio_optimization/README.md | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index d3591364c21..6542f2476c5 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -8,8 +8,12 @@ This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of th * overwrite current tickers with new tickers * [equal](#equal) * equally weighted +* [mktcap](#mktcap) + * weighted according to market cap (property marketCap) +* [dividend](#dividend) + * weighted according to dividend yield (property dividendYield) * [property](#property) - * weight according to selected info property (e.g. marketCap) + * weight according to selected info property [Mean Variance Optimization](#Mean_Variance_Optimization) @@ -63,6 +67,34 @@ Returns an equally weighted portfolio ![equal](https://user-images.githubusercontent.com/25267873/114958292-9771e080-9e5a-11eb-9de6-1c2dfbe1e27b.png) +### mktcap + +```text +property -p marketCap [-v VALUE] [--pie] +``` + +Returns a portfolio that is weighted based on market cap info + +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights + +![mktCap](https://user-images.githubusercontent.com/25267873/115084151-57fdcf80-9f00-11eb-885c-bfe93de0eb6b.png) + + +### dividend + +```text +property -p dividend [-v VALUE] [--pie] +``` + +Returns a portfolio that is weighted based on dividend yield info + +* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie : Display a pie chart for weights + +Captura de ecrã 2021-04-16, às 22 11 41 + + ### property ```text From f97a2eea1d9de103db4a7790c732d0ac4b437f5a Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Fri, 16 Apr 2021 22:19:23 +0100 Subject: [PATCH 18/20] Update README.md --- gamestonk_terminal/portfolio_optimization/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index 6542f2476c5..3e9e44d4603 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -114,9 +114,9 @@ Returns a portfolio that is weighted based on a selected property info These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem. -Minimize: - -Subject to: , and +

+ +

* Where S is the covariance matrix between stocks and R is the expected returns. * The condition that all weights add up to 1 just implies that you want to have a net long portfolio (with no margin). From 4a7b7402368693e39ce2167fe03c4977be4dc881 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 23:35:32 +0100 Subject: [PATCH 19/20] flake8 leave me alone pleaaaase --- gamestonk_terminal/comparison_analysis/ca_controller.py | 1 - gamestonk_terminal/portfolio_optimization/po_controller.py | 1 - 2 files changed, 2 deletions(-) diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index 499b6c9cd39..deedd6b8ff6 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -5,7 +5,6 @@ import argparse import requests import random from typing import List -from datetime import datetime import pandas as pd from matplotlib import pyplot as plt from gamestonk_terminal import feature_flags as gtff diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 0913f2b50d4..6f2bb6b7bda 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -5,7 +5,6 @@ __docformat__ = "numpy" import argparse from typing import List import matplotlib.pyplot as plt -from datetime import datetime 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 f8bd6f9e6ac1651992a146ea0c6044f40972d442 Mon Sep 17 00:00:00 2001 From: didier Date: Fri, 16 Apr 2021 23:39:10 +0100 Subject: [PATCH 20/20] removed wrong import --- gamestonk_terminal/comparison_analysis/ca_controller.py | 1 + gamestonk_terminal/portfolio_optimization/po_controller.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index deedd6b8ff6..499b6c9cd39 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -5,6 +5,7 @@ import argparse import requests import random from typing import List +from datetime import datetime import pandas as pd from matplotlib import pyplot as plt from gamestonk_terminal import feature_flags as gtff diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 6f2bb6b7bda..c66ca63e846 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -10,7 +10,6 @@ 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 -from gamestonk_terminal.comparison_analysis import ca_controller class PortfolioOptimization: