From 2269764c2cd606ebe12af3f3690c6df51db5541d Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 22 Mar 2021 14:33:46 -0400 Subject: [PATCH 1/3] Add Alpaca Broker with same functions as Robinhood. --- gamestonk_terminal/portfolio/README.md | 65 +++++++++++-- gamestonk_terminal/portfolio/alp_api.py | 97 +++++++++++++++++++ .../portfolio/port_controller.py | 61 +++++++----- 3 files changed, 188 insertions(+), 35 deletions(-) create mode 100644 gamestonk_terminal/portfolio/alp_api.py diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md index 52eb70cf214..1c4756c000c 100644 --- a/gamestonk_terminal/portfolio/README.md +++ b/gamestonk_terminal/portfolio/README.md @@ -4,6 +4,7 @@ This page shows the available brokers for loading in data. If yours is not list Current brokers: * Robinhood +* Alpaca Once this screen is accessed, the first command to be run is @@ -15,31 +16,40 @@ The brokers should just be your brokers from the list shown on the first menu. ```` login rh ```` -When td is added, the command will be +To login to both robinhood and alpaca: ```` -login rh td +login rh alp ```` 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. +Your login information should be stored as environment variables in [config file](/gamestonk_terminal/config_terminal.py), but check each individual section for specifics. [ROBINHOOD](#ROBINHOOD) -* [hold](#hold) +* [rhhold](#rhhold) * Look at current (stock only) holdings * [rhhist](#rhhist) * Get and plot historical portfolio + +[ALPACA](#ALPACA) +* [alphold](#alphold) + * Look at current (stock only) holdings +* [alphist](#rhhist) + * Get and plot historical portfolio ## ROBINHOOD +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. -### hold +In order to login, the [config file](/gamestonk_terminal/config_terminal.py) requires two variables: +* "GT_RH_USERNAME" +* "GT_RH_PASSWORD" + +### rhhold Displays current holdings to the console: ```` -usage: hold +usage: rhhold ```` There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and the % change from previous close. @@ -60,4 +70,39 @@ usage: rhhist [-s --span] [-i --interval] Example Default Output: -![rhhist](https://user-images.githubusercontent.com/18151143/111718919-36da8e00-8831-11eb-99e1-957c8eccb583.png) \ No newline at end of file +![rhhist](https://user-images.githubusercontent.com/18151143/111718919-36da8e00-8831-11eb-99e1-957c8eccb583.png) +## ALPACA + +Alpaca has a nicely maintained python API that is used here. To login, the best approach is to just save the following environment variables: +* "APCA_API_BASE_URL" +* "APCA_API_KEY_ID" +* "APCA_API_SECRET_KEY" + +If these are defined, then the login command does not explictly need to be run, but the login command verifies all those keys are defined. + +Note that alpaca does support paper trading, so if your base_url is "https://paper-api.alpaca.markets", then this will be importing +your paper trading account. + +### alphold + +Displays current holdings to the console. Same as rhhold + +```` +usage: alphold +```` +There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and +the % change from previous close. + +Example Output: +![](https://user-images.githubusercontent.com/18151143/112039340-536d1380-8b1a-11eb-99d1-44d1edd4d54a.png) + +### alphist +Plots historical portfolio. The output plot in this case will not be a candle, as the api provides your equity at a gven time, +unlike the robin_stocks, which gives high/low at a given time. + +```` +usage: alphist [-p --period] [-t --timeframe] +```` + +* -p/--period The duration of the data in \ + \, such as 1D, where can be D for day, W for week, M for month and A for year. Defaults to 1M +* -t/--timeframe The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period, 15Min for less than 30 days, or otherwise 1D. diff --git a/gamestonk_terminal/portfolio/alp_api.py b/gamestonk_terminal/portfolio/alp_api.py new file mode 100644 index 00000000000..2583f6c628c --- /dev/null +++ b/gamestonk_terminal/portfolio/alp_api.py @@ -0,0 +1,97 @@ +import argparse +import os +from pandas.plotting import register_matplotlib_converters +import matplotlib.pyplot as plt +import alpaca_trade_api as alp_api +from termcolor import colored +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, +) +from gamestonk_terminal import feature_flags as gtff + +register_matplotlib_converters() + + +def login(): + + if not ( + os.getenv("APCA_API_KEY_ID") + and os.getenv("APCA_API_SECRET_KEY") + and os.getenv("APCA_API_BASE_URL") + ): + raise ValueError("Alpaca Keys not defined in env") + + +def show_holdings(): + api = alp_api.REST() + positions = api.list_positions() + print("") + print("Stonk\t last price \t prev close \t equity \t % Change") + + for pos in positions: + stonk = pos.symbol + last_price = round(float(pos.current_price), 2) + prev_close = round(float(pos.lastday_price), 2) + eq = round(float(pos.market_value), 2) + pct_change = round(float(pos.change_today), 3) + to_print = f"{stonk}\t {last_price}\t\t {prev_close}\t\t {eq}\t\t {pct_change}" + if pct_change >= 0: + print(colored(to_print, "green")) + else: + print(colored(to_print, "red")) + print("") + + +def plot_historical(l_args): + api = alp_api.REST() + parser = argparse.ArgumentParser( + add_help=False, + prog="Port", + description=""" + Historical Portfolio Info + """, + ) + + parser.add_argument( + "-p", + "--period", + dest="period", + type=str, + default="1M", + help="Duration of data ( + )", + ) + + parser.add_argument( + "-t", + "--timeframe", + dest="timeframe", + default="1D", + type=str, + help="Resolution of data ( + )", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, l_args) + port_hist = api.get_portfolio_history( + period=ns_parser.period, timeframe=ns_parser.timeframe + ).df + + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + plt.plot(port_hist.index, port_hist.equity) + plt.xlim(port_hist.index[0], port_hist.index[-1]) + plt.ylabel("Equity") + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.minorticks_on() + plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + plt.show() + + if gtff.USE_ION: + plt.ion() + print("") + + except Exception as e: + print(e) + print("") + return diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/portfolio/port_controller.py index 597ffeeb094..b8c533fb76e 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/portfolio/port_controller.py @@ -6,33 +6,25 @@ 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 +from gamestonk_terminal.portfolio import rh_api, alp_api class PortfolioController: - CHOICES = [ - "help", - "q", - "quit", - "login", - "hold", - "rhhist", - ] + CHOICES = ["help", "q", "quit", "login", "rhhold", "rhhist", "alphold", "alphist"] BROKERS = [ "rh", + "alp", ] 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() + self.broker_list = set() @staticmethod def print_help(broker_list): - if not broker_list: - broker_list = [" None"] """ Print help """ print("\nPortfolio:") @@ -42,24 +34,33 @@ class PortfolioController: ) print(" quit quit to abandon program, logs out of brokers") print(" login login to your brokers") - print(f"\nCurrent Brokers : {('', ', '.join(broker_list))[bool(broker_list)]}") + print( + f"\nCurrent Brokers : {('None', ', '.join(broker_list))[bool(broker_list)]}" + ) print("\nCurrently Supported :") print(" rh Robinhood - fuck these guys") + print(" alp Alpaca ") print("\nCommands (login required):") - print("") - print(" hold view rh holdings") + print("\nRobinhood:") + print(" rhhold view rh holdings") print(" rhhist plot rh portfolio history") + print("\nAlpaca:") + print(" alphold view alp holdings") + print(" alphist view alp portfolio history") 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( + f"\nCurrent Brokers : {('None', ', '.join(self.broker_list))[bool(self.broker_list)]}" + ) + print("\nRobinhood:") + print(" rhhold check holdings") print(" rhhist plot historical RH portfolio") + print("\nAlpaca:") + print(" alphold view alpaca holdings") + print(" alphist plot historical alpaca portfolio") + print("") def switch(self, an_input: str): @@ -80,7 +81,7 @@ class PortfolioController: def call_help(self, _): """Process Help command""" - self.print_help(self.brokers_list) + self.print_help(self.broker_list) def call_q(self, _): """Process Q command - quit the menu""" @@ -102,7 +103,7 @@ class PortfolioController: api = broker + "_api" try: eval(api + ".login()") - self.brokers_list.add(broker) + self.broker_list.add(broker) logged_in = True except Exception as e: print("") @@ -119,19 +120,29 @@ class PortfolioController: def call_rhhist(self, other_args: List[str]): rh_api.plot_historical(other_args) - def call_hold(self, _): + def call_rhhold(self, _): try: rh_api.show_holdings() except Exception as e: print(e) print("") + def call_alphold(self, _): + try: + alp_api.show_holdings() + except Exception as e: + print(e) + print("") + + def call_alphist(self, other_args: List[str]): + alp_api.plot_historical(other_args) + def menu(): """Portfolio Analysis Menu""" port_controller = PortfolioController() - port_controller.print_help(port_controller.brokers_list) + port_controller.print_help(port_controller.broker_list) while True: # Get input command from user From 651ef9e4762850ade490eb291df4b12d72e99b78 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 22 Mar 2021 14:37:40 -0400 Subject: [PATCH 2/3] spellcheck --- gamestonk_terminal/portfolio/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md index 1c4756c000c..4ba78a602bf 100644 --- a/gamestonk_terminal/portfolio/README.md +++ b/gamestonk_terminal/portfolio/README.md @@ -78,7 +78,7 @@ Alpaca has a nicely maintained python API that is used here. To login, the best * "APCA_API_KEY_ID" * "APCA_API_SECRET_KEY" -If these are defined, then the login command does not explictly need to be run, but the login command verifies all those keys are defined. +If these are defined, then the login command does not explicitly need to be run, but the login command verifies all those keys are defined. Note that alpaca does support paper trading, so if your base_url is "https://paper-api.alpaca.markets", then this will be importing your paper trading account. From e44e93de8d52a0d6ff0742d44c46c76499f51be1 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 22 Mar 2021 15:40:10 -0400 Subject: [PATCH 3/3] updated ROADMAP.md --- ROADMAP.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 3d48f982f4c..bda1aa219fe 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -141,13 +141,12 @@ ## Portfolio Analysis ### Short term -* [ ] Add Brokers (td, alpaca, webull, etc) -* [ ] Merge data from different brokers -* [ ] Refactor +- [x] Add alpaca (@jmaslek) - [PR #259](https://github.com/DidierRLopes/GamestonkTerminal/pull/229) - [x] Add robinhood (@jmaslek) - [PR #229](https://github.com/DidierRLopes/GamestonkTerminal/pull/229) -- [ ] Add Brokers (td, alpaca, webull, etc) (@jmaslek) +- [ ] Add Brokers (td, webull, etc) (@jmaslek) - [ ] Merge data from different brokers (@jmaslek) +- [ ] Refactoring ### Long term