From d7bf15b1d8e67ddcf1ace0e286b0123331642bd8 Mon Sep 17 00:00:00 2001 From: lucky7323 Date: Mon, 24 Aug 2020 04:57:25 +0900 Subject: [PATCH 1/3] Added historical kline tests for 1M interval --- tests/test_historical_klines.py | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/tests/test_historical_klines.py b/tests/test_historical_klines.py index dd3eb8228..d03448d91 100644 --- a/tests/test_historical_klines.py +++ b/tests/test_historical_klines.py @@ -245,3 +245,120 @@ def test_historical_kline_generator_empty_response(): with pytest.raises(StopIteration): next(klines) + + +def test_historical_kline_with_month_interval(): + """Test historical klines with one month interval""" + + first_available_res = [ + [ + 1498867200000, + "0.00005000", + "0.00005480", + "0.00001000", + "0.00003654", + "61059068.00000000", + 1501545599999, + "2572.23205388", + 33297, + "33906053.00000000", + "1442.17447471", + "100206524.84393587" + ] + ] + first_res = [] + row = [ + 1519862400000, + "0.00101270", + "0.00167650", + "0.00083250", + "0.00159650", + "122814213.69000000", + 1522540799999, + "142681.39725065", + 3242765, + "68994444.35000000", + "79545.22096745", + "0" + ] + + for i in range(0, 8): + first_res.append(row) + + with requests_mock.mock() as m: + m.get( + "https://api.binance.com/api/v1/klines?interval=1M&limit=1&startTime=0&symbol=BNBBTC", + json=first_available_res, + ) + m.get( + "https://api.binance.com/api/v1/klines?interval=1M&limit=500&startTime=1519862400000&endTime=1539234000000&symbol=BNBBTC", + json=first_res, + ) + klines = client.get_historical_klines( + symbol="BNBBTC", + interval=Client.KLINE_INTERVAL_1MONTH, + start_str="1st March 2018", + end_str="11st Oct 2018 05:00:00", + ) + assert len(klines) == 8 + + +def test_historical_kline_generator_with_month_interval(): + """Test historical klines generator with one month interval""" + + first_available_res = [ + [ + 1498867200000, + "0.00005000", + "0.00005480", + "0.00001000", + "0.00003654", + "61059068.00000000", + 1501545599999, + "2572.23205388", + 33297, + "33906053.00000000", + "1442.17447471", + "100206524.84393587" + ] + ] + first_res = [] + row = [ + 1519862400000, + "0.00101270", + "0.00167650", + "0.00083250", + "0.00159650", + "122814213.69000000", + 1522540799999, + "142681.39725065", + 3242765, + "68994444.35000000", + "79545.22096745", + "0" + ] + + for i in range(0, 8): + first_res.append(row) + + with requests_mock.mock() as m: + m.get( + "https://api.binance.com/api/v1/klines?interval=1M&limit=1&startTime=0&symbol=BNBBTC", + json=first_available_res, + ) + m.get( + "https://api.binance.com/api/v1/klines?interval=1M&limit=500&startTime=1519862400000&endTime=1539234000000&symbol=BNBBTC", + json=first_res, + ) + klines = client.get_historical_klines_generator( + symbol="BNBBTC", + interval=Client.KLINE_INTERVAL_1MONTH, + start_str=1519862400000, + end_str=1539234000000, + ) + + for i in range(8): + assert len(next(klines)) > 0 + + with pytest.raises(StopIteration): + next(klines) From 12896d4961ad8b8fc6ee9522651b7ab76640568c Mon Sep 17 00:00:00 2001 From: lucky7323 Date: Mon, 24 Aug 2020 05:11:22 +0900 Subject: [PATCH 2/3] Add 1 Month Interval(1M) for Historical Klines --- binance/client.py | 12 +++++++++--- binance/helpers.py | 18 ++++++++++++++++++ docs/helpers.rst | 2 +- examples/save_historical_data.py | 29 +++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/binance/client.py b/binance/client.py index 432a53a30..1fec1f411 100644 --- a/binance/client.py +++ b/binance/client.py @@ -5,7 +5,7 @@ import requests import time from operator import itemgetter -from .helpers import date_to_milliseconds, interval_to_milliseconds +from .helpers import date_to_milliseconds, interval_to_milliseconds, increment_month from .exceptions import BinanceAPIException, BinanceRequestException, BinanceWithdrawException @@ -831,7 +831,10 @@ def get_historical_klines(self, symbol, interval, start_str, end_str=None, break # increment next call by our timeframe - start_ts += timeframe + if interval == self.KLINE_INTERVAL_1MONTH: + start_ts = increment_month(start_ts) + else: + start_ts += timeframe # sleep after every 3rd call to be kind to the API if idx % 3 == 0: @@ -912,7 +915,10 @@ def get_historical_klines_generator(self, symbol, interval, start_str, end_str=N break # increment next call by our timeframe - start_ts += timeframe + if interval == self.KLINE_INTERVAL_1MONTH: + start_ts = increment_month(start_ts) + else: + start_ts += timeframe # sleep after every 3rd call to be kind to the API if idx % 3 == 0: diff --git a/binance/helpers.py b/binance/helpers.py index fe91df3f4..d92ad5621 100644 --- a/binance/helpers.py +++ b/binance/helpers.py @@ -4,6 +4,7 @@ import pytz from datetime import datetime +from dateutil.relativedelta import relativedelta def date_to_milliseconds(date_str): @@ -50,3 +51,20 @@ def interval_to_milliseconds(interval): return int(interval[:-1]) * seconds_per_unit[interval[-1]] * 1000 except (ValueError, KeyError): return None + + +def increment_month(origin_ts): + """Increment a given timestamp by one month + + :param origin_ts: original timestamp, e.g.: 1501545600000, 1504224000000, ... + :type origin_ts: int + + :return: + Timestamp incremented by one month from a given timestamp + """ + d = datetime.fromtimestamp(origin_ts/1000) + relativedelta(months=1) + # if the date is not timezone aware apply UTC timezone + if d.tzinfo is None or d.tzinfo.utcoffset(d) is None: + d = d.replace(tzinfo=pytz.utc) + + return int(datetime.timestamp(d)) diff --git a/docs/helpers.rst b/docs/helpers.rst index 2820ff65f..6981244f5 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -2,5 +2,5 @@ Helper Functions ================ .. autoclass:: binance.helpers - :members: date_to_milliseconds, interval_to_milliseconds + :members: date_to_milliseconds, interval_to_milliseconds, increment_month :noindex: diff --git a/examples/save_historical_data.py b/examples/save_historical_data.py index c5aebecd8..502520912 100644 --- a/examples/save_historical_data.py +++ b/examples/save_historical_data.py @@ -4,6 +4,8 @@ import json from datetime import datetime + +from dateutil.relativedelta import relativedelta from binance.client import Client @@ -57,6 +59,23 @@ def interval_to_milliseconds(interval): return ms +def increment_month(origin_ts): + """Increment a given timestamp by one month + + :param origin_ts: original timestamp, e.g.: 1501545600000, 1504224000000, ... + :type origin_ts: int + + :return: + Timestamp incremented by one month from a given timestamp + """ + d = datetime.fromtimestamp(origin_ts/1000) + relativedelta(months=1) + # if the date is not timezone aware apply UTC timezone + if d.tzinfo is None or d.tzinfo.utcoffset(d) is None: + d = d.replace(tzinfo=pytz.utc) + + return int(datetime.timestamp(d)) + + def get_historical_klines(symbol, interval, start_str, end_str=None): """Get Historical Klines from Binance @@ -118,10 +137,16 @@ def get_historical_klines(symbol, interval, start_str, end_str=None): output_data += temp_data # update our start timestamp using the last value in the array and add the interval timeframe - start_ts = temp_data[len(temp_data) - 1][0] + timeframe + if interval == Client.KLINE_INTERVAL_1MONTH: + start_ts = increment_month(temp_data[len(temp_data) - 1][0]) + else: + start_ts = temp_data[len(temp_data) - 1][0] + timeframe else: # it wasn't listed yet, increment our start date - start_ts += timeframe + if interval == Client.KLINE_INTERVAL_1MONTH: + start_ts = increment_month(start_ts) + else: + start_ts += timeframe idx += 1 # check if we received less than the required limit and exit the loop From 9d75683726a72996a5a6f1c58101310e13337e31 Mon Sep 17 00:00:00 2001 From: pga Date: Mon, 28 Mar 2022 18:31:59 +0200 Subject: [PATCH 3/3] Fix passing start_ts > end_ts when fetching historical klines When end_ts is passed historical klines are fetched until Binance API returns no results. With every query start_ts is incremented. For the most part this cause one unnecessary query to Binance to be made, where start_ts is bigger than end_ts. Moreover, when fetching klines for future markets (not spot), Binance API returns an error in such condition (-1023). This makes it impossible to fetch klines for future markets when providing end_str param. --- binance/client.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/binance/client.py b/binance/client.py index 1fec1f411..d6c690b90 100644 --- a/binance/client.py +++ b/binance/client.py @@ -835,6 +835,10 @@ def get_historical_klines(self, symbol, interval, start_str, end_str=None, start_ts = increment_month(start_ts) else: start_ts += timeframe + + # check if end_ts is reached + if end_ts is not None and start_ts >= end_ts: + break # sleep after every 3rd call to be kind to the API if idx % 3 == 0: @@ -919,6 +923,10 @@ def get_historical_klines_generator(self, symbol, interval, start_str, end_str=N start_ts = increment_month(start_ts) else: start_ts += timeframe + + # check if end_ts is reached + if end_ts is not None and start_ts >= end_ts: + break # sleep after every 3rd call to be kind to the API if idx % 3 == 0: