diff --git a/README.md b/README.md index 95eefd9a008..bececc71b51 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,7 @@ Distributed under the MIT License. See [LICENSE](https://github.com/DidierRLopes [Artem Veremy](https://www.linkedin.com/in/veremey/) - artem@veremey.net +[James Maslek](https://www.linkedin.com/in/james-maslek-b6810186/) - jmaslek11@gmail.com ## Acknowledgments * [VICE article](https://www.vice.com/en/article/qjp9vp/gamestonk-terminal-is-a-diy-meme-stock-version-of-bloomberg-terminal) diff --git a/ROADMAP.md b/ROADMAP.md index 4c290755403..7a46642a5f2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -202,7 +202,7 @@ ### Short term -- [ ] Add Coingecko +- [x] Add Coingecko (@jmaslek) - [PR #283](#https://github.com/DidierRLopes/GamestonkTerminal/pulls) - [ ] Add [Coinpaprika](https://coinpaprika.com/api/) - [ ] Add crypto charts diff --git a/gamestonk_terminal/cryptocurrency/README.md b/gamestonk_terminal/cryptocurrency/README.md new file mode 100644 index 00000000000..fe328670d84 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/README.md @@ -0,0 +1,42 @@ +# Cryptocurrency + +This page gives an overview of the ability to load cryptocurrency data. + +Current crypto data is [Powered by CoinGecko API](#https://www.coingecko.com/en). +This is an awesome service that currently requires no API Key! + +Current functionality is aimed to be similar to loading a stock ticker. + +* [load](#load) +* [view](#view) +* [clear](#clear) + +## load +Load a given coin vs a given currency: + +```` +usage: load [-c --coin] [-d --days] [--vs] +```` +* -c/--coin The coin you wish to load. This can either be the symbol or the name. `load -c btc` and `load -c bitcoin` + will load. The -c flag is optional, the above is equivalent to `load btc`. + +* -d/--days The number of days to look. Defaults to 30 days. As per the API: Minutely data will be used for duration within 1 day, + Hourly data will be used for duration between 1 day and 90 days, Daily data will be used for duration above 90 days. + +* --vs The currency to look against. Defaults to "usd". + +Currently only retrieves price, not volume or MarketCap. + +Example doge/eur for the past day: +```` +load -c doge -d 1 --vs eur +```` + +## view +Plot the loaded crypto data. +```` +usage: view +```` +After loading the above 1 day dogecoin/eur. Running view plots the data: + +![doge](https://user-images.githubusercontent.com/18151143/112690617-7832f500-8e52-11eb-84c4-253ab222a918.png) diff --git a/gamestonk_terminal/cryptocurrency/__init__.py b/gamestonk_terminal/cryptocurrency/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/cryptocurrency/coin_api.py b/gamestonk_terminal/cryptocurrency/coin_api.py new file mode 100644 index 00000000000..3049c28163c --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/coin_api.py @@ -0,0 +1,115 @@ +import argparse +import pandas as pd +from pandas.plotting import register_matplotlib_converters +import matplotlib.pyplot as plt +from pycoingecko import CoinGeckoAPI +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale +from gamestonk_terminal.cryptocurrency.crypto_helper import coin_symbol_to_id, coin_ids +from gamestonk_terminal.config_plot import PLOT_DPI + +register_matplotlib_converters() +# pylint: disable=inconsistent-return-statements + + +def load(l_args): + + cg = CoinGeckoAPI() + parser = argparse.ArgumentParser( + add_help=False, + prog="Crypto", + description=""" + Cryptocurrencies + """, + ) + + parser.add_argument( + "-c", + "--coin", + required=True, + type=str, + dest="coin", + help="Coin to load data for", + ) + + parser.add_argument( + "-d", "--days", default=30, dest="days", help="Number of days to get data for" + ) + + parser.add_argument( + "--vs", default="usd", dest="vs", help="Currency to display vs coin" + ) + + try: + if l_args: + if "-" not in l_args[0]: + l_args.insert(0, "-c") + + ns_parser = parse_known_args_and_warn(parser, l_args) + + if not ns_parser: + return + + if ns_parser.coin in coin_ids: + coin = ns_parser.coin + else: + try: + coin = coin_symbol_to_id[ns_parser.coin] + except KeyError: + print(f"Could not find coin with the id: {ns_parser.coin}") + print("") + return [None, pd.DataFrame] + + prices = cg.get_coin_market_chart_by_id( + coin, vs_currency=ns_parser.vs, days=ns_parser.days + ) + prices = prices["prices"] + prices = pd.DataFrame(data=prices, columns=["Time", "Price"]) + prices["Time"] = pd.to_datetime(prices.Time, unit="ms") + prices = prices.set_index("Time") + prices["currency"] = ns_parser.vs + print(f"{coin}/{ns_parser.vs} loaded") + print("") + return [coin, prices] + + except SystemExit: + print("") + return [None, pd.DataFrame()] + + except Exception as e: + print(e) + print("") + return [None, pd.DataFrame()] + + +def view(coin, prices, l_args): + + parser = argparse.ArgumentParser( + add_help=False, + prog="Crypto", + description=""" + Cryptocurrencies + """, + ) + try: + ns_parser = parse_known_args_and_warn(parser, l_args) + + if not ns_parser: + return + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + plt.plot(prices.index, prices.Price, "-ok", ms=2) + plt.xlabel("Time") + plt.xlim(prices.index[0], prices.index[-1]) + plt.ylabel("Price") + 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.title(f"{coin}/{prices['currency'][0]}") + plt.show() + print("") + + except SystemExit: + print("") + + except Exception as e: + print(e) + print("") diff --git a/gamestonk_terminal/cryptocurrency/crypto_controller.py b/gamestonk_terminal/cryptocurrency/crypto_controller.py new file mode 100644 index 00000000000..e494136cea3 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/crypto_controller.py @@ -0,0 +1,103 @@ +__docformat__ = "numpy" + +import argparse +import pandas as pd +import matplotlib.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.cryptocurrency import coin_api + + +class CryptoController: + + CHOICES = ["help", "q", "quit", "load", "view"] + + def __init__(self): + """ CONSTRUCTOR """ + + self.crypto_parser = argparse.ArgumentParser(add_help=False, prog="crypto") + self.crypto_parser.add_argument("cmd", choices=self.CHOICES) + self.current_coin = None + self.current_df = pd.DataFrame() + + @staticmethod + def print_help(current_coin): + """ Print help """ + print("\nCryptocurrency:") + 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 Coin : {current_coin}") + print("") + print(" load load cryptocurrency data") + print(" view load and view cryptocurrency data") + 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.crypto_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.current_coin) + + 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_load(self, other_args): + self.current_coin, self.current_df = coin_api.load(other_args) + + def call_view(self, other_args): + if self.current_coin: + coin_api.view(self.current_coin, self.current_df, other_args) + + else: + print("No coin selected. Use 'load' to load the coin you want to look at.") + print("") + + +def menu(): + crypto_controller = CryptoController() + crypto_controller.print_help(crypto_controller.current_coin) + plt.close("all") + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in crypto_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (crypto)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (crypto)> ") + + try: + process_input = crypto_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/cryptocurrency/crypto_helper.py b/gamestonk_terminal/cryptocurrency/crypto_helper.py new file mode 100644 index 00000000000..171b6b9b525 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/crypto_helper.py @@ -0,0 +1,11 @@ +from pycoingecko import CoinGeckoAPI + + +cg = CoinGeckoAPI() +coins = cg.get_coins() +coin_symbol_to_id = {} +coin_ids = [] + +for coin in coins: + coin_symbol_to_id[coin["symbol"]] = coin["id"] + coin_ids.append(coin["id"]) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 7bff6997eb6..c5d95f067f3 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -54,6 +54,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): " fred economic data, \t\t\t from: Federal Reserve Bank of St. Louis " ) print(" pa portfolio analysis, \t\t Currently Supported: Robinhood ") + print(" crypto cryptocurrencies, \t\t coingecko api") if s_ticker: print( diff --git a/poetry.lock b/poetry.lock index d58561f577b..cc79aeb62c3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1799,6 +1799,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycoingecko" +version = "1.4.0" +description = "Python wrapper around the CoinGecko API" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = "*" + [[package]] name = "pycparser" version = "2.20" @@ -2767,7 +2778,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t [metadata] lock-version = "1.1" python-versions = "^3.6.8" -content-hash = "ab13d87e3da8dcb61024c74340e528554cea71b5ccab8637e3c75ab88550fcd1" +content-hash = "b05035fef8aa3a5dacf6c4713579493f1104296ea25305049466fc47e0c35401" [metadata.files] absl-py = [ @@ -3940,6 +3951,10 @@ pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] +pycoingecko = [ + {file = "pycoingecko-1.4.0-py3-none-any.whl", hash = "sha256:bc6e2d87a59bda88bd01a241b37d49c30104842534fdf65ba96616c90419d756"}, + {file = "pycoingecko-1.4.0.tar.gz", hash = "sha256:082949e83b49ab89090d19b88b6177c9458679dc4a98323db950bd4cef7afa2e"}, +] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, diff --git a/pyproject.toml b/pyproject.toml index fbea026c225..f4c3f068266 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ robin-stocks = "^2.0.3" termcolor = "^1.1.0" alpaca-trade-api = "^1.0.1" pyally = "^1.1.2" +pycoingecko = "^1.4.0" [tool.poetry.dev-dependencies] pytest = "^6.2.2" diff --git a/requirements-full.txt b/requirements-full.txt index 0b3e88934d5..24784254cfe 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -126,6 +126,7 @@ py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implem pyally==1.1.2 pyasn1-modules==0.2.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.6.0" +pycoingecko==1.4.0 pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" pygments==2.8.1; python_version >= "3.6" pymeeus==0.5.9; python_version >= "3.5" diff --git a/requirements-old.txt b/requirements-old.txt index 8fb9b3235f1..6aaede2a75a 100644 --- a/requirements-old.txt +++ b/requirements-old.txt @@ -86,6 +86,7 @@ psaw==0.0.12; python_version >= "3" pyally==1.1.2 pyasn1-modules==0.2.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.6.0" +pycoingecko==1.4.0 pymeeus==0.4.3; python_version >= "3.5" pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" diff --git a/requirements-win.txt b/requirements-win.txt index a3c055a89be..7cebb528db2 100644 --- a/requirements-win.txt +++ b/requirements-win.txt @@ -87,6 +87,7 @@ psaw==0.0.12; python_version >= "3" pyally==1.1.2 pyasn1-modules==0.2.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.6.0" +pycoingecko==1.4.0 pymeeus==0.4.3; python_version >= "3.5" pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" diff --git a/requirements.txt b/requirements.txt index 0880e096d5a..919b6242f57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -85,6 +85,7 @@ psaw==0.0.12; python_version >= "3" ptyprocess==0.7.0; os_name != "nt" and python_version >= "3.6" and sys_platform != "win32" py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or implementation_name == "pypy" and python_version >= "3.6" and python_full_version >= "3.4.0" pyally==1.1.2 +pycoingecko==1.4.0 pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" pygments==2.8.1; python_version >= "3.6" pymeeus==0.5.9; python_version >= "3.5" diff --git a/terminal.py b/terminal.py index 4199658323c..e9e9665eabc 100644 --- a/terminal.py +++ b/terminal.py @@ -26,6 +26,7 @@ from gamestonk_terminal.comparison_analysis import ca_controller from gamestonk_terminal.options import op_controller from gamestonk_terminal.fred import fred_menu as fm from gamestonk_terminal.portfolio import port_controller +from gamestonk_terminal.cryptocurrency import crypto_controller # import warnings # warnings.simplefilter("always") @@ -79,6 +80,7 @@ def main(): "op", "fred", "pa", + "crypto", ] menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -209,6 +211,9 @@ def main(): elif ns_known_args.opt == "pa": b_quit = port_controller.menu() + elif ns_known_args.opt == "crypto": + b_quit = crypto_controller.menu() + elif ns_known_args.opt == "pred": if not gtff.ENABLE_PREDICT: