mirror of
https://github.com/wassname/libcryptomarket.git
synced 2026-09-09 11:25:52 +08:00
[#11] Add Bitfinex API
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/bin/python
|
||||
# import requests
|
||||
# import hmac
|
||||
# import hashlib
|
||||
# import urllib
|
||||
# from functools import partial
|
||||
|
||||
from libcryptomarket.api.exchange_api import ExchangeApi
|
||||
|
||||
|
||||
class BitfinexApi(ExchangeApi):
|
||||
"""Bitfinex 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.bitfinex.com/v2"
|
||||
|
||||
@classmethod
|
||||
def get_public_calls(cls):
|
||||
"""Get public API calls.
|
||||
"""
|
||||
return {
|
||||
'tickers': 'GET',
|
||||
'ticker': 'GET',
|
||||
'trades': 'GET',
|
||||
'book': 'GET',
|
||||
'stat1': 'GET',
|
||||
'candles': '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
|
||||
|
||||
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 name == "ticker":
|
||||
symbol = kwargs["symbol"]
|
||||
del kwargs["symbol"]
|
||||
name = '/'.join([name, symbol])
|
||||
elif name == "trades":
|
||||
symbol = kwargs["symbol"]
|
||||
del kwargs["symbol"]
|
||||
name = '/'.join([name, symbol, "hist"])
|
||||
elif name == "book":
|
||||
symbol = kwargs["symbol"]
|
||||
precision = kwargs["precision"]
|
||||
del kwargs["symbol"]
|
||||
del kwargs["precision"]
|
||||
name = "/".join([name, symbol, precision])
|
||||
elif name == "stat1":
|
||||
key = kwargs["key"]
|
||||
size = kwargs["size"]
|
||||
symbol = kwargs["symbol"]
|
||||
section = kwargs["section"]
|
||||
del kwargs["key"]
|
||||
del kwargs["size"]
|
||||
del kwargs["symbol"]
|
||||
del kwargs["section"]
|
||||
name = "/".join([name,
|
||||
"{}:{}:{}".format(key, size, symbol),
|
||||
section])
|
||||
elif name == "candles":
|
||||
timeframe = kwargs["timeframe"]
|
||||
symbol = kwargs["symbol"]
|
||||
section = kwargs["section"]
|
||||
del kwargs["timeframe"]
|
||||
del kwargs["symbol"]
|
||||
del kwargs["section"]
|
||||
name = "/".join([name,
|
||||
"trade:{}:{}".format(timeframe, symbol),
|
||||
section])
|
||||
|
||||
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
|
||||
+173
-76
@@ -9,6 +9,9 @@ def historical_ticker(source, symbol, period, start_time=None, end_time=None):
|
||||
"""Return historical ticker.
|
||||
|
||||
:param source: Source, an Exchange API object.
|
||||
:param symbol: Symbol, string object.
|
||||
:param period: Period or frequency, followed with exchange protocol, string
|
||||
object.
|
||||
:param start_time: Start time, datetime object.
|
||||
:param end_time: Start time, datetime object.
|
||||
"""
|
||||
@@ -23,84 +26,178 @@ def historical_ticker(source, symbol, period, start_time=None, end_time=None):
|
||||
source_name = source.__class__.__name__.lower().replace("api", "")
|
||||
|
||||
if source_name == "poloniex":
|
||||
# Exchange validation
|
||||
if start_time is None and end_time is None:
|
||||
raise ValueError("Start time and end time cannot be both None.")
|
||||
|
||||
request_func = partial(source.return_chart_data, currencyPair=symbol,
|
||||
period=period)
|
||||
|
||||
if start_time is not None:
|
||||
request_func = partial(request_func,
|
||||
start=start_time.timestamp())
|
||||
if end_time is not None:
|
||||
request_func = partial(request_func,
|
||||
end=end_time.timestamp())
|
||||
data = request_func()
|
||||
data.raise_for_status()
|
||||
data = pd.DataFrame(data.json())
|
||||
data['date'] = data['date'].apply(
|
||||
lambda x: pd.to_datetime(x, unit='s'))
|
||||
data = data.set_index(['date'])
|
||||
data.index.name = 'datetime'
|
||||
data.columns.name = symbol
|
||||
|
||||
return data
|
||||
return _historical_ticker_poloniex(
|
||||
source=source, symbol=symbol, period=period,
|
||||
start_time=start_time, end_time=end_time)
|
||||
|
||||
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
|
||||
|
||||
return _historical_ticker_gdax(
|
||||
source=source, symbol=symbol, period=period,
|
||||
start_time=start_time, end_time=end_time)
|
||||
elif source_name == "bitfinex":
|
||||
return _historical_ticker_bitfinex(
|
||||
source=source, symbol=symbol, period=period,
|
||||
start_time=start_time, end_time=end_time)
|
||||
else:
|
||||
raise ValueError("Source (%s [%s]) does not support historical ticker"
|
||||
% (source, source_name))
|
||||
|
||||
|
||||
def _historical_ticker_poloniex(source, symbol, period, start_time, end_time):
|
||||
"""Return historical ticker in Poloniex.
|
||||
|
||||
:param source: Source, an Exchange API object.
|
||||
:param symbol: Symbol, string object.
|
||||
:param period: Period or frequency, followed with exchange protocol, string
|
||||
object.
|
||||
:param start_time: Start time, datetime object.
|
||||
:param end_time: Start time, datetime object.
|
||||
"""
|
||||
# Exchange validation
|
||||
if start_time is None and end_time is None:
|
||||
raise ValueError("Start time and end time cannot be both None.")
|
||||
|
||||
request_func = partial(source.return_chart_data, currencyPair=symbol,
|
||||
period=period)
|
||||
|
||||
if start_time is not None:
|
||||
request_func = partial(request_func,
|
||||
start=start_time.timestamp())
|
||||
if end_time is not None:
|
||||
request_func = partial(request_func,
|
||||
end=end_time.timestamp())
|
||||
data = request_func()
|
||||
data.raise_for_status()
|
||||
data = pd.DataFrame(data.json())
|
||||
data['date'] = data['date'].apply(
|
||||
lambda x: pd.to_datetime(x, unit='s'))
|
||||
data = data.set_index(['date'])
|
||||
data.index.name = 'datetime'
|
||||
data.columns.name = symbol
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _historical_ticker_gdax(source, symbol, period, start_time, end_time):
|
||||
"""Return historical ticker in GDAX.
|
||||
|
||||
:param source: Source, an Exchange API object.
|
||||
:param symbol: Symbol, string object.
|
||||
:param period: Period or frequency, followed with exchange protocol, string
|
||||
object.
|
||||
:param start_time: Start time, datetime object.
|
||||
:param end_time: Start time, datetime object.
|
||||
"""
|
||||
# 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
|
||||
|
||||
|
||||
def _historical_ticker_bitfinex(source, symbol, period, start_time, end_time):
|
||||
"""Return historical ticker in Bitfinex.
|
||||
|
||||
:param source: Source, an Exchange API object.
|
||||
:param symbol: Symbol, string object.
|
||||
:param period: Period or frequency, followed with exchange protocol, string
|
||||
object.
|
||||
:param start_time: Start time, datetime object.
|
||||
:param end_time: Start time, datetime object.
|
||||
"""
|
||||
request_func = partial(source.candles, symbol=symbol, timeframe=period,
|
||||
section="hist", sort=1, limit=1000)
|
||||
|
||||
if start_time is None and end_time is None:
|
||||
data = request_func()
|
||||
data.raise_for_status()
|
||||
data = pd.DataFrame(data.json())
|
||||
else:
|
||||
# Safety net
|
||||
last_datetime = (
|
||||
0 if start_time is None else start_time.timestamp() * 1000)
|
||||
|
||||
data = []
|
||||
|
||||
while start_time is None or end_time is None or start_time <= end_time:
|
||||
f = request_func
|
||||
|
||||
if start_time is not None:
|
||||
f = partial(f, start=round(start_time.timestamp() * 1000))
|
||||
|
||||
if end_time is not None:
|
||||
f = partial(f, end=round(end_time.timestamp() * 1000))
|
||||
|
||||
tmp_data = f()
|
||||
tmp_data.raise_for_status()
|
||||
tmp_data = tmp_data.json()
|
||||
|
||||
if len(tmp_data) == 0:
|
||||
break
|
||||
elif last_datetime >= tmp_data[-1][0]:
|
||||
print(last_datetime)
|
||||
print(tmp_data[-1][0])
|
||||
break
|
||||
else:
|
||||
last_datetime = tmp_data[-1][0]
|
||||
start_time = datetime.fromtimestamp(
|
||||
round(last_datetime / 1000 + 1))
|
||||
data.append(pd.DataFrame(tmp_data))
|
||||
|
||||
sleep(1)
|
||||
|
||||
data = pd.concat(data, axis=0)
|
||||
|
||||
data.columns = ["datetime", "open", "close", "high", "low", "volume"]
|
||||
data["datetime"] = pd.to_datetime(data["datetime"], unit="ms")
|
||||
data = data.set_index("datetime").sort_index()
|
||||
data = data[~data.index.duplicated(keep='first')]
|
||||
|
||||
return data
|
||||
|
||||
Reference in New Issue
Block a user