diff --git a/binance/client.py b/binance/client.py index 09b6652d6..6a904d202 100755 --- a/binance/client.py +++ b/binance/client.py @@ -8,12 +8,12 @@ import time from operator import itemgetter from urllib.parse import urlencode - +from pandas import DataFrame from .helpers import interval_to_milliseconds, convert_ts_str from .exceptions import BinanceAPIException, BinanceRequestException, NotImplementedException -from .enums import HistoricalKlinesType - +from .enums import HistoricalKlinesType, OHLC_COLUMNS +from .decorators import HistoricalKlinesData class BaseClient: @@ -910,6 +910,7 @@ def _get_earliest_valid_timestamp(self, symbol, interval, klines_type: Historica return kline[0][0] def get_historical_klines(self, symbol, interval, start_str, end_str=None, limit=500, + output_format='list', klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): """Get Historical Klines from Binance @@ -923,15 +924,20 @@ def get_historical_klines(self, symbol, interval, start_str, end_str=None, limit :type end_str: str|int :param limit: Default 500; max 1000. :type limit: int + + :param output_format: 'list' or 'df' or 'decorated' (default is 'list') + :type output_format: str + :param klines_type: Historical klines type: SPOT or FUTURES :type klines_type: HistoricalKlinesType :return: list of OHLCV values """ - return self._historical_klines(symbol, interval, start_str, end_str=end_str, limit=limit, klines_type=klines_type) + return self._historical_klines(symbol, interval, start_str, end_str=end_str, limit=limit, output_format=output_format, klines_type=klines_type) def _historical_klines(self, symbol, interval, start_str, end_str=None, limit=500, + output_format='list', klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): """Get Historical Klines from Binance (spot or futures) @@ -949,6 +955,10 @@ def _historical_klines(self, symbol, interval, start_str, end_str=None, limit=50 :type end_str: None|str|int :param limit: Default 500; max 1000. :type limit: int + + :param output_format: 'list' or 'df' or 'decorated' (default is 'list') + :type output_format: str + :param klines_type: Historical klines type: SPOT or FUTURES :type klines_type: HistoricalKlinesType @@ -1005,7 +1015,12 @@ def _historical_klines(self, symbol, interval, start_str, end_str=None, limit=50 if idx % 3 == 0: time.sleep(1) - return output_data + if output_format == "df": + return DataFrame(output_data, columns=OHLC_COLUMNS) + elif output_format == "decorated": + return [HistoricalKlinesData(row) for row in output_data] + else: + return output_data def get_historical_klines_generator(self, symbol, interval, start_str, end_str=None, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): diff --git a/binance/decorators.py b/binance/decorators.py new file mode 100644 index 000000000..25373de54 --- /dev/null +++ b/binance/decorators.py @@ -0,0 +1,54 @@ + + + +from functools import cached_property +from datetime import datetime + +class HistoricalKlinesData(): + # https://github.com/binance-us/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data + # + # [ + # 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 + # ] + + def __init__(self, raw_data): + self.raw_data = raw_data + + @cached_property + def open_at(self): + return datetime.fromtimestamp(self.raw_data[0] / 1000) + + @cached_property + def close_at(self): + return datetime.fromtimestamp(self.raw_data[6] / 1000) + + @property + def open(self): + return float(self.raw_data[1]) + + @property + def high(self): + return float(self.raw_data[2]) + + @property + def low(self): + return float(self.raw_data[3]) + + @property + def close(self): + return float(self.raw_data[4]) + + @property + def volume(self): + return float(self.raw_data[5]) diff --git a/binance/enums.py b/binance/enums.py index 9fdbe6284..137dc517a 100644 --- a/binance/enums.py +++ b/binance/enums.py @@ -62,6 +62,14 @@ MARGIN_BUY_TYPE = 'MARGIN_BUY' AUTO_REPAY_TYPE = 'AUTO_REPAY' +# the names of candlestick data columns, in order +# https://github.com/binance-us/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data +OHLC_COLUMNS = [ + 'open_time', 'open', 'high', 'low', 'close', 'volume', 'close_time', + 'quote_asset_volume', 'number_of_trades', 'taker_buy_base_asset_volume', 'taker_buy_quote_asset_volume', + 'ignore' +] + class HistoricalKlinesType(Enum): SPOT = 1 diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..1de42a564 --- /dev/null +++ b/conftest.py @@ -0,0 +1,17 @@ + + + +ohlc_row = [ + 1519892340000, + "0.00099400", + "0.00099810", + "0.00099400", + "0.00099810", + "4806.04000000", + 1519892399999, + "4.78553253", + 154, + "1785.14000000", + "1.77837524", + "0", +] diff --git a/requirements.txt b/requirements.txt index 041d9ae64..8b96fdbbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ dateparser requests ujson websockets==9.1 +pandas diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_decorators.py b/tests/test_decorators.py new file mode 100644 index 000000000..ba6091027 --- /dev/null +++ b/tests/test_decorators.py @@ -0,0 +1,17 @@ + +from datetime import datetime + +from binance.decorators import HistoricalKlinesData + +from conftest import ohlc_row + +def test_historical_klines_decorator(): + + obj = HistoricalKlinesData(ohlc_row) + assert isinstance(obj.open_at, datetime) + assert isinstance(obj.close_at, datetime) + assert isinstance(obj.open, float) + assert isinstance(obj.high, float) + assert isinstance(obj.low, float) + assert isinstance(obj.close, float) + assert isinstance(obj.volume, float) diff --git a/tests/test_historical_klines.py b/tests/test_historical_klines.py index 18969d77a..17f071a3e 100644 --- a/tests/test_historical_klines.py +++ b/tests/test_historical_klines.py @@ -2,53 +2,58 @@ # coding=utf-8 from binance.client import Client +from binance.enums import OHLC_COLUMNS +from binance.decorators import HistoricalKlinesData + +from pandas import DataFrame + import pytest import requests_mock client = Client("api_key", "api_secret") +first_available_res = [ + [ + 1500004800000, + "0.00005000", + "0.00005300", + "0.00001000", + "0.00004790", + "663152.00000000", + 1500004859999, + "30.55108144", + 43, + "559224.00000000", + "25.65468144", + "83431971.04346950", + ] +] -def test_exact_amount(): - """Test Exact amount returned""" +first_res = [] +row = [ + 1519892340000, + "0.00099400", + "0.00099810", + "0.00099400", + "0.00099810", + "4806.04000000", + 1519892399999, + "4.78553253", + 154, + "1785.14000000", + "1.77837524", + "0", +] - first_available_res = [ - [ - 1500004800000, - "0.00005000", - "0.00005300", - "0.00001000", - "0.00004790", - "663152.00000000", - 1500004859999, - "30.55108144", - 43, - "559224.00000000", - "25.65468144", - "83431971.04346950", - ] - ] +for i in range(0, 500): + first_res.append(row) - first_res = [] - row = [ - 1519892340000, - "0.00099400", - "0.00099810", - "0.00099400", - "0.00099810", - "4806.04000000", - 1519892399999, - "4.78553253", - 154, - "1785.14000000", - "1.77837524", - "0", - ] +second_res = [] - for i in range(0, 500): - first_res.append(row) - second_res = [] +def test_exact_amount(): + """Test Exact amount returned""" with requests_mock.mock() as m: m.get( @@ -280,3 +285,57 @@ def test_historical_kline_generator_empty_response(): with pytest.raises(StopIteration): next(klines) + + + + + + +def test_test_historical_kline_output_format(): + """Test output format""" + + with requests_mock.mock() as m: + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + json=first_available_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=500&startTime=1519862400000&symbol=BNBBTC", + json=first_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=500&startTime=1519892400000&symbol=BNBBTC", + json=second_res, + ) + + # + # DEFAULT FORMAT + # + klines_list = client.get_historical_klines( + symbol="BNBBTC", interval=Client.KLINE_INTERVAL_1MINUTE, start_str="1st March 2018" + ) + assert len(klines_list) == 500 + assert isinstance(klines_list, list) + assert isinstance(klines_list[0], list) + + # + # DATAFRAME FORMAT + # + klines_df = client.get_historical_klines( + symbol="BNBBTC", interval=Client.KLINE_INTERVAL_1MINUTE, start_str="1st March 2018", + output_format="df" + ) + assert len(klines_df) == 500 + assert isinstance(klines_df, DataFrame) + assert klines_df.columns.tolist() == OHLC_COLUMNS + + # + # DECORATED FORMAT + # + klines_data = client.get_historical_klines( + symbol="BNBBTC", interval=Client.KLINE_INTERVAL_1MINUTE, start_str="1st March 2018", + output_format="decorated" + ) + assert len(klines_data) == 500 + assert isinstance(klines_data, list) + assert isinstance(klines_data[0], HistoricalKlinesData)