From ccd322d6b60c9755154b3d86ed1b47f815c8dc12 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 11 Apr 2021 16:15:11 -0400 Subject: [PATCH] add basic portfolio optimization controller --- .../comparison_analysis/ca_controller.py | 8 + gamestonk_terminal/main_helper.py | 1 + .../portfolio_optimization/README.md | 16 ++ .../portfolio_optimization/__init__.py | 0 .../portfolio_optimization/port_opt_api.py | 28 +++ .../port_optimization_controller.py | 160 ++++++++++++++++++ terminal.py | 6 + 7 files changed, 219 insertions(+) create mode 100644 gamestonk_terminal/portfolio_optimization/README.md create mode 100644 gamestonk_terminal/portfolio_optimization/__init__.py create mode 100644 gamestonk_terminal/portfolio_optimization/port_opt_api.py create mode 100644 gamestonk_terminal/portfolio_optimization/port_optimization_controller.py diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index 67b91f8b4da..9c4aaa89258 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -15,6 +15,7 @@ from gamestonk_terminal.comparison_analysis import yahoo_finance_api as yf_api from gamestonk_terminal.comparison_analysis import market_watch_api as mw_api from gamestonk_terminal.comparison_analysis import finbrain_api as f_api from gamestonk_terminal.comparison_analysis import finviz_compare_view +from gamestonk_terminal.portfolio_optimization import port_optimization_controller from gamestonk_terminal.menu import session from prompt_toolkit.completion import NestedCompleter @@ -42,6 +43,7 @@ class ComparisonAnalysisController: "ownership", "performance", "technical", + "po" ] def __init__( @@ -107,6 +109,8 @@ class ComparisonAnalysisController: print(" performance brief performance comparison") print(" technical brief technical comparison") print("") + print(" po portfolio optimzation from selected tickers") + print("") return @staticmethod @@ -312,6 +316,10 @@ class ComparisonAnalysisController: """Process technical command""" finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar) + def call_po(self, _): + """Open Portfolio Optimization menu with ticker and similar""" + port_optimization_controller.menu([self.ticker] + self.similar) + def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str): """Comparison Analysis Menu""" diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 81238c99223..8b152e09909 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -60,6 +60,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): ) print(" pa portfolio analysis, \t\t supports: robinhood, alpaca, ally ") print(" crypto cryptocurrencies, \t\t uses coingecko api") + print(" po portfolio analysis, \t\t UPDATE ME BEFORE PR") if s_ticker: print( diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md new file mode 100644 index 00000000000..b546337b1be --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -0,0 +1,16 @@ +# Portfolio Optimization + +This menu aims to discover optimal portfolios for selected stocks + + +###add +Adds tickers to current list for optimization +```` +usage: add ticker1,ticker2,... +```` +###optimize +Runs optimization +```` +usage: optimize +```` + diff --git a/gamestonk_terminal/portfolio_optimization/__init__.py b/gamestonk_terminal/portfolio_optimization/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py new file mode 100644 index 00000000000..c4654af9965 --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py @@ -0,0 +1,28 @@ +""" Portfolio Optimization Functions""" +__docformat__ = "numpy" + +import argparse +from typing import List + +def optimize(list_of_stocks:List, other_args:List[str]): + """ + + Parameters + ---------- + list_of_stocks: List[str] + List of tickers to be included in optimization + + Returns + ------- + weights : dict + Dictionary of weights where keys are the tickers + + """ + weights = {} + n_stocks = len(list_of_stocks) + for stock in list_of_stocks: + weights[stock] = round(1/n_stocks, 5) + + return weights + + diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py new file mode 100644 index 00000000000..b90bb4b8b8d --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py @@ -0,0 +1,160 @@ +"""Portfolio Optimization Controller Module""" +__docformat__ = "numpy" + + +import argparse +from typing import List, Set +from datetime import datetime + +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn +from gamestonk_terminal.menu import session +from gamestonk_terminal.portfolio_optimization import port_opt_api as po_api +from prompt_toolkit.completion import NestedCompleter + +class PortfolioOptimization: + + CHOICES = ["help", + "q", + "quit", + "clear", + "add", + "optimize"] + + def __init__(self, + tickers:Set[str] = None + ): + """ + Construct Portfolio Optimization + """ + + self.po_parser = argparse.ArgumentParser(add_help=False, prog="po") + self.po_parser.add_argument("cmd", choices=self.CHOICES ) + self.tickers = set(tickers) + + @staticmethod + def print_help(tickers: Set[str]): + """Print help""" + print("\nPortfolio Optimization:") + print(" help show this menu again") + print( + " q quit this menu, and shows back to main menu" + ) + print(" quit quit to abandon program") + print( + f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}" + ) + print("") + print(" add add ticker to optimize") + print(" optimize run optimization") + print("") + + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + (known_args, other_args) = self.po_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help(self.tickers) + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_add(self, other_args:List[str]): + self.add_stocks(self, other_args) + + def call_optimize(self, other_args:List[str]): + weights = po_api.optimize(self.tickers, other_args) + print("Optimal Weights for Equal Weighting:") + print(weights) + print("") + + @staticmethod + def add_stocks(self, other_args: List[str]): + + """ Add ticker to current list for optimization""" + parser = argparse.ArgumentParser( + add_help=False, + prog="add", + description="""Add tickers for optimizing.""", + ) + parser.add_argument( + "-t", + "--tickers", + dest="add_tickers", + type=lambda s: [str(item).upper() for item in s.split(",")], + default=[], + help="add tickers to optimzation.", + ) + try: + # For the case where a user uses: 'select NIO,XPEV,LI' + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + for ticker in ns_parser.add_tickers: + self.tickers.add(ticker) + + + print( + f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}" + ) + print("") + except Exception as e: + print(e) + + print("") + + +def menu(tickers:List[str]): + """Portfolio Optimization Menu""" + if tickers == ['']: + tickers = [] + po_controller = PortfolioOptimization(tickers) + po_controller.call_help(tickers) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in po_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (po)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (po)> ") + + try: + process_input = po_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue + diff --git a/terminal.py b/terminal.py index 91fa02c2e94..123c73a78da 100644 --- a/terminal.py +++ b/terminal.py @@ -31,6 +31,7 @@ from gamestonk_terminal.residuals_analysis import ra_controller from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller +from gamestonk_terminal.portfolio_optimization import port_optimization_controller as po_controller # import warnings # warnings.simplefilter("always") @@ -88,7 +89,9 @@ def main(): "pa", "crypto", "ra", + "po" ] + menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -242,6 +245,9 @@ def main(): elif ns_known_args.opt == "crypto": b_quit = crypto_controller.menu() + elif ns_known_args.opt == "po": + b_quit = po_controller.menu([s_ticker]) + elif ns_known_args.opt == "pred": if not gtff.ENABLE_PREDICT: