diff --git a/.github/workflows/Mac.yml b/.github/workflows/Mac.yml index 0636f0aea05..996a29b56f8 100644 --- a/.github/workflows/Mac.yml +++ b/.github/workflows/Mac.yml @@ -1,4 +1,7 @@ name: Mac OS X Full Clean Build with ML +env: + GTFF_USE_PROMPT_TOOLKIT: false + GTFF_USE_ION: false on: push: branches: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 382a74b02e7..59b23e270f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,7 @@ name: Tests +env: + GTFF_USE_PROMPT_TOOLKIT: false + GTFF_USE_ION: false on: [pull_request, push] jobs: linting: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 940138e40df..3e469e6fddf 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,4 +1,7 @@ name: Windows 10 Full Clean Build with ML +env: + GTFF_USE_PROMPT_TOOLKIT: false + GTFF_USE_ION: false on: push: branches: diff --git a/Dockerfile b/Dockerfile index d14fb478572..1838468f475 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,12 @@ dnsutils \ git \ gnupg \ unzip \ -bzip2 && \ +bzip2 \ +libsm6 \ +libxt6 \ +libgl1-mesa-glx \ +libpng16-16 \ +python3-tk && \ apt-get -y autoremove && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/ROADMAP.md b/ROADMAP.md index 8989040cbad..62b9a8fe496 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -131,9 +131,13 @@ ## Portfolio Analysis ### Short term +* [ ] Add Brokers (td, alpaca, webull, etc) +* [ ] Merge data from different brokers +* [ ] Refactor ### Long term - +* [ ] Summaries / tear sheets +* [ ] Portfolio Optimization --- ## Credit Analysis diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py index 6939d823cee..fb7c764ad67 100644 --- a/gamestonk_terminal/config_terminal.py +++ b/gamestonk_terminal/config_terminal.py @@ -28,3 +28,7 @@ API_TWITTER_BEARER_TOKEN = os.getenv("GT_API_TWITTER_BEARER_TOKEN") or "REPLACE_ # https://fred.stlouisfed.org/docs/api/api_key.html API_FRED_KEY = os.getenv("GT_FRED_API_KEY") or "REPLACE_ME" + +# Robinhood +RH_USERNAME = os.getenv("GT_RH_USERNAME") or "REPLACE_ME" +RH_PASSWORD = os.getenv("GT_RH_PASSWORD") or "REPLACE_ME" diff --git a/gamestonk_terminal/feature_flags.py b/gamestonk_terminal/feature_flags.py index cb7481d9127..f8360c1872b 100644 --- a/gamestonk_terminal/feature_flags.py +++ b/gamestonk_terminal/feature_flags.py @@ -1,12 +1,13 @@ +from distutils.util import strtobool import os -USE_COLOR = os.getenv("GTFF_USE_COLOR") or True +USE_COLOR = strtobool(os.getenv("GTFF_USE_COLOR", "True")) USE_FLAIR = os.getenv("GTFF_USE_FLAIR") or "stars" -USE_ION = os.getenv("GTFF_USE_ION") or True -USE_PROMPT_TOOLKIT = os.getenv("GTFF_USE_PROMPT_TOOLKIT") or False +USE_ION = strtobool(os.getenv("GTFF_USE_ION", "True")) +USE_PROMPT_TOOLKIT = strtobool(os.getenv("GTFF_USE_PROMPT_TOOLKIT", "False")) # Enable Prediction features -ENABLE_PREDICT = os.getenv("GTFF_ENABLE_PREDICT") or False +ENABLE_PREDICT = strtobool(os.getenv("GTFF_ENABLE_PREDICT", "False")) # Enable plot autoscaling -USE_PLOT_AUTOSCALING = os.getenv("GTFF_USE_PLOT_AUTOSCALING") or False +USE_PLOT_AUTOSCALING = strtobool(os.getenv("GTFF_USE_PLOT_AUTOSCALING", "False")) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index f83dd224180..2444b02d122 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -53,6 +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 ") if s_ticker: print( diff --git a/gamestonk_terminal/menu.py b/gamestonk_terminal/menu.py index 8fb14e0e4a3..00e818a0f0d 100644 --- a/gamestonk_terminal/menu.py +++ b/gamestonk_terminal/menu.py @@ -1,11 +1,25 @@ import os +from matplotlib import pyplot + from prompt_toolkit import PromptSession +from prompt_toolkit.eventloop.inputhook import set_eventloop_with_inputhook from prompt_toolkit.history import FileHistory + +def inputhook(inputhook_context): + while not inputhook_context.input_is_ready(): + pyplot.pause(0.1) + return False + + history_file = os.path.join(os.path.expanduser("~"), ".gamestonk_terminal.his") try: session = PromptSession(history=FileHistory(history_file)) + set_eventloop_with_inputhook(inputhook) except Exception as e: # noqa: F841 + print( + "WARNING: Prompt toolkit is turned on but did not initialize successfully. Falling back to input()..." + ) session = None diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md new file mode 100644 index 00000000000..de0511d4e42 --- /dev/null +++ b/gamestonk_terminal/portfolio/README.md @@ -0,0 +1,49 @@ +# PORTFOLIO + +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. + + +Once this screen is accessed, the first command to be run is +```` +login +```` +Which will use your login information, which 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. + +[ROBINHOOD](#ROBINHOOD) +* [hold](#hold) + * Look at current (stock only) holdings +* [rhhist](#rhhist) + * Get and plot historical portfolio + +## ROBINHOOD + +### hold + +Displays current holdings to the console: + +```` +usage: hold +```` +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: + +![hold](https://user-images.githubusercontent.com/18151143/111685384-3c6ab080-87fe-11eb-80ce-9b256c396bf2.png) + +### rhhist +Display your RH portfolio based on provided interval and span. Based on the API data availablility, plotted as a candlestick chart. +```` +usage: rhhist [-s --span] [-i --interval] +```` +* -s/--span : How long to look back. Defaults to the day. Options: + * [day, week, month, 3month, year, 5year, all] +* -i/--interval: Data resolution. Defaults to 10minute intervals. Options: + * [5minute, 10minute, hour, day, week] + +Example Default Output: + +![rhhist](https://user-images.githubusercontent.com/18151143/111718919-36da8e00-8831-11eb-99e1-957c8eccb583.png) \ No newline at end of file diff --git a/gamestonk_terminal/portfolio/__init__.py b/gamestonk_terminal/portfolio/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/portfolio/port_menu.py b/gamestonk_terminal/portfolio/port_menu.py new file mode 100644 index 00000000000..df46626a2c6 --- /dev/null +++ b/gamestonk_terminal/portfolio/port_menu.py @@ -0,0 +1,100 @@ +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/gamestonk_terminal/portfolio/rh_api.py b/gamestonk_terminal/portfolio/rh_api.py new file mode 100644 index 00000000000..a49f35e437d --- /dev/null +++ b/gamestonk_terminal/portfolio/rh_api.py @@ -0,0 +1,164 @@ +import argparse +import datetime +from datetime import datetime as dt +import pandas as pd +from pandas.plotting import register_matplotlib_converters +import matplotlib.pyplot as plt +import mplfinance as mpf +import numpy as np + +from robin_stocks import robinhood +from termcolor import colored +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, +) +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.config_terminal import RH_USERNAME as user, RH_PASSWORD as pw + +register_matplotlib_converters() + +dt_format = "%Y-%m-%dT%H:%M:%SZ" +valid_span = ["day", "week", "month", "3month", "year", "5year", "all"] +valid_interval = ["5minute", "10minute", "hour", "day", "week"] + +span_title_dict = { + "day": "Day", + "week": "Week", + "month": "Month", + "3month": "3 Months", + "year": "Year", + "5year": "5 Years", + "all": "All Time", +} + + +def login(): + robinhood.login(user, pw) + + +def logoff(): + robinhood.logout() + + +def show_holdings(): + + holds = robinhood.account.build_holdings() + stocks = [] + equity = [] + for stock, data in holds.items(): + stocks.append(stock) + equity.append(round(float(data["equity"]), 2)) + + print("") + print("Stonk\t last price \t prev close \t equity \t % Change") + print("") + for stonk, eq in zip(stocks, equity): + stonk_data = robinhood.stocks.get_quotes(stonk)[0] + prev_close = round(float(stonk_data["adjusted_previous_close"]), 2) + last_price = round(float(stonk_data["last_trade_price"]), 2) + pct_change = round((last_price - prev_close) / prev_close, 3) + to_print = f"{stonk}\t {last_price}\t\t {prev_close}\t\t {eq}\t\t {pct_change}" + + if last_price >= prev_close: + print(colored(to_print, "green")) + else: + print(colored(to_print, "red")) + + print("") + + +def plot_historical(l_args): + parser = argparse.ArgumentParser( + add_help=False, + prog="Port", + description=""" + Historical Portfolio Info + """, + ) + + parser.add_argument( + "-s", + "--span", + dest="span", + type=str, + default="day", + help="Span of historical data", + ) + + parser.add_argument( + "-i", + "--interval", + dest="interval", + default="10minute", + type=str, + help="Interval to look at portfolio", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, l_args) + + if not ns_parser: + return + + if ns_parser.interval not in valid_interval: + raise ValueError( + "Invalid Interval: Must be one of: 5minute, 10minute, hour, day, week" + ) + if ns_parser.span not in valid_span: + raise ValueError( + "Invalid Span: Must be one of : day, week, month, 3month, year, 5year, all" + ) + + rhhist = robinhood.account.get_historical_portfolio( + ns_parser.interval, span=ns_parser.span + ) + rhhist_eq = rhhist["equity_historicals"] + open_eq = [] + close_eq = [] + time = [] + + for h in rhhist_eq: + time.append( + dt.strptime(h["begins_at"], dt_format) - datetime.timedelta(hours=4) + ) + close_eq.append(float(h["adjusted_close_equity"])) + open_eq.append(float(h["adjusted_open_equity"])) + + close_eq = np.asarray(close_eq) + open_eq = np.asarray(open_eq) + high = np.maximum(open_eq, close_eq) + low = np.minimum(open_eq, close_eq) + + df = pd.DataFrame(index=time) + df["High"] = high + df["Low"] = low + df["Open"] = open_eq + df["Close"] = close_eq + + mc = mpf.make_marketcolors( + up="green", down="red", edge="black", wick="black", ohlc="i" + ) + s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=False) + + mpf.plot( + df, + type="candle", + style=s, + title=f"Portfolio for {span_title_dict[ns_parser.span]}", + ylabel="Equity ($)", + figsize=(plot_autoscale()), + update_width_config=dict( + candle_linewidth=1.0, + candle_width=0.8, + ), + ) + if gtff.USE_ION: + plt.ion() + + print("") + + except Exception as e: + print(e) + print("") + return diff --git a/gamestonk_terminal/thought_of_the_day.py b/gamestonk_terminal/thought_of_the_day.py new file mode 100644 index 00000000000..0afa152c7a2 --- /dev/null +++ b/gamestonk_terminal/thought_of_the_day.py @@ -0,0 +1,130 @@ +""" Fundamental Analysis Market Watch API """ +__docformat__ = "numpy" + +import re +import random +import requests +from bs4 import BeautifulSoup + +from gamestonk_terminal.helper_funcs import get_user_agent + + +class ThoughtOfTheDay: + """ ThoughtOfTheDay class """ + + def __init__(self): + """ Constructor """ + self.urls = { + "Marcus_Aurelius": "https://www.goodreads.com/author/quotes/17212.Marcus_Aurelius", + "Epictetus": "https://www.goodreads.com/author/quotes/13852.Epictetus", + "Seneca": "https://www.goodreads.com/author/quotes/4918776.Seneca", + "Marcus_Tullius_Cicero": "https://www.goodreads.com/author/quotes/13755.Marcus_Tullius_Cicero", + "Aristotle": "https://www.goodreads.com/author/quotes/2192.Aristotle", + "Plato": "https://www.goodreads.com/author/quotes/879.Plato", + "Pythagoras": "https://www.goodreads.com/author/quotes/203707.Pythagoras", + } + + self.metadata = {} + + def get_urls(self) -> dict: + """ Getter method for URLs """ + return self.urls + + def get_metadata(self, author: str) -> dict: + """Loads metadata for a given author + + Parameters + ---------- + author : str + Author key - Marcus_Aurelius, Epictetus, Seneca, Marcus_Tullius_Cicero, Aristotle, Plato, Pythagoras + + Returns + ------- + dict + Metadata dictionary that includes number of quotes, number of pages and first 30 quotes + """ + quotes_page = BeautifulSoup( + requests.get( + self.urls[author], + headers={"User-Agent": get_user_agent()}, + ).text, + "lxml", + ) + + find_navigation = quotes_page.find("em", {"class": "current"}).find_parent( + "div" + ) + + page_count = list() + for a_page_ref in find_navigation.find_all("a", href=True): + page_count.append(a_page_ref.text.strip("\n")) + + ret = {} + ret["pages"] = page_count[-2] + + find_count = quotes_page.find(string=re.compile("Showing 1-30 of")) + + quote_count = re.search(r"Showing 1-30 of (?P[\d,]+)", find_count) + + if quote_count: + ret["quoutes"] = quote_count.group("number") + + all_quotes = quotes_page.find_all("div", {"class": "quote"}) + + ret["quotes"] = [] + + for a_quote in all_quotes: + parsed_quote = {} + + parsed_quote = a_quote.find("div", {"class": "quoteText"}).text + + ret["quotes"].append(parsed_quote) + + return ret + + def quote_to_str(self, a_quote: str) -> str: + """Format a quote parsed from Goodreads into a string + + Parameters + ---------- + a_quote : str + A quote formatted by Goodreads + + Returns + ------- + str + A string version of the quote + """ + ready = [] + prev = None + for a_line in a_quote: + if not prev: + ready.append(a_line) + prev = a_line + continue + + if a_line != "\n": + ready.append(a_line) + prev = a_line + continue + + if prev == " \n": + ready.pop() + prev = None + + return "".join(map(str, ready)) + + +def get_thought_of_the_day(): + """Pick a thought of the day""" + totd = ThoughtOfTheDay() + + quotes = [] + for an_author in totd.get_urls(): + metadata = totd.get_metadata(an_author) + quotes = quotes + metadata["quotes"] + + print("Thought of the day:") + print(totd.quote_to_str(quotes[random.randint(0, len(quotes) - 1)])) + + print("") diff --git a/poetry.lock b/poetry.lock index e4c01cf7575..774eb622b9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -29,6 +29,23 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] +[[package]] +name = "alpaca-trade-api" +version = "1.1.0" +description = "Alpaca API python client" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +msgpack = "1.0.2" +numpy = "*" +pandas = "*" +requests = ">2,<3" +urllib3 = ">1.24,<2" +websocket-client = ">=0.56.0,<1" +websockets = ">=8.0,<9" + [[package]] name = "alpha-vantage" version = "2.3.1" @@ -323,6 +340,25 @@ pytz = ">=2014.10" docs = ["sphinx", "sphinx-rtd-theme"] test = ["coverage"] +[[package]] +name = "cryptography" +version = "3.4.6" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + [[package]] name = "cssselect" version = "1.1.0" @@ -1182,6 +1218,14 @@ python-versions = "*" matplotlib = "*" pandas = "*" +[[package]] +name = "msgpack" +version = "1.0.2" +description = "MessagePack (de)serializer." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "multidict" version = "5.1.0" @@ -1770,6 +1814,14 @@ python-versions = ">=3.6" [package.dependencies] pyobjc-core = ">=7.1" +[[package]] +name = "pyotp" +version = "2.6.0" +description = "Python One Time Password Library" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyparsing" version = "2.4.7" @@ -1842,6 +1894,17 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "0.15.0" +description = "Add .env support to your django/flask apps in development and deployments" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pytrends" version = "4.7.3" @@ -2007,6 +2070,20 @@ python-versions = "*" [package.dependencies] six = ">=1.7.0" +[[package]] +name = "robin-stocks" +version = "2.0.3" +description = "A Python wrapper around the Robinhood API" +category = "main" +optional = false +python-versions = ">=3" + +[package.dependencies] +cryptography = "*" +pyotp = "*" +python-dotenv = "*" +requests = "*" + [[package]] name = "rsa" version = "4.7.2" @@ -2280,7 +2357,7 @@ name = "termcolor" version = "1.1.0" description = "ANSII Color formatting for output in terminal." category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -2532,6 +2609,14 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" +[[package]] +name = "websockets" +version = "8.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.6.1" + [[package]] name = "werkzeug" version = "1.0.1" @@ -2609,7 +2694,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t [metadata] lock-version = "1.1" python-versions = "^3.6.8" -content-hash = "8b892e0dc807e52b9f8ece6c5bb60b1e5881e66c72426fe1ffe2faee2930cf87" +content-hash = "6050ce94f3ec0988ef02b85642d7c33af3eb4622130bb303c3d44dbb471be5cb" [metadata.files] absl-py = [ @@ -2655,6 +2740,10 @@ aiohttp = [ {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, ] +alpaca-trade-api = [ + {file = "alpaca-trade-api-1.1.0.tar.gz", hash = "sha256:751e06db061cf69aeaf6e21e7cce1baee652cc222a4c00c9f3c2074f7711f757"}, + {file = "alpaca_trade_api-1.1.0-py3-none-any.whl", hash = "sha256:098cde3d3ce70a000c643131e6750f372184db79998e91bf9c5ed787a0da3b13"}, +] alpha-vantage = [ {file = "alpha_vantage-2.3.1-py3-none-any.whl", hash = "sha256:5d05355febe6f0fafc4bd11dc6ce3504a02d9d3c105d60202539855c0f608661"}, {file = "alpha_vantage-2.3.1.tar.gz", hash = "sha256:0ce76908c3e2a22f9bbdacead90195ec3a4fa41ef8ae7c69a4a2fc99459bfbec"}, @@ -2809,6 +2898,20 @@ convertdate = [ {file = "convertdate-2.3.2-py3-none-any.whl", hash = "sha256:8f5e9efc2b71410d33217012c0a215f83463f765c94d5883bf656ce7a962b888"}, {file = "convertdate-2.3.2.tar.gz", hash = "sha256:57df962a6047534f1452c390ad48e3dc5c83052ad83ad6dd78d60ae22f99214c"}, ] +cryptography = [ + {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, + {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, + {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, + {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, + {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, + {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, + {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, + {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, + {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, +] cssselect = [ {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, @@ -3376,6 +3479,36 @@ mplfinance = [ {file = "mplfinance-0.12.7a10-py3-none-any.whl", hash = "sha256:aa4d9759a1befadfed190f9fc72ac8389d80fb61bc6f8ce6693d9c2627e88021"}, {file = "mplfinance-0.12.7a10.tar.gz", hash = "sha256:f3745ad29ec834cd5505aa510f401f2c44b44328f92c781d093b3bb597641545"}, ] +msgpack = [ + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, + {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, + {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, + {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, + {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, + {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, + {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, + {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, + {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, + {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, + {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, + {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, + {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, + {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, +] multidict = [ {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, @@ -3733,6 +3866,10 @@ pyobjc-framework-cocoa = [ {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f68f022f1f6d5985c418e10c6608c562fcf4bfe3714ec64fd10ce3dc6221bd4"}, {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ecfefd4c48dae42275c18679c69f6f2fff970e711097515a0a8732fc10194018"}, ] +pyotp = [ + {file = "pyotp-2.6.0-py2.py3-none-any.whl", hash = "sha256:9d144de0f8a601d6869abe1409f4a3f75f097c37b50a36a3bf165810a6e23f28"}, + {file = "pyotp-2.6.0.tar.gz", hash = "sha256:d28ddfd40e0c1b6a6b9da961c7d47a10261fb58f378cb00f05ce88b26df9c432"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -3775,6 +3912,10 @@ python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] +python-dotenv = [ + {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, + {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, +] pytrends = [ {file = "pytrends-4.7.3-py3-none-any.whl", hash = "sha256:88d92e398b9408c79fd92946ddc2b03fa622301233bc49f16804042eb2897328"}, {file = "pytrends-4.7.3.tar.gz", hash = "sha256:8ccb06c57c31fa157b978a0d810de7718ee46583d28cf818250d45f36abd2faa"}, @@ -4002,6 +4143,9 @@ requests-oauthlib = [ retrying = [ {file = "retrying-1.3.3.tar.gz", hash = "sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b"}, ] +robin-stocks = [ + {file = "robin_stocks-2.0.3.tar.gz", hash = "sha256:c7300a7721748a1776bc9160c6dc6aea1801b8397e3900609378dc40d884e943"}, +] rsa = [ {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, {file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"}, @@ -4348,6 +4492,30 @@ websocket-client = [ {file = "websocket_client-0.58.0-py2.py3-none-any.whl", hash = "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663"}, {file = "websocket_client-0.58.0.tar.gz", hash = "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"}, ] +websockets = [ + {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, + {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, + {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, + {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, + {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, + {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, + {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, + {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, + {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, + {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, +] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, diff --git a/pyproject.toml b/pyproject.toml index 20a292ec5cf..7d5e275a594 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,4 @@ +# https://python-poetry.org/docs/dependency-specification/ [tool.poetry] name = "GamestonkTerminal" version = "0.1.0" @@ -6,7 +7,7 @@ authors = ["Didier Rodrigues Lopes"] [tool.poetry.dependencies] python = "^3.6.8" -pandas = "^1.1.5" +pandas = "^1.1.5,<1.2" iso8601 = "^0.1.14" FundamentalAnalysis = "^0.2.6" requests = "^2.25.1" @@ -37,13 +38,16 @@ prompt-toolkit = "^3.0.16" jupyter = "^1.0.0" mplfinance = "^0.12.7-alpha.7" seaborn = "^0.11.0" -scipy = "1.5.4" +scipy = "~1.5.4" fredapi = "^0.4.3" screeninfo = "^0.6.7" -numpy = "~1.19.5" +numpy = "~1.19.5,<1.20" pyrsistent = "~0.14.11" regex = "~2020.11.13" h5py = "~2.10.0" +robin-stocks = "^2.0.3" +termcolor = "^1.1.0" +alpaca-trade-api = "^1.0.1" [tool.poetry.dev-dependencies] pytest = "^6.2.2" @@ -57,3 +61,13 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +# Notes for developers: +# 1. numpy >= 1.20.0 sometimes has issues with tensorflow=2.4.1 +# 2. The following packages dropped support for python<3.7: +# pandas >= 1.2.0 +# numpy >= 1.20.0 +# scipy >= 1.6.0 +# 3. The following often cause poetry to misbehave: +# pyrsistent >= 0.17 +# regex >= 2021 diff --git a/terminal.py b/terminal.py index e5faa38c273..8197fc0612a 100644 --- a/terminal.py +++ b/terminal.py @@ -11,6 +11,7 @@ from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal import thought_of_the_day as thought from gamestonk_terminal import res_menu as rm from gamestonk_terminal.discovery import disc_menu as dm from gamestonk_terminal.due_diligence import dd_menu as ddm @@ -24,6 +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 # import warnings # warnings.simplefilter("always") @@ -75,6 +77,7 @@ def main(): "ca", "op", "fred", + "port", ] menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -84,6 +87,13 @@ def main(): should_print_help = True parsed_stdin = False + print("-------------------") + try: + thought.get_thought_of_the_day() + except Exception as e: + print(e) + print("") + # Loop forever and ever while True: main_cmd = False @@ -193,6 +203,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 == "pred": if not gtff.ENABLE_PREDICT: