sammchardy / python-binance

Binance Exchange API python implementation for automated trading
https://python-binance.readthedocs.io/en/latest/
MIT License
6.01k stars 2.2k forks source link

What kind of request_params can be passed in Client module? #659

Open mshmurda opened 3 years ago

mshmurda commented 3 years ago

Describe the bug I run receive the error:

requests.exceptions.SSLError: HTTPSConnectionPool(host='api.binance.us', port=443): Max retries exceeded with url: /api/v1/ping (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available."))

which I think occurs when I reach the API rate limit. When calling the Client module (below) there is the option to pass requests parameters. I have looked through the documentation but could not find any examples of what these parameters can be.

class binance.client.Client(api_key=None, api_secret=None, requests_params=None, tld='us')

Does anyone know what kind of params might be useful for not reaching the API rate limit?

**Environment

uneasyguy commented 3 years ago

To avoid hitting the API rate limit, you'll want to take a look at the headers that are returned by Binance on each call.

If you take a look at line 196 of client.py, you'll see that it's setting self.response equal to the response from Binance:

self.response = getattr(self.session, method)(uri, **kwargs)

The "self" in question, is the Client object created previously. This means we'll have access to the headers. Below is a link to an overly simplistic approach to avoiding rate limits. Please note that you can use up to 1200 weight per minute, and the threshold of 6 below is simply for demonstration purposes.

https://gist.github.com/uneasyguy/2f2053e918e773791991bfe60060d6d8

gandy92 commented 1 year ago

You could also modify the client to automatically reduce the rate, at least in case of the non-async client:

from __future__ import annotations

import logging
import time
from datetime import datetime

import requests
from binance.exceptions import BinanceAPIException
import binance

logger = logging.getLogger(__name__)

author = ""

from functools import wraps

def rate_limiter(func, client_obj: binance.client, available_weight_1m=1150, delay_buffer_sec=5):
    used_api_weight = 0
    last_call_ts = time.time()

    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal used_api_weight
        nonlocal last_call_ts
        while True:
            sec_elapsed = time.time() - last_call_ts
            seconds_into_current_minute = datetime.now().second
            if available_weight_1m <= used_api_weight:
                logging.debug(f"{available_weight_1m=} < {used_api_weight=}, {sec_elapsed=}")
                wait_seconds = 60 + delay_buffer_sec - seconds_into_current_minute - sec_elapsed
                if wait_seconds > 0:
                    logging.warning(
                        f"Last call from {sec_elapsed:.2f} sec ago hit {used_api_weight}. Sleep for {wait_seconds:.0f} sec."
                    )
                    time.sleep(wait_seconds)
            last_call_ts = time.time()
            try:
                result = func(*args, **kwargs)
                response: requests.Response = client_obj.response
                used_api_weight = int(response.headers.get("x-mbx-used-weight-1m", 0))
                logging.debug(f"Already used {used_api_weight} API weight, {seconds_into_current_minute=}.")
                return result
            except BinanceAPIException as e:
                if e.status_code in [429, 418]:
                    response: requests.Response = e.response
                    wait_seconds = int(response.headers.get("Retry-After", 66))
                    logging.warning(f"API instructs us to wait for {wait_seconds} seconds ({e.status_code}).")
                    time.sleep(wait_seconds)

    return wrapper

def main():
    logging.basicConfig(level=logging.DEBUG)
    try:

        client = binance.Client()
        client._request = rate_limiter(client._request, client, available_weight_1m=20)
        for _ in range(15):
            client.ping()

    except Exception as e:
        print(f"Whoops, we ran into the following error: {e}")

if __name__ == "__main__":
    main()