diff --git a/README.md b/README.md index c23c9dc..5661db7 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,13 @@ Python 3.5+ ## Installation -To install the library, please run the command to install via pip +You can install it via pip for static version +``` +pip install libcryptomarket +``` + +or development version ``` pip install git+https://github.com/libcryptomarket/libcryptomarket.git @@ -39,13 +44,17 @@ instruments = get_instruments() ### Historical -Run +Currently, multiple data sources of historical data are supported. + +For example, for the source of [Cryptocompare](https://www.cryptocompare.com/api/#), +run ``` from datetime import datetime from libcryptomarket.historical import get_historical_prices -prices = get_historical_prices(symbol='LTCBTC', +prices = get_historical_prices(source='cryptocompare', + symbol='LTC/BTC', exchange='Poloniex', period="hour", from_time=datetime(2017, 5, 1), @@ -56,6 +65,16 @@ Then you can get historical price in ascending order seamlessly, even though the limit has exceeded the source limit. The application helps continue querying until the data reaches the requirements. +For the source of [Poloniex](https://poloniex.com/support/api/), run + +``` +prices = get_historical_prices(source='Poloniex', + symbol='LTC/BTC', + period="30m", + from_time=datetime(2016, 1, 1), + to_time=datetime(2017, 8, 1)) +``` + ## Contribution The project is targeting as a core but generic toolkit to query cryptocurrency diff --git a/libcryptomarket/api/poloniex_api.py b/libcryptomarket/api/poloniex_api.py new file mode 100644 index 0000000..0a59ab6 --- /dev/null +++ b/libcryptomarket/api/poloniex_api.py @@ -0,0 +1,32 @@ +#!/bin/python +import requests +from datetime import datetime +import logging + +API_URL = "https://poloniex.com/public?command=" +VALID_PERIODS = [300, 900, 1800, 7200, 14400, 86400] + +def get_return_chart_data(currency_pair, period, start, end=None): + """Return returnChartData. + + :param currency_pair: Currency pair. For example, BTC_XMR. + :param period: Period. Valid values are 300, 900, 1800, 7200, 14400 and + 86400. + :param start: Start time in unix timestamp. + :param end: End time in unix timestamp. Optional. + """ + + assert period in VALID_PERIODS, ( + "Period is not in the valid periods (%s)" % VALID_PERIODS) + + url = (API_URL + "returnChartData" + + "¤cyPair={0}".format(currency_pair) + + "&period={0}".format(period) + + "&start={0}".format(start)) + + if end is not None: + url += "&end={0}".format(end) + + r = requests.get(url) + r.raise_for_status() + return r.json() \ No newline at end of file diff --git a/libcryptomarket/historical.py b/libcryptomarket/historical.py index b1e070f..79c7bcf 100644 --- a/libcryptomarket/historical.py +++ b/libcryptomarket/historical.py @@ -1,11 +1,8 @@ from functools import partial +from datetime import datetime import pandas as pd -from libcryptomarket.api.cryptocompare_api import ( - get_histo, CryptocompareHisto -) - def get_historical_prices(source='cryptocompare', symbol=None, exchange=None, period=None, limit=0, from_time=None, to_time=None): @@ -23,6 +20,8 @@ def get_historical_prices(source='cryptocompare', symbol=None, exchange=None, :param to_time: To time. Default is None, which follows the source default value. """ + source = source.lower() + if source == 'cryptocompare': if period is None: raise ValueError("Input parameter period cannot be None.") @@ -39,64 +38,120 @@ def get_historical_prices(source='cryptocompare', symbol=None, exchange=None, raise ValueError("Cannot accept either from_time or to_time is " "None") - # Parse from (first 3) and to (last 3) symbol from the parameter - # symbol. - from_sym = symbol[:3] - to_sym = symbol[3:] + return _get_historical_prices_cryptocompare(symbol, exchange, period, + limit, from_time, to_time) + elif source == 'poloniex': + if exchange is not None: + raise ValueError("Poloniex does not need exchange parameter.") - func = partial(get_histo, period=period, fsym=from_sym, tsym=to_sym, - e=exchange) + # Validate and transform symbol + symbol = symbol.replace("/", "_").upper() - data = [] - if limit > 0: - # Get the data by limit of records - to_time = 0 - - while limit > 0: - if to_time == 0: - response = func(limit=limit)['Data'] - else: - response = func(limit=limit, toTs=to_time)['Data'] - - if len(response) == 0: - # Terminate if no further response - break - else: - data += response - limit -= len(response) - to_time = response[0]['time'] - 1 - elif from_time is not None and to_time is not None: - # Get the data by time range - from libcryptomarket.api.cryptocompare_api import MAX_QUERY_LIMIT - from_time_ts = int(from_time.timestamp()) - to_time_ts = int(to_time.timestamp()) - - while from_time_ts < to_time_ts: - response = func(limit=MAX_QUERY_LIMIT, - toTs=to_time_ts)['Data'] - - if len(response) == 0: - # Terminate if no further response - break - else: - data += response - to_time_ts = response[0]['time'] - 1 + # Validate and transform periods + valid_periods = { + "5m": 300, + "15m": 900, + "30m": 1800, + "2h": 7200, + "4h": 14400, + "1d": 86400 + } + period = period.lower() + if period not in valid_periods.keys(): + raise ValueError("Periods is not valid. " + + ("Valid values (%s)" % str(valid_periods))) else: - data += func()['Data'] + period = valid_periods[period] - if len(data) == 0: - return data + # Validate and transform to_time and from_time + from_time = from_time.timestamp() + to_time = to_time.timestamp() if to_time is not None else 9999999999 + if to_time <= from_time: + raise ValueError("From time should not be greater than or " + "equal to to time.") - data = pd.DataFrame([CryptocompareHisto(**e).__dict__ for e in data]) - # Filter only valid time range - if from_time is not None and to_time is not None: - data = data[(data['r_time'] >= from_time) & - (data['r_time'] <= to_time)] - - data = data.set_index(['r_time']) - data.index.name = 'datetime' - - return data + return _get_historical_prices_poloniex(symbol, period, + from_time, to_time) else: raise ValueError("No source is called {0}".format(source)) + + +def _get_historical_prices_cryptocompare(symbol, exchange, period, + limit, from_time, to_time): + from libcryptomarket.api.cryptocompare_api import ( + get_histo, CryptocompareHisto + ) + + from_sym = symbol.split('/')[0] + to_sym = symbol.split('/')[1] + + func = partial(get_histo, period=period, fsym=from_sym, tsym=to_sym, + e=exchange) + + data = [] + if limit > 0: + # Get the data by limit of records + to_time = 0 + + while limit > 0: + if to_time == 0: + response = func(limit=limit)['Data'] + else: + response = func(limit=limit, toTs=to_time)['Data'] + + if len(response) == 0: + # Terminate if no further response + break + else: + data += response + limit -= len(response) + to_time = response[0]['time'] - 1 + elif from_time is not None and to_time is not None: + # Get the data by time range + from libcryptomarket.api.cryptocompare_api import MAX_QUERY_LIMIT + from_time_ts = int(from_time.timestamp()) + to_time_ts = int(to_time.timestamp()) + + while from_time_ts < to_time_ts: + response = func(limit=MAX_QUERY_LIMIT, + toTs=to_time_ts)['Data'] + + if len(response) == 0: + # Terminate if no further response + break + else: + data += response + to_time_ts = response[0]['time'] - 1 + + else: + data += func()['Data'] + + if len(data) == 0: + return data + + data = pd.DataFrame([CryptocompareHisto(**e).__dict__ for e in data]) + # Filter only valid time range + if from_time is not None and to_time is not None: + data = data[(data['r_time'] >= from_time) & + (data['r_time'] <= to_time)] + + data = data.set_index(['r_time']) + data.index.name = 'datetime' + + return data + + +def _get_historical_prices_poloniex(symbol, period, from_time, to_time): + from libcryptomarket.api.poloniex_api import get_return_chart_data + data = get_return_chart_data(symbol, period, from_time, to_time) + if len(data) == 0 or isinstance(data, dict): + raise ValueError("Poloniex data is not in a right format.\n{0}".format( + data)) + + data = pd.DataFrame(data) + data['date'] = data['date'].map(lambda x: pd.Timestamp(datetime.fromtimestamp(x))) + data.columns = "r_" + data.columns.str.lower() + data = data.set_index(['r_date']) + data.index.name = 'datetime' + return data \ No newline at end of file