From 3949bed65a7c2500da20aa1294df5934d92627cd Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 20 Mar 2021 19:59:41 -0400 Subject: [PATCH 1/6] refactored portfolio to be class --- .../portfolio/port_controller.py | 153 ++++++++++++++++++ gamestonk_terminal/portfolio/port_menu.py | 100 ------------ terminal.py | 7 +- 3 files changed, 156 insertions(+), 104 deletions(-) create mode 100644 gamestonk_terminal/portfolio/port_controller.py delete mode 100644 gamestonk_terminal/portfolio/port_menu.py diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py new file mode 100644 index 00000000000..055b22e69ff --- /dev/null +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -0,0 +1,153 @@ +__docformat__ = "numpy" + +import argparse +from typing import List +from matplotlib import pyplot as plt +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session +from gamestonk_terminal.portfolio import rh_api + +class PortfolioController: + + CHOICES = [ + "help", + "q", + "quit", + "login", + "hold", + "rhhist", + ] + + BROKERS = [ + "rh", + ] + + def __init__(self): + self.port_parser = argparse.ArgumentParser(add_help=False, prog="port") + self.port_parser.add_argument("cmd", + choices = self.CHOICES) + self.brokers_list = set() + + + @staticmethod + def print_help(broker_list): + if not broker_list: + print_broke = " None" + if broker_list: + print_broke=" " + for broker in broker_list: + print_broke += broker+" " + + """ Print help """ + print("\nPortfolio:") + print(" help show this menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print(" login login to your brokers") + print("\nCurrent Brokers :" + print_broke) + print("") + print("\nCurrently Supported :") + print(" rh Robinhood - fuck these guys") + print("") + + + def print_portfolio_menu(self): + + print_broke = " " + for broker in self.brokers_list: + print_broke += broker + " " + print("\nCurrent Brokers :" + print_broke) + print("") + print(" hold check holdings") + print(" rhhist plot historical RH portfolio") + 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.port_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.brokers_list) + + 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_login(self, other_args): + + if not other_args: + print("Please enter brokers you wish to login to") + print("") + return + for broker in other_args: + api = broker + "_api" + self.brokers_list.add(broker) + try: + eval(api+".login()") + except Exception as e: + print("") + print(f"Error at broker : {broker}") + print(e) + print("Make sure credentials are defined in config_terminal.py ") + print("") + + self.print_portfolio_menu() + + def call_rhhist(self, other_args: List[str]): + rh_api.plot_historical(other_args) + + def call_hold(self,_): + try: + rh_api.show_holdings() + except Exception as e: + print(e) + print("") + +def menu(): + """Portfolio Analysis Menu""" + + port_controller = PortfolioController() + port_controller.print_help(port_controller.brokers_list) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in port_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (port)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (port)> ") + + try: + process_input = port_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/gamestonk_terminal/portfolio/port_menu.py b/gamestonk_terminal/portfolio/port_menu.py deleted file mode 100644 index df46626a2c6..00000000000 --- a/gamestonk_terminal/portfolio/port_menu.py +++ /dev/null @@ -1,100 +0,0 @@ -import argparse -from matplotlib import pyplot as plt -from prompt_toolkit.completion import NestedCompleter -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import get_flair -from gamestonk_terminal.menu import session -from gamestonk_terminal.portfolio import rh_api - - -def print_port(show_login): - """ Print help """ - print("\nPortfolio:") - print(" help show this menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print(" ") - print("Robinhood:") - print("") - if show_login: - print(" login login") - print(" hold look at current holdings") - print(" rhhist look at historical portfolio") - print(" ") - - -def port_menu(): - plt.close("all") - port_parser = argparse.ArgumentParser(prog="port", add_help=False) - choices = ["help", "q", "quit", "hold", "rhhist", "login"] - port_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - should_print_help = True - print_login = True - while True: - if should_print_help: - print_port(print_login) - should_print_help = False - - # Get input command from user - if session and gtff.USE_PROMPT_TOOLKIT: - as_input = session.prompt( - f"{get_flair()} (port)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (port)> ") - try: - (ns_known_args, l_args) = port_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - should_print_help = True - - elif ns_known_args.cmd == "q": - # Leave the port menu + logout of robinhood - # logoff raises error if not logged in - try: - rh_api.logoff() - except Exception: - pass - return False - - elif ns_known_args.cmd == "quit": - # Will raise exception if not logged in - try: - rh_api.logoff() - except Exception: - pass - # Abandon the program - return True - - elif ns_known_args.cmd == "hold": - try: - rh_api.show_holdings() - except Exception as e: - print(e) - print("") - - elif ns_known_args.cmd == "rhhist": - rh_api.plot_historical(l_args) - - elif ns_known_args.cmd == "login": - try: - rh_api.login() - should_print_help = True - print_login = False - except Exception as e: - print("") - print(e) - print("Make sure credentials are defined in config_terminal.py ") - print("") - should_print_help = False - print_login = True - - else: - print("Command not recognized") - print("") diff --git a/terminal.py b/terminal.py index 6b995ffb1fe..82b72935c35 100644 --- a/terminal.py +++ b/terminal.py @@ -25,8 +25,7 @@ from gamestonk_terminal.technical_analysis import ta_menu as tam from gamestonk_terminal.comparison_analysis import ca_menu as cam from gamestonk_terminal.options import op_menu as opm from gamestonk_terminal.fred import fred_menu as fm -from gamestonk_terminal.portfolio import port_menu - +from gamestonk_terminal.portfolio import port_controller # import warnings # warnings.simplefilter("always") @@ -199,8 +198,8 @@ def main(): b_quit = fm.fred_menu() elif ns_known_args.opt == "port": - b_quit = port_menu.port_menu() - + #b_quit = port_menu.port_menu() + b_quit = port_controller.menu() elif ns_known_args.opt == "pred": if not gtff.ENABLE_PREDICT: From 2fc0bc7f1aad8b449d19ec8b8a9e7976b912efb0 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 20 Mar 2021 20:08:27 -0400 Subject: [PATCH 2/6] update readme + requirements.txt --- gamestonk_terminal/portfolio/README.md | 18 ++++++++++++++++-- .../portfolio/port_controller.py | 18 ++++++++---------- requirements.txt | 2 ++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md index de0511d4e42..52eb70cf214 100644 --- a/gamestonk_terminal/portfolio/README.md +++ b/gamestonk_terminal/portfolio/README.md @@ -2,12 +2,26 @@ This page shows the available brokers for loading in data. If yours is not listed, please submit a new issue and we will look at adding it. +Current brokers: +* Robinhood + Once this screen is accessed, the first command to be run is ```` -login +login [brokers] ```` -Which will use your login information, which should be stored as environment variables in [config file](/gamestonk_terminal/config_terminal.py) + +The brokers should just be your brokers from the list shown on the first menu. To login to Robinhood, the command is +```` +login rh +```` +When td is added, the command will be +```` +login rh td +```` +After logging in, the help menu will display which brokers you logged into. + +Your login information should be stored as environment variables in [config file](/gamestonk_terminal/config_terminal.py) Robinhood has Two Factor Authentication, so you will likely be prompted to enter a code that is texted/emailed to you. ###NOTE THAT LOGGING IN WILL SAVE A TOKEN TO `os.path.expanduser("~")/.tokens` WHICH CAN BE USED TO LOGIN EVEN IF USER CREDENTIALS ARE INCORRECT. diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py index 055b22e69ff..41ae63b0879 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -9,6 +9,7 @@ from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal.menu import session from gamestonk_terminal.portfolio import rh_api + class PortfolioController: CHOICES = [ @@ -18,7 +19,7 @@ class PortfolioController: "login", "hold", "rhhist", - ] + ] BROKERS = [ "rh", @@ -26,19 +27,17 @@ class PortfolioController: def __init__(self): self.port_parser = argparse.ArgumentParser(add_help=False, prog="port") - self.port_parser.add_argument("cmd", - choices = self.CHOICES) + self.port_parser.add_argument("cmd", choices=self.CHOICES) self.brokers_list = set() - @staticmethod def print_help(broker_list): if not broker_list: print_broke = " None" if broker_list: - print_broke=" " + print_broke = " " for broker in broker_list: - print_broke += broker+" " + print_broke += broker + " " """ Print help """ print("\nPortfolio:") @@ -52,7 +51,6 @@ class PortfolioController: print(" rh Robinhood - fuck these guys") print("") - def print_portfolio_menu(self): print_broke = " " @@ -64,7 +62,6 @@ class PortfolioController: print(" rhhist plot historical RH portfolio") print("") - def switch(self, an_input: str): """Process and dispatch input @@ -103,7 +100,7 @@ class PortfolioController: api = broker + "_api" self.brokers_list.add(broker) try: - eval(api+".login()") + eval(api + ".login()") except Exception as e: print("") print(f"Error at broker : {broker}") @@ -116,13 +113,14 @@ class PortfolioController: def call_rhhist(self, other_args: List[str]): rh_api.plot_historical(other_args) - def call_hold(self,_): + def call_hold(self, _): try: rh_api.show_holdings() except Exception as e: print(e) print("") + def menu(): """Portfolio Analysis Menu""" diff --git a/requirements.txt b/requirements.txt index 06971cc844c..2a0bc3abb1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ aiohttp==3.7.4.post0; python_version >= "3.6" +alpaca-trade-api==1.0.1 alpha-vantage==2.3.1 ansiwrap==0.8.4; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" @@ -103,6 +104,7 @@ rapidfuzz==1.2.1; python_version >= "2.7" regex==2020.11.13; python_version >= "3.6" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") retrying==1.3.3 +robin-stocks==2.0.3 scipy==1.5.4; python_version >= "3.6" seaborn==0.11.1; python_version >= "3.6" send2trash==1.5.0; python_version >= "3.5" From 7486072b29b93bd947ecdcc2c1cab711637b95df Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 20 Mar 2021 20:33:31 -0400 Subject: [PATCH 3/6] add message for incorrect broker --- .../portfolio/port_controller.py | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py index 41ae63b0879..47a6347a320 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -1,8 +1,8 @@ __docformat__ = "numpy" import argparse +from ast import literal_eval from typing import List -from matplotlib import pyplot as plt from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import get_flair @@ -42,13 +42,18 @@ class PortfolioController: """ Print help """ print("\nPortfolio:") print(" help show this menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") + print( + " q quit this menu, and shows back to main menu, logs out of brokers" + ) + print(" quit quit to abandon program, logs out of brokers") print(" login login to your brokers") print("\nCurrent Brokers :" + print_broke) - print("") print("\nCurrently Supported :") print(" rh Robinhood - fuck these guys") + print("\nCommands (login required):") + print("") + print(" hold view rh holdings") + print(" rhhist plot rh portfolio history") print("") def print_portfolio_menu(self): @@ -92,23 +97,29 @@ class PortfolioController: def call_login(self, other_args): + logged_in = False if not other_args: print("Please enter brokers you wish to login to") print("") return for broker in other_args: - api = broker + "_api" - self.brokers_list.add(broker) - try: - eval(api + ".login()") - except Exception as e: - print("") - print(f"Error at broker : {broker}") - print(e) - print("Make sure credentials are defined in config_terminal.py ") - print("") + if broker in self.BROKERS: + api = broker + "_api" + try: + eval(api + ".login()") + self.brokers_list.add(broker) + logged_in = True + except Exception as e: + print("") + print(f"Error at broker : {broker}") + print(e) + print("Make sure credentials are defined in config_terminal.py ") + print("") + else: + print(f"{broker} not supported") - self.print_portfolio_menu() + if logged_in: + self.print_portfolio_menu() def call_rhhist(self, other_args: List[str]): rh_api.plot_historical(other_args) From 6ec10cccb0f26b19c9f01662975fa8541a451870 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 20 Mar 2021 20:35:45 -0400 Subject: [PATCH 4/6] unused imports --- gamestonk_terminal/portfolio/port_controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py index 47a6347a320..a0b19c1a2f8 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -1,7 +1,6 @@ __docformat__ = "numpy" import argparse -from ast import literal_eval from typing import List from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import feature_flags as gtff From 4d8bd1282bcb017e143082f9135683a852346bd3 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 20 Mar 2021 20:39:56 -0400 Subject: [PATCH 5/6] run black --- terminal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminal.py b/terminal.py index 82b72935c35..5bc5bd7d827 100644 --- a/terminal.py +++ b/terminal.py @@ -26,6 +26,7 @@ from gamestonk_terminal.comparison_analysis import ca_menu as cam from gamestonk_terminal.options import op_menu as opm from gamestonk_terminal.fred import fred_menu as fm from gamestonk_terminal.portfolio import port_controller + # import warnings # warnings.simplefilter("always") @@ -198,7 +199,7 @@ def main(): b_quit = fm.fred_menu() elif ns_known_args.opt == "port": - #b_quit = port_menu.port_menu() + # b_quit = port_menu.port_menu() b_quit = port_controller.menu() elif ns_known_args.opt == "pred": From 8b665a6d669764a7276192a513911fb17cd98400 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 21 Mar 2021 16:12:32 -0400 Subject: [PATCH 6/6] change string formatting --- gamestonk_terminal/main_helper.py | 2 +- gamestonk_terminal/portfolio/port_controller.py | 12 ++++-------- terminal.py | 6 +++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 2444b02d122..49727d34b4b 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -53,7 +53,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): print( " fred economic data, \t\t\t from: Federal Reserve Bank of St. Louis " ) - print(" port portfolio, \t\t\t Currently: Robinhood ") + print(" pa portfolio analysis, \t\t Currently Supported: Robinhood ") if s_ticker: print( diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py index a0b19c1a2f8..597ffeeb094 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -32,11 +32,7 @@ class PortfolioController: @staticmethod def print_help(broker_list): if not broker_list: - print_broke = " None" - if broker_list: - print_broke = " " - for broker in broker_list: - print_broke += broker + " " + broker_list = [" None"] """ Print help """ print("\nPortfolio:") @@ -46,7 +42,7 @@ class PortfolioController: ) print(" quit quit to abandon program, logs out of brokers") print(" login login to your brokers") - print("\nCurrent Brokers :" + print_broke) + print(f"\nCurrent Brokers : {('', ', '.join(broker_list))[bool(broker_list)]}") print("\nCurrently Supported :") print(" rh Robinhood - fuck these guys") print("\nCommands (login required):") @@ -144,11 +140,11 @@ def menu(): {c: None for c in port_controller.CHOICES} ) an_input = session.prompt( - f"{get_flair()} (port)> ", + f"{get_flair()} (pa)> ", completer=completer, ) else: - an_input = input(f"{get_flair()} (port)> ") + an_input = input(f"{get_flair()} (pa)> ") try: process_input = port_controller.switch(an_input) diff --git a/terminal.py b/terminal.py index 5bc5bd7d827..4855ee2cce3 100644 --- a/terminal.py +++ b/terminal.py @@ -77,7 +77,7 @@ def main(): "ca", "op", "fred", - "port", + "pa", ] menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -198,9 +198,9 @@ def main(): elif ns_known_args.opt == "fred": b_quit = fm.fred_menu() - elif ns_known_args.opt == "port": - # b_quit = port_menu.port_menu() + elif ns_known_args.opt == "pa": b_quit = port_controller.menu() + elif ns_known_args.opt == "pred": if not gtff.ENABLE_PREDICT: