diff --git a/ROADMAP.md b/ROADMAP.md index 57d332b0f50..699d5329397 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 diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md index 52eb70cf214..4ba78a602bf 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 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. + +### 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