summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjp <plutakuba@gmail.com>2021-09-13 20:07:00 +0200
committerGitHub <noreply@github.com>2021-09-13 14:07:00 -0400
commit0b554c6893aa4e805c27b606f7de7ae6c0eb454d (patch)
tree390d2a378fd60d35f34d4e140887451ba74cc9e7
parent6a2792ea8e79e799d0eacc732127dc3ca7d1ce80 (diff)
Coinbase pro api (#743)
* Add separate view for finbrain for crypto curreny sentiment analysis * Move json with symbols to separate directory, little refactoring, update readme * Add screenshot of finbrain for PolkaDot * Coinbase menu added * Add more methods for coinbase * Add coinbase views, coinbase models. Add coinbase to load, chart, find commands. Add coinbase to controller * Add auth client for coinbase pro endpoints that needs auth * Add more views for coinbase to crypto menu * Add some print tests for Coinbase view * Add 2 more tests for coinbase view * Update README with Coinbase API keys * Cleaning empty lines, spaces etc. * Use os.path.join in imports, change column names in one normalized df * Move Coinbase to portfolio brokers. Adjust tests, and imports * precommit config return to prev ver * Fix coinbase accounts command, del column filter for deposits
-rw-r--r--.pre-commit-config.yaml2
-rw-r--r--README.md2
-rw-r--r--gamestonk_terminal/config_terminal.py5
-rw-r--r--gamestonk_terminal/cryptocurrency/coinbase_helpers.py167
-rw-r--r--gamestonk_terminal/cryptocurrency/crypto_controller.py177
-rw-r--r--gamestonk_terminal/cryptocurrency/cryptocurrency_helpers.py291
-rw-r--r--gamestonk_terminal/cryptocurrency/data/coinbase_gecko_map.json101
-rw-r--r--gamestonk_terminal/cryptocurrency/discovery/pycoingecko_model.py33
-rw-r--r--gamestonk_terminal/cryptocurrency/due_diligence/binance_model.py35
-rw-r--r--gamestonk_terminal/cryptocurrency/due_diligence/binance_view.py4
-rw-r--r--gamestonk_terminal/cryptocurrency/due_diligence/coinbase_model.py193
-rw-r--r--gamestonk_terminal/cryptocurrency/due_diligence/coinbase_view.py152
-rw-r--r--gamestonk_terminal/cryptocurrency/due_diligence/dd_controller.py282
-rw-r--r--gamestonk_terminal/cryptocurrency/overview/coinbase_model.py35
-rw-r--r--gamestonk_terminal/cryptocurrency/overview/coinbase_view.py60
-rw-r--r--gamestonk_terminal/cryptocurrency/overview/overview_controller.py73
-rw-r--r--gamestonk_terminal/portfolio/brokers/bro_controller.py12
-rw-r--r--gamestonk_terminal/portfolio/brokers/coinbase/__init__.py0
-rw-r--r--gamestonk_terminal/portfolio/brokers/coinbase/coinbase_controller.py368
-rw-r--r--gamestonk_terminal/portfolio/brokers/coinbase/coinbase_model.py177
-rw-r--r--gamestonk_terminal/portfolio/brokers/coinbase/coinbase_view.py198
-rw-r--r--tests/test_cryptocurrency/test_coinbase_view.py239
22 files changed, 2410 insertions, 196 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ce0dd7dcbc7..132d4a82a05 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -26,7 +26,7 @@ repos:
rev: 'v0.812'
hooks:
- id: mypy
- args: [ --ignore-missing-imports ]
+ args: [ --ignore-missing-imports ]
- repo: https://github.com/pre-commit/mirrors-pylint
rev: 'v3.0.0a4'
hooks:
diff --git a/README.md b/README.md
index 63ea3173cce..2461ee21146 100644
--- a/README.md
+++ b/README.md
@@ -266,6 +266,7 @@ These are the ones where a key is necessary:
* SentimentInvestor: https://sentimentinvestor.com
* Tradier: https://developer.tradier.com/getting_started
* Twitter: https://developer.twitter.com
+* Coinbase Pro API: https://docs.pro.coinbase.com/
When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py).
@@ -288,6 +289,7 @@ Alternatively, you can also set them to the following environment variables:
| [SentimentInvestor](https://sentimentinvestor.com) | GT_API_SENTIMENTINVESTOR_TOKEN <br> GT_API_SENTIMENTINVESTOR_KEY |
| [Tradier](https://developer.tradier.com) | GT_TRADIER_TOKEN |
| [Twitter](https://developer.twitter.com) | GT_API_TWITTER_KEY <br/> GT_API_TWITTER_SECRET_KEY <br/> GT_API_TWITTER_BEARER_TOKEN |
+| [Coinbase](https://docs.pro.coinbase.com/) | GT_API_COINBASE_KEY <br/> GT_API_COINBASE_SECRET <br/> GT_API_COINBASE_PASS_PHRASE |
Example:
```
diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py
index 91cef9f0e05..b81493537f8 100644
--- a/gamestonk_terminal/config_terminal.py
+++ b/gamestonk_terminal/config_terminal.py
@@ -85,3 +85,8 @@ API_SENTIMENTINVESTOR_KEY = os.getenv("GT_API_SENTIMENTINVESTOR_KEY") or "REPLAC
API_SENTIMENTINVESTOR_TOKEN = (
os.getenv("GT_API_SENTIMENTINVESTOR_TOKEN") or "REPLACE_ME"
)
+
+# https://pro.coinbase.com/profile/api
+API_COINBASE_KEY = os.getenv("GT_API_COINBASE_KEY") or "REPLACE_ME"
+API_COINBASE_SECRET = os.getenv("GT_API_COINBASE_SECRET") or "REPLACE_ME"
+API_COINBASE_PASS_PHRASE = os.getenv("GT_API_COINBASE_PASS_PHRASE") or "REPLACE_ME"
diff --git a/gamestonk_terminal/cryptocurrency/coinbase_helpers.py b/gamestonk_terminal/cryptocurrency/coinbase_helpers.py
new file mode 100644
index 00000000000..99ca482b5eb
--- /dev/null
+++ b/gamestonk_terminal/cryptocurrency/coinbase_helpers.py
@@ -0,0 +1,167 @@
+"""Coinbase helpers model"""
+__docformat__ = "numpy"
+
+import argparse
+import binascii
+
+from typing import Optional, Any, Union
+import hmac
+import hashlib
+import time
+import base64
+import requests
+from requests.auth import AuthBase
+import gamestonk_terminal.config_terminal as cfg
+
+
+class CoinbaseProAuth(AuthBase):
+ """Authorize CoinbasePro requests. Source: https://docs.pro.coinbase.com/?python#signing-a-message"""
+
+ def __init__(self, api_key, secret_key, passphrase):
+ self.api_key = api_key
+ self.secret_key = secret_key
+ self.passphrase = passphrase
+
+ def __call__(self, request):
+ timestamp = str(time.time())
+ message = timestamp + request.method + request.path_url + (request.body or "")
+ message = message.encode("ascii")
+
+ try:
+ hmac_key = base64.b64decode(self.secret_key)
+ signature = hmac.new(hmac_key, message, hashlib.sha256)
+ signature_b64 = base64.b64encode(signature.digest())
+ except binascii.Error:
+ signature_b64 = ""
+
+ request.headers.update(
+ {
+ "CB-ACCESS-SIGN": signature_b64,
+ "CB-ACCESS-TIMESTAMP": timestamp,
+ "CB-ACCESS-KEY": self.api_key,
+ "CB-ACCESS-PASSPHRASE": self.passphrase,
+ "Content-Type": "application/json",
+ }
+ )
+ return request
+
+
+class CoinbaseRequestException(Exception):
+ """Coinbase Request Exception object"""
+
+ def __init__(self, message: str):
+ super().__init__(message)
+ self.message = message
+
+ def __str__(self) -> str:
+ return "CoinbaseRequestException: %s" % self.message
+
+
+class CoinbaseApiException(Exception):
+ """Coinbase API Exception object"""
+
+ def __init__(self, message: str):
+ super().__init__(message)
+ self.message = message
+
+ def __str__(self) -> str:
+ return "CoinbaseApiException: %s" % self.message
+
+
+def check_validity_of_product(product_id: str) -> str:
+ """Helper method that checks if provided product_id exists. It's a pair of coins in format COIN-COIN.
+ If product exists it return it, in other case it raise an error. [Source: Coinbase]
+
+ Parameters
+ ----------
+ product_id: str
+ Trading pair of coins on Coinbase e.g ETH-USDT or UNI-ETH
+
+ Returns
+ -------
+ str
+ pair of coins in format COIN-COIN
+ """
+
+ products = [pair["id"] for pair in make_coinbase_request("/products")]
+ if product_id.upper() not in products:
+ raise argparse.ArgumentTypeError(
+ f"You provided wrong pair of coins {product_id}. "
+ f"It should be provided as a pair in format COIN-COIN e.g UNI-USD"
+ )
+ return product_id.upper()
+
+
+def make_coinbase_request(
+ endpoint, params: Optional[dict] = None, auth: Optional[Any] = None
+) -> dict:
+ """Request handler for Coinbase Pro Api. Prepare a request url, params and payload and call endpoint.
+ [Source: Coinbase]
+
+ Parameters
+ ----------
+ endpoint: str
+ Endpoint path e.g /products
+ params: dict
+ Parameter dedicated for given endpoint
+ auth: any
+ Api credentials for purpose of using endpoints that needs authentication
+
+ Returns
+ -------
+ dict
+ response from Coinbase Pro Api
+ """
+
+ url = "https://api.pro.coinbase.com"
+ response = requests.get(url + endpoint, params=params, auth=auth)
+
+ if not 200 <= response.status_code < 300:
+ raise CoinbaseApiException("Invalid Authentication: %s" % response.text)
+ try:
+ return response.json()
+ except ValueError as e:
+ raise CoinbaseRequestException("Invalid Response: %s" % response.text) from e
+
+
+def _get_account_coin_dict() -> dict:
+ """Helper method that returns dictionary with all symbols and account ids in dictionary format. [Source: Coinbase]
+
+ Returns
+ -------
+ dict:
+ Your accounts in coinbase
+ {'1INCH': '0c29b708-d73b-4e1c-a58c-9c261cb4bedb', 'AAVE': '0712af66-c069-45b5-84ae-7b2347c2fd24', ..}
+
+ """
+ auth = CoinbaseProAuth(
+ cfg.API_COINBASE_KEY, cfg.API_COINBASE_SECRET, cfg.API_COINBASE_PASS_PHRASE
+ )
+ accounts = make_coinbase_request("/accounts", auth=auth)
+ return {acc["currency"]: acc["id"] for acc in accounts}
+
+
+def _check_account_validity(account: str) -> Union[str, Any]:
+ """Helper methods that checks if given account exists. [Source: Coinbase]
+
+ Parameters
+ ----------
+ account: str
+ coin or account id
+
+ Returns
+ -------
+ Union[str, Any]
+ Your account id or None
+ """
+
+ accounts = _get_account_coin_dict()
+
+ if account in list(accounts.keys()):
+ return accounts[account]
+
+ if account in list(accounts.values()):
+ return account
+
+ print("Wrong account id or coin symbol")
+ return None
diff --git a/gamestonk_terminal/cryptocurrency/crypto_controller.py b/gamestonk_terminal/cryptocurrency/crypto_controller.py
index ec7c58d492b..7e7cb4ba3a1 100644
--- a/gamestonk_terminal/cryptocurrency/crypto_controller.py
+++ b/gamestonk_terminal/cryptocurrency/crypto_controller.py
@@ -38,10 +38,9 @@ from gamestonk_terminal.cryptocurrency.cryptocurrency_helpers import (
plot_chart,
)
from gamestonk_terminal.cryptocurrency.report import report_controller
+from gamestonk_terminal.cryptocurrency.due_diligence import binance_model
+from gamestonk_terminal.cryptocurrency.due_diligence import coinbase_model
from gamestonk_terminal.cryptocurrency.onchain import onchain_controller
-from gamestonk_terminal.cryptocurrency.due_diligence.binance_model import (
- show_available_pairs_for_given_symbol,
-)
import gamestonk_terminal.config_terminal as cfg
@@ -67,6 +66,7 @@ class CryptoController:
"bin": "Binance",
"cg": "CoinGecko",
"cp": "CoinPaprika",
+ "cb": "Coinbase",
}
DD_VIEWS_MAPPING = {
@@ -178,7 +178,7 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="load",
description="Load crypto currency to perform analysis on. "
- "Available data sources are CoinGecko, CoinPaprika, and Binance"
+ "Available data sources are CoinGecko, CoinPaprika, Binance, Coinbase"
"By default main source used for analysis is CoinGecko (cg). To change it use --source flag",
)
@@ -196,7 +196,7 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
"--source",
help="Source of data",
dest="source",
- choices=("cp", "cg", "bin"),
+ choices=("cp", "cg", "bin", "cb"),
default="cg",
required=False,
)
@@ -236,11 +236,8 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="chart",
- description="""Loads data for technical analysis. You can specify currency vs which you want
- to show chart and also number of days to get data for.
- By default currency: usd and days: 30.
- E.g. if you loaded in previous step Ethereum and you want to see it's price vs btc
- in last 90 days range use `ta --vs btc --days 90`""",
+ description="""Display chart for loaded coin. You can specify currency vs which you want
+ to show chart and also number of days to get data for.""",
)
if self.source == "cp":
@@ -295,7 +292,9 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
"1month": client.KLINE_INTERVAL_1MONTH,
}
- _, quotes = show_available_pairs_for_given_symbol(self.current_coin)
+ _, quotes = binance_model.show_available_pairs_for_given_symbol(
+ self.current_coin
+ )
parser.add_argument(
"--vs",
@@ -325,13 +324,61 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
type=check_positive,
)
+ if self.source == "cb":
+ interval_map = {
+ "1min": 60,
+ "5min": 300,
+ "15min": 900,
+ "1hour": 3600,
+ "6hour": 21600,
+ "24hour": 86400,
+ "1day": 86400,
+ }
+
+ _, quotes = coinbase_model.show_available_pairs_for_given_symbol(
+ self.current_coin
+ )
+ if len(quotes) < 0:
+ print(
+ f"Couldn't find any quoted coins for provided symbol {self.current_coin}"
+ )
+ return
+
+ parser.add_argument(
+ "--vs",
+ help="Quote currency (what to view coin vs)",
+ dest="vs",
+ type=str,
+ default="USDT" if "USDT" in quotes else quotes[0],
+ choices=quotes,
+ )
+
+ parser.add_argument(
+ "-i",
+ "--interval",
+ help="Interval to get data",
+ choices=list(interval_map.keys()),
+ dest="interval",
+ default="1day",
+ type=str,
+ )
+
+ parser.add_argument(
+ "-l",
+ "--limit",
+ dest="limit",
+ default=100,
+ help="Number to get",
+ type=check_positive,
+ )
+
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
- if self.source == "bin":
+ if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
@@ -424,7 +471,9 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
"1month": client.KLINE_INTERVAL_1MONTH,
}
- _, quotes = show_available_pairs_for_given_symbol(self.current_coin)
+ _, quotes = binance_model.show_available_pairs_for_given_symbol(
+ self.current_coin
+ )
parser.add_argument(
"--vs",
help="Quote currency (what to view coin vs)",
@@ -453,13 +502,109 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
type=check_positive,
)
+ if self.source == "cb":
+ interval_map = {
+ "1min": 60,
+ "5min": 300,
+ "15min": 900,
+ "1hour": 3600,
+ "6hour": 21600,
+ "24hour": 86400,
+ "1day": 86400,
+ }
+
+ _, quotes = coinbase_model.show_available_pairs_for_given_symbol(
+ self.current_coin
+ )
+ if len(quotes) < 0:
+ print(
+ f"Couldn't find any quoted coins for provided symbol {self.current_coin}"
+ )
+ return
+
+ parser.add_argument(
+ "--vs",
+ help="Quote currency (what to view coin vs)",
+ dest="vs",
+ type=str,
+ default="USDT" if "USDT" in quotes else quotes[0],
+ choices=quotes,
+ )
+
+ parser.add_argument(
+ "-i",
+ "--interval",
+ help="Interval to get data",
+ choices=list(interval_map.keys()),
+ dest="interval",
+ default="1day",
+ type=str,
+ )
+
+ parser.add_argument(
+ "-l",
+ "--limit",
+ dest="limit",
+ default=100,
+ help="Number to get",
+ type=check_positive,
+ )
+
+ if self.source == "cb":
+ interval_map = {
+ "1min": 60,
+ "5min": 300,
+ "15min": 900,
+ "1hour": 3600,
+ "6hour": 21600,
+ "24hour": 86400,
+ "1day": 86400,
+ }
+
+ _, quotes = coinbase_model.show_available_pairs_for_given_symbol(
+ self.current_coin
+ )
+ if len(quotes) < 0:
+ print(
+ f"Couldn't find any quoted coins for provided symbol {self.current_coin}"
+ )
+ return
+
+ parser.add_argument(
+ "--vs",
+ help="Quote currency (what to view coin vs)",
+ dest="vs",
+ type=str,
+ default="USDT" if "USDT" in quotes else quotes[0],
+ choices=quotes,
+ )
+
+ parser.add_argument(
+ "-i",
+ "--interval",
+ help="Interval to get data",
+ choices=list(interval_map.keys()),
+ dest="interval",
+ default="1day",
+ type=str,
+ )
+
+ parser.add_argument(
+ "-l",
+ "--limit",
+ dest="limit",
+ default=100,
+ help="Number to get",
+ type=check_positive,
+ )
+
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
- if self.source == "bin":
+ if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
@@ -599,7 +744,7 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="""
Find similar coin by coin name,symbol or id. If you don't remember exact name or id of the Coin at CoinGecko,
- Binance or CoinPaprika you can use this command to display coins with similar name, symbol or id
+ Binance, Coinbase or CoinPaprika you can use this command to display coins with similar name, symbol or id
to your search query.
Example of usage: coin name is something like "polka". So I can try: find -c polka -k name -t 25
It will search for coin that has similar name to polka and display top 25 matches.
@@ -639,7 +784,7 @@ Note: Some of CoinGecko commands can fail. Team is working on fix.
parser.add_argument(
"--source",
dest="source",
- choices=["cp", "cg", "bin"],
+ choices=["cp", "cg", "bin", "cb"],
default="cg",
help="Source of data.",
type=str,
diff --git a/gamestonk_terminal/cryptocurrency/cryptocurrency_helpers.py b/gamestonk_terminal/cryptocurrency/cryptocurrency_helpers.py
index 32a4fffb6bf..fd06778d276 100644
--- a/gamestonk_terminal/cryptocurrency/cryptocurrency_helpers.py
+++ b/gamestonk_terminal/cryptocurrency/cryptocurrency_helpers.py
@@ -2,9 +2,11 @@
__docformat__ = "numpy"
import os
+import json
from typing import Tuple, Any, Optional, Union
import difflib
import pandas as pd
+import numpy as np
from binance.client import Client
import matplotlib.pyplot as plt
from tabulate import tabulate
@@ -13,15 +15,12 @@ from gamestonk_terminal.helper_funcs import (
plot_autoscale,
export_data,
)
+from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal.cryptocurrency.due_diligence import (
pycoingecko_model,
coinpaprika_model,
)
-from gamestonk_terminal.cryptocurrency.discovery.pycoingecko_model import (
- get_coin_list,
- get_mapping_matrix_for_binance,
- load_binance_map,
-)
+from gamestonk_terminal.cryptocurrency.discovery.pycoingecko_model import get_coin_list
from gamestonk_terminal.cryptocurrency.overview.coinpaprika_model import (
get_list_of_coins,
)
@@ -30,11 +29,35 @@ from gamestonk_terminal.cryptocurrency.due_diligence.binance_model import (
show_available_pairs_for_given_symbol,
plot_candles,
)
+
+from gamestonk_terminal.cryptocurrency.due_diligence import coinbase_model
import gamestonk_terminal.config_terminal as cfg
from gamestonk_terminal.feature_flags import USE_ION as ion
from gamestonk_terminal import feature_flags as gtff
+def _load_coin_map(file_name: str) -> pd.DataFrame:
+ if file_name.split(".")[1] != "json":
+ raise TypeError("Please load json file")
+
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ path = os.path.join(current_dir, "data", file_name)
+ with open(path, encoding="utf8") as f:
+ coins = json.load(f)
+
+ coins_df = pd.Series(coins).reset_index()
+ coins_df.columns = ["symbol", "id"]
+ return coins_df
+
+
+def load_binance_map():
+ return _load_coin_map("binance_gecko_map.json")
+
+
+def load_coinbase_map():
+ return _load_coin_map("coinbase_gecko_map.json")
+
+
def prepare_all_coins_df() -> pd.DataFrame:
"""Helper method which loads coins from all sources: CoinGecko, CoinPaprika, Binance and
merge those coins on keys:
@@ -47,12 +70,17 @@ def prepare_all_coins_df() -> pd.DataFrame:
CoinGecko - id for coin in CoinGecko API: uniswap
CoinPaprika - id for coin in CoinPaprika API: uni-uniswap
Binance - symbol (baseAsset) for coin in Binance API: UNI
+ Coinbase - symbol for coin in Coinbase Pro API e.g UNI
Symbol: uni
"""
gecko_coins_df = get_coin_list()
paprika_coins_df = get_list_of_coins()
+
+ # TODO: Think about scheduled job, that once a day will update data
+
binance_coins_df = load_binance_map().rename(columns={"symbol": "Binance"})
+ coinbase_coins_df = load_coinbase_map().rename(columns={"symbol": "Coinbase"})
gecko_paprika_coins_df = pd.merge(
gecko_coins_df, paprika_coins_df, on="name", how="left"
)
@@ -72,7 +100,15 @@ def prepare_all_coins_df() -> pd.DataFrame:
inplace=True,
)
- return df_merged[["CoinGecko", "CoinPaprika", "Binance", "Symbol"]]
+ df_merged = pd.merge(
+ left=df_merged,
+ right=coinbase_coins_df,
+ left_on="CoinGecko",
+ right_on="id",
+ how="left",
+ )
+
+ return df_merged[["CoinGecko", "CoinPaprika", "Binance", "Coinbase", "Symbol"]]
def _create_closest_match_df(
@@ -109,7 +145,7 @@ def load(
coin: str,
source: str,
) -> Tuple[Union[Optional[str], pycoingecko_model.Coin], Any]:
- """Load cryptocurrency from given source. Available sources are: CoinGecko, CoinPaprika and Binance.
+ """Load cryptocurrency from given source. Available sources are: CoinGecko, CoinPaprika, Coinbase and Binance.
Loading coin from Binance and CoinPaprika means validation if given coins