diff --git a/libcryptomarket/api/bitmex_api.py b/libcryptomarket/api/bitmex_api.py index 22e245d..72ae672 100644 --- a/libcryptomarket/api/bitmex_api.py +++ b/libcryptomarket/api/bitmex_api.py @@ -82,4 +82,4 @@ class BitmexApi(ExchangeApi): # return self._send_request( # command=name, http_method=http_method, params=kwargs, # public_key=self._public_key, private_key=self._private_key) - raise NotImplementedError("request private is not implemented.") \ No newline at end of file + raise NotImplementedError("request private is not implemented.") diff --git a/libcryptomarket/api/exchange_api.py b/libcryptomarket/api/exchange_api.py index 7c76e6f..5001c5c 100644 --- a/libcryptomarket/api/exchange_api.py +++ b/libcryptomarket/api/exchange_api.py @@ -1,15 +1,17 @@ #!/bin/python import requests -import hmac -import hashlib -import urllib +# import hmac +# import hashlib +# import urllib import json from functools import partial from time import time + class ExchangeApi: """Exchange API connector. """ + def __init__(self, public_key, private_key, logger=None): """Constructor. @@ -135,7 +137,6 @@ class ExchangeApi: """ raise NotImplementedError("Not yet implemented.") - def _send_request(self, command, http_method, params=None, data=None, public_method=False): """Send request. diff --git a/libcryptomarket/api/gdax_api.py b/libcryptomarket/api/gdax_api.py new file mode 100644 index 0000000..fb97c65 --- /dev/null +++ b/libcryptomarket/api/gdax_api.py @@ -0,0 +1,125 @@ +#!/bin/python +# import requests +# import hmac +# import hashlib +# import urllib +# from functools import partial + +from libcryptomarket.api.exchange_api import ExchangeApi + + +class GdaxApi(ExchangeApi): + """GDAX API. + """ + + def __init__(self, public_key=None, private_key=None, logger=None): + """Constructor. + + :param public_key: Public key. + :param private_key: Private key. + :param logger: Logger. + """ + ExchangeApi.__init__(self, public_key, private_key, logger) + + @classmethod + def get_url(cls): + """Get API url. + """ + return "https://api.gdax.com" + + @classmethod + def get_public_calls(cls): + """Get public API calls. + """ + return { + "products": "GET", + "products/book": "GET", + "products/trades": "GET", + "products/candles": "GET", + "currencies": "GET", + "time": "GET", + } + + @classmethod + def get_private_calls(cls): + """Get public API calls. + """ + return { + } + + @classmethod + def translate_call_name(cls, name): + """Translate API call name. + + The class method name is always underscored (aligned with Python + standard.) This method is to translate underscored name to exchange + API call name. + + :param name: Method name (underscored). + """ + return name.replace("_", "/") + + def _request_public(self, name, http_method, **kwargs): + """Request public API call. + + :param name: Method name. + :param http_method: HTTP method (POST, GET, DELETE). + """ + if 'product_id' in kwargs.keys(): + name = name.split('/') + name = [name[0]] + [kwargs['product_id']] + name[1:] + del kwargs['product_id'] + return self._send_request( + command='/'.join(name), http_method=http_method, + public_method=True, params=kwargs) + else: + return self._send_request( + command=name, http_method=http_method, + public_method=True, params=kwargs) + + def _request_private(self, name, http_method, **kwargs): + """Request private API call. + + :param name: Method name. + :param http_method: HTTP method (POST, GET, DELETE). + """ + raise NotImplementedError() + + # @classmethod + # def _generate_auth(cls, public_key, private_key): + # """Generate authentication. + + # :param public_key: Public key. + # :param private_key: Private key. + # """ + # return None + + # @classmethod + # def _generate_headers(cls, command, http_method, params, data, + # public_key, private_key): + # """Generate headers. + + # :param command: Command. + # :param http_method: HTTP method, for example GET. + # :param params: Parameters. + # :param data: Data. + # :param public_key: Public key. + # :param private_key: Private key. + # """ + # signature = hmac.new(private_key.encode(), + # urllib.parse.urlencode(data).encode(), + # digestmod=hashlib.sha512).hexdigest() + # header = { + # 'Key': public_key, + # 'Sign': signature + # } + + # return header + + # @classmethod + # def _format_data(cls, data): + # """Format the data to exchange desirable format. + + # :param data: Data. + # """ + # return data diff --git a/libcryptomarket/api/poloniex_api.py b/libcryptomarket/api/poloniex_api.py index 6857d8c..552f93a 100644 --- a/libcryptomarket/api/poloniex_api.py +++ b/libcryptomarket/api/poloniex_api.py @@ -3,7 +3,7 @@ import requests import hmac import hashlib import urllib -from functools import partial +# from functools import partial from libcryptomarket.api.exchange_api import ExchangeApi API_URL = "https://poloniex.com/public?command=" diff --git a/libcryptomarket/core.py b/libcryptomarket/core.py index 3ab359f..aad581f 100644 --- a/libcryptomarket/core.py +++ b/libcryptomarket/core.py @@ -1,5 +1,6 @@ from functools import partial from datetime import datetime, timedelta +from time import sleep import pandas as pd @@ -45,6 +46,61 @@ def historical_ticker(source, symbol, period, start_time=None, end_time=None): data.columns.name = symbol return data + + elif source_name == "gdax": + # Exchange validation + if (start_time is None) + (end_time is None) not in [0, 2]: + # Both start and end time must be provided + raise ValueError("Start and end time must be both provided") + + request_func = partial(source.products_candles, product_id=symbol, + granularity=period) + + if start_time is None and end_time is None: + # Just get the latest 300 ticks + data = request_func() + data.raise_for_status() + data = pd.DataFrame(data.json()) + + else: + # Safety net + last_datetime = start_time.timestamp() + + data = [] + + while start_time <= end_time: + tmp_data = request_func( + start=start_time.isoformat(), + end=(start_time + + timedelta(seconds=period * 200)).isoformat()) + tmp_data.raise_for_status() + tmp_data = pd.DataFrame(tmp_data.json()) + + # Append into data list + data.append(tmp_data) + + # Check to exit + if last_datetime >= tmp_data.iloc[0, 0]: + # Same as the previous query + break + else: + last_datetime = tmp_data.iloc[0, 0] + start_time = datetime.fromtimestamp(last_datetime) + + sleep(0.333) + + if len(data) > 1: + data = pd.concat(data, axis=0) + else: + data = data[0] + + data.columns = ['datetime', 'low', 'high', 'open', 'close', 'volume'] + data['datetime'] = pd.to_datetime(data['datetime'], unit='s') + data = data.set_index('datetime').sort_index() + data = data[~data.index.duplicated(keep='first')] + + return data + else: raise ValueError("Source (%s [%s]) does not support historical ticker" - % (source, source_name)) \ No newline at end of file + % (source, source_name)) diff --git a/libcryptomarket/historical.py b/libcryptomarket/historical.py index 090ae6c..a87b48d 100644 --- a/libcryptomarket/historical.py +++ b/libcryptomarket/historical.py @@ -87,7 +87,7 @@ def get_historical_prices(source='cryptocompare', symbol=None, exchange=None, # Validate and transform to_time and from_time if (to_time is not None and from_time is not None and - to_time <= from_time): + to_time <= from_time): raise ValueError("From time should not be greater than or " "equal to to time.") @@ -96,7 +96,10 @@ def get_historical_prices(source='cryptocompare', symbol=None, exchange=None, while from_time is None or to_time is None or from_time < to_time: # Import and query from libcryptomarket.api.bitmex_api import BitmexApi - exchange = BitmexApi(public_key=None, private_key=None, logger=None) + exchange = BitmexApi( + public_key=None, + private_key=None, + logger=None) func = partial(exchange.trade_bucketed, symbol=symbol, binSize=period)