From 8b3c396d83938aa82afbf78fb6620fe0f56239a7 Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 20 May 2021 14:39:47 -0400 Subject: [PATCH] Add option to go to ta menu from crypto --- ROADMAP.md | 6 +- gamestonk_terminal/README.md | 5 +- gamestonk_terminal/cryptocurrency/README.md | 32 ++-- .../cryptocurrency/binance_model.py | 174 ++++++++---------- .../cryptocurrency/crypto_controller.py | 63 ++++++- 5 files changed, 152 insertions(+), 128 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index ef9ad45c8ac..5b4a890b4b2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -101,9 +101,10 @@ * [x] Add several exponential smoothing techniques (@didier) - [PR #132](https://github.com/DidierRLopes/GamestonkTerminal/pull/132) * [x] Allow backtesting (@didier) - [PR #169](https://github.com/DidierRLopes/GamestonkTerminal/pull/169) * [x] Add averaging around multiple predictions (@didier) - [PR #252](https://github.com/DidierRLopes/GamestonkTerminal/pull/252) +* [x] CNN 1D (@jmaslek) - [PR #436](https://github.com/GamestonkTerminal/GamestonkTerminal/pull/436) + **NEXT** -* [ ] CNN 1D (@didier) * [ ] Combine Sentiment Analysis with Chart data - [IS #240](https://github.com/DidierRLopes/GamestonkTerminal/issues/240) * [ ] Cross-Validation methods (Forward Chaining, K-Fold, Group K-Fold) (@didier) @@ -204,10 +205,11 @@ ___ * [x] Add Coingecko (@jmaslek) - [PR #283](https://github.com/DidierRLopes/GamestonkTerminal/pull/283) * [x] view top coins from coinmarketcap (@jmaslek) - [PR #378](https://github.com/DidierRLopes/GamestonkTerminal/pull/378) * [x] Add binance (@jmaslek) [PR #428](#https://github.com/DidierRLopes/GamestonkTerminal/pull/428) +* [x] Add crypto ta (@jmaslek) + **NEXT** * [ ] Add [Coinpaprika](https://coinpaprika.com/api/) -* [ ] Add crypto charts * [ ] Add top altcoin lists --- diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index c67bd74ee1c..7a16eb56def 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -297,9 +297,9 @@ Command|Description [coinmarketcap](#http://coinmarketcap.com)| `top` | view top coins from coinmarketcap [binance](#http://binance.us)| -`select` | Select a coin/currency +`select` | select coin/currency to use and load candle data `book`| show order book -`candle`| get klines/candles and plot +`candle`| show candles `balance`| show coin balance   @@ -469,4 +469,3 @@ with ticker provided | `house` | house trades on the ticker   - diff --git a/gamestonk_terminal/cryptocurrency/README.md b/gamestonk_terminal/cryptocurrency/README.md index da8832da854..e92f658f783 100644 --- a/gamestonk_terminal/cryptocurrency/README.md +++ b/gamestonk_terminal/cryptocurrency/README.md @@ -2,6 +2,9 @@ This menu aims to explore crypto world, and the usage of the following commands along with an example will be exploited below. +Note that we have added the ability to look at technical analysis in the ta menu. Data loaded from CoinGecko has no candle data, +so indicators that rely on anything other than close will fail with an error. + [COINGECKO](#COINGECKO) * [load](#load) * load a given coin vs a given currency [CoinGecko] @@ -9,10 +12,10 @@ This menu aims to explore crypto world, and the usage of the following commands * plot the loaded crypto data * [trend](#trend) * show top 7 trending coins - + [COINMARKETCAP](#COINMARKETCAP) * [top](#top) - * view top coins from coinmarketcap [coinmarketcap.com] + * view top coins from coinmarketcap [coinmarketcap.com] [BINANCE](#BINANCE) * [select](#select) @@ -24,6 +27,10 @@ This menu aims to explore crypto world, and the usage of the following commands * [balance](#balance) * show coin balance +[MENUS](#Menu) +* [> ta](technical_analysis/README.md) + * **technical analysis for selected tickers** + # COINGECKO ## load @@ -31,11 +38,11 @@ This menu aims to explore crypto world, and the usage of the following commands usage: load [-c --coin] [-d --days] [--vs] ```` -Load a given coin vs a given currency. Currently only retrieves price, not volume or MarketCap. The current crypto data is [Powered by CoinGecko API](#https://www.coingecko.com/en), which is an awesome service that currently requires no API Key! +Load a given coin vs a given currency. Currently only retrieves price, not volume or MarketCap. The current crypto data is [Powered by CoinGecko API](#https://www.coingecko.com/en), which is an awesome service that currently requires no API Key! -* -c/--coin The coin you wish to load. This can either be the symbol or the name. `load -c btc` and `load -c bitcoin` +* -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, +* -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". @@ -80,17 +87,18 @@ This command displays the top n cryptocurrencies from coinmarketcap.com. ## select ```` -usage: select [-c --coin] [-q --quote] +usage: select [-c --coin] [-q --quote] [-i --interval] [-l --limit] ```` -Select a coin/currency to the current object. Note that in binance, the exchange "ticker" is usually COINCURR, such as BTCEUR for BTC to EURO. There is no USD, but it uses a coin tethered to the USD (USDT), which is the default. Some symbols are a combination of coins, as `ETHBTC` is a valid symbol +Select a coin/currency to the current object and load in the price dataframe. Note that in binance, the exchange "ticker" is usually COINCURR, such as BTCEUR for BTC to EURO. There is no USD, but it uses a coin tethered to the USD (USDT), which is the default. Some symbols are a combination of coins, as `ETHBTC` is a valid symbol * -c/--coin Coin to load. If not specified, BTC will be loaded to prevent errors later. * -q/--quote Quote currency. Defaults to `USDT` (which is 1-to-1 with USD) - +* -i/--interval. Interval for candles. One of [1day,3day,1hour,2hour,4hour,6hour,8hour,12hour,1week,1min,3min,5min,15min,30min,1month]. Defaults to 1day. +* -l/--limit. Number of candles to get. Defaults to 100. ## book ```` -usage: book -l/--limit +usage: book -l/--limit ```` Gets and shows the order book for the given coin/currency. Shows the cumulative amount of orders. @@ -102,13 +110,11 @@ Gets and shows the order book for the given coin/currency. Shows the cumulative ## candle ```` -usage: candle [-i --interval] [-l --limit] +usage: candle ```` Show candle chart for loaded coin/currency. -* -i/--interval. Interval for candles. One of [1day,3day,1hour,2hour,4hour,6hour,8hour,12hour,1week,1min,3min,5min,15min,30min,1month]. Defaults to 1day. -* -l/--limit. Number of candles to get. Defaults to 100. ![candle](https://user-images.githubusercontent.com/25267873/116886993-abbb2900-ac21-11eb-9ff8-b6a8131fdac5.png) @@ -119,5 +125,3 @@ usage: balance ```` Shows the current holding balance in your account. - - diff --git a/gamestonk_terminal/cryptocurrency/binance_model.py b/gamestonk_terminal/cryptocurrency/binance_model.py index 7bfad6acd6f..2e183ab1d45 100644 --- a/gamestonk_terminal/cryptocurrency/binance_model.py +++ b/gamestonk_terminal/cryptocurrency/binance_model.py @@ -38,11 +38,14 @@ def select_binance_coin(other_args: List[str]): ------- coin: str Coin that is defined on binance + df_coin : pd.DataFrame + Dataframe of prices for selected coin """ + client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET) parser = argparse.ArgumentParser( prog="select", add_help=False, - description="Define the coin to be used from binance", + description="Define the coin to be used from binance and get data", ) parser.add_argument( "-c", "--coin", help="Coin to get", dest="coin", type=str, default="BTC" @@ -55,22 +58,76 @@ def select_binance_coin(other_args: List[str]): type=str, default="USDT", ) + interval_map = { + "1day": client.KLINE_INTERVAL_1DAY, + "3day": client.KLINE_INTERVAL_3DAY, + "1hour": client.KLINE_INTERVAL_1HOUR, + "2hour": client.KLINE_INTERVAL_2HOUR, + "4hour": client.KLINE_INTERVAL_4HOUR, + "6hour": client.KLINE_INTERVAL_6HOUR, + "8hour": client.KLINE_INTERVAL_8HOUR, + "12hour": client.KLINE_INTERVAL_12HOUR, + "1week": client.KLINE_INTERVAL_1WEEK, + "1min": client.KLINE_INTERVAL_1MINUTE, + "3min": client.KLINE_INTERVAL_3MINUTE, + "5min": client.KLINE_INTERVAL_5MINUTE, + "15min": client.KLINE_INTERVAL_15MINUTE, + "30min": client.KLINE_INTERVAL_30MINUTE, + "1month": client.KLINE_INTERVAL_1MONTH, + } + interval_choices = list(interval_map.keys()) + + parser.add_argument( + "-i", + "--interval", + help="Interval to get data", + choices=interval_choices, + dest="interval", + default="1day", + type=str, + ) + + parser.add_argument( + "-l", "--limit", dest="limit", default=100, help="Number to get", type=int + ) try: if other_args: if "-" not in other_args[0]: other_args.insert(0, "-c") ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: - return None, None + return None, None, pd.DataFrame() coin = ns_parser.coin + ns_parser.quote + if check_valid_binance_str(coin): print(f"{ns_parser.coin.upper()} loaded vs {ns_parser.quote.upper()}") - return ns_parser.coin.upper(), ns_parser.quote.upper() + + candles = client.get_klines( + symbol=coin.upper(), + interval=interval_map[ns_parser.interval], + limit=ns_parser.limit, + ) + candles_df = pd.DataFrame(candles).astype(float).iloc[:, :6] + + candles_df.columns = [ + "Time0", + "Open", + "High", + "Low", + "Close", + "Volume", + ] + df_coin = candles_df.set_index( + pd.to_datetime(candles_df["Time0"], unit="ms") + ).drop("Time0", axis=1) + + return ns_parser.coin.upper(), ns_parser.quote.upper(), df_coin except Exception as e: print(e, "\n") - return None, None + return None, None, pd.DataFrame def order_book(other_args: List[str], coin: str, currency: str): @@ -121,112 +178,25 @@ def order_book(other_args: List[str], coin: str, currency: str): print(e, "\n") -def show_candles(other_args: List[str], coin: str, currency: str): - """Get klines/candles for coin +def show_candles(candles_df: pd.DataFrame, coin: str, currency: str): + """Show candles Parameters ---------- - other_args: List[str] - Argparse arguments + candles_df: pd.DataFrame + Dataframe of prices coin: str - Coin to get symbol of + Coin loaded currency : str - Currency against which to check symbol + Currency loaded """ - client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET) - interval_map = { - "1day": client.KLINE_INTERVAL_1DAY, - "3day": client.KLINE_INTERVAL_3DAY, - "1hour": client.KLINE_INTERVAL_1HOUR, - "2hour": client.KLINE_INTERVAL_2HOUR, - "4hour": client.KLINE_INTERVAL_4HOUR, - "6hour": client.KLINE_INTERVAL_6HOUR, - "8hour": client.KLINE_INTERVAL_8HOUR, - "12hour": client.KLINE_INTERVAL_12HOUR, - "1week": client.KLINE_INTERVAL_1WEEK, - "1min": client.KLINE_INTERVAL_1MINUTE, - "3min": client.KLINE_INTERVAL_3MINUTE, - "5min": client.KLINE_INTERVAL_5MINUTE, - "15min": client.KLINE_INTERVAL_15MINUTE, - "30min": client.KLINE_INTERVAL_30MINUTE, - "1month": client.KLINE_INTERVAL_1MONTH, - } - interval_choices = list(interval_map.keys()) - - parser = argparse.ArgumentParser( - prog="candle", - add_help=False, - description="Program to plot candles for binance data", + plot_candles( + candles_df, + f"{coin+currency} from {candles_df.index[0].strftime('%Y/%m/%d')} to " + f"{candles_df.index[-1].strftime('%Y/%m/%d')}", ) - parser.add_argument( - "-i", - "--interval", - help="Interval to get data", - choices=interval_choices, - dest="interval", - default="1day", - type=str, - ) - - parser.add_argument( - "-l", "--limit", dest="limit", default=100, help="Number to get", type=int - ) - - try: - if not coin or not currency: - print("Coin needs to be selected prior to this command\n") - return - - coin += currency - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - candles = client.get_klines( - symbol=coin, - interval=interval_map[ns_parser.interval], - limit=ns_parser.limit, - ) - - # Response - # 1499040000000, // Open time - # "0.01634790", // Open - # "0.80000000", // High - # "0.01575800", // Low - # "0.01577100", // Close - # "148976.11427815", // Volume - # 1499644799999, // Close time - # "2434.19055334", // Quote asset volume - # 308, // Number of trades - # "1756.87402397", // Taker buy base asset volume - # "28.46694368", // Taker buy quote asset volume - # "17928899.62484339" // Ignore. - - candles_df = pd.DataFrame(candles).astype(float).iloc[:, :7] - - candles_df.columns = [ - "Time0", - "Open", - "High", - "Low", - "Close", - "Volume", - "Time1", - ] - candles_df.index = pd.to_datetime( - (candles_df["Time0"] + candles_df["Time1"]) / 2, unit="ms" - ) - - plot_candles( - candles_df, - f"{coin} from {candles_df.index[0].strftime('%Y/%m/%d')} to " - f"{candles_df.index[-1].strftime('%Y/%m/%d')}", - ) - - except Exception as e: - print(e, "\n") + print("") def balance(coin: str): diff --git a/gamestonk_terminal/cryptocurrency/crypto_controller.py b/gamestonk_terminal/cryptocurrency/crypto_controller.py index 958b345f5d5..776ca627034 100644 --- a/gamestonk_terminal/cryptocurrency/crypto_controller.py +++ b/gamestonk_terminal/cryptocurrency/crypto_controller.py @@ -13,6 +13,7 @@ from gamestonk_terminal.cryptocurrency import ( pycoingecko_view, coinmarketcap_view as cmc_view, ) +from gamestonk_terminal.technical_analysis import ta_controller class CryptoController: @@ -30,6 +31,7 @@ class CryptoController: "candle", "balance", "select", + "ta", ] def __init__(self): @@ -40,6 +42,7 @@ class CryptoController: self.current_coin = None self.current_currency = None self.current_df = pd.DataFrame() + self.source = "" def print_help(self): """Print help""" @@ -58,11 +61,13 @@ class CryptoController: print(" top view top coins from coinmarketcap") print("") print("Binance:") - print(" select select coin/currency to use") + print(" select select coin/currency to use and load candle data") print(" book show order book") - print(" candle get klines/candles and plot") + print(" candle show candles") print(" balance show coin balance") print("") + print(" >ta technical analysis menu for") + print("") def switch(self, an_input: str): """Process and dispatch input @@ -95,6 +100,7 @@ class CryptoController: def call_load(self, other_args): """Process load command""" self.current_coin, self.current_df = pycoingecko_view.load(other_args) + self.source = "CG" def call_view(self, other_args): """Process view command""" @@ -117,9 +123,11 @@ class CryptoController: """Process book command""" binance_model.order_book(other_args, self.current_coin, self.current_currency) - def call_candle(self, other_args): + def call_candle(self, _): """Process candle command""" - binance_model.show_candles(other_args, self.current_coin, self.current_currency) + binance_model.show_candles( + self.current_df, self.current_coin, self.current_currency + ) def call_balance(self, _): """Process balance command""" @@ -127,11 +135,52 @@ class CryptoController: def call_select(self, other_args): """Process select command""" - self.current_coin, self.current_currency = binance_model.select_binance_coin( - other_args - ) + ( + self.current_coin, + self.current_currency, + self.current_df, + ) = binance_model.select_binance_coin(other_args) + self.source = "BIN" print("") + # pylint: disable=inconsistent-return-statements + def call_ta(self, _): + """Process ta command""" + if not self.current_coin: + print("Please load a coin through either load or select", "\n") + if self.current_df.empty: + print("Price dataframe is empty") + else: + # Need to make the columns in df be compatible. Also since there are no splits or dividends, there is no + # adj close. To tell ta to use Close, we can just set the interval to anything but 1440. + # Binance provides candles so we just need to rename: + if self.source == "BIN": + self.current_df = self.current_df.rename( + columns={ + "Open": "1. open", + "High": "2. high", + "Low": "3. low", + "Close": "4. close", + "Volume": "6. volume", + } + ) + self.current_df.index.name = "date" + + # Coingecko does not provide candles so we can only provide close data. + elif self.source == "CG": + + self.current_df = self.current_df[["Price"]].rename( + columns={"Price": "4. close"} + ) + self.current_df.index.name = "date" + + return ta_controller.menu( + self.current_df, + self.current_coin, + self.current_df.index[0], + "Hi", + ) + def menu(): crypto_controller = CryptoController()