from binance import OrderType, OrderStatus, OrderSide, RequestMethod,Interval import requests import time import hmac import hashlib from threading import Lock from utils import config from utils import utility class BinanceSpotHttp(object): ''' # https://binance-docs.github.io/apidocs/spot/cn/ # 152d3db758 # 如果上面的baseURL访问有性能问题,请访问下面的API集群: # https://api1.binance.com # https://api2.binance.com # https://api3.binance.com # https://api4.binance.com # ORDERS rateLimits 100次/10s 200000次/1day ''' def __init__(self, api_key=None, secret=None, host=None, proxy_host=None, proxy_port=0, timeout=5, try_counts=5): self.api_key = api_key self.secret = secret self.host = host if host else "https://api.binance.com" self.recv_window = 10000 self.timeout = timeout self.order_count_lock = Lock() self.order_count = 1_000_000 self.try_counts = try_counts # 失败尝试的次数. self.proxy_host = proxy_host self.proxy_port = proxy_port @property def proxies(self): if self.proxy_host and self.proxy_port: proxy = f"http://{self.proxy_host}:{self.proxy_port}" return {"http": proxy, "https": proxy} return None def build_parameters(self, params: dict): keys = list(params.keys()) keys.sort() return '&'.join([f"{key}={params[key]}" for key in params.keys()]) def request(self, req_method: RequestMethod, path: str, requery_dict=None, verify=False): url = self.host + path if verify: query_str = self._sign(requery_dict) url += '?' + query_str elif requery_dict: url += '?' + self.build_parameters(requery_dict) headers = {"X-MBX-APIKEY": self.api_key} for i in range(0, self.try_counts): try: response = requests.request(req_method.value, url=url, headers=headers, timeout=self.timeout, proxies=self.proxies) http_code = response.status_code if http_code == 200: return response.json() elif http_code == 400 and "Account has insufficient balance for requested action." in str(response.json()): print(f"交易对: {config.symbol} 余额不足") break elif http_code == 400 and "Filter failure: MIN_NOTIONAL" in str(response.json()): print(f"交易对: {config.symbol} 最小下单数量不够,多买点") break elif http_code == 400 and "Unknown order sent." in str(response.json()): print(f"交易对: {config.symbol} 订单号错误") print(f"请求结果{str(response.json())}") break elif http_code == 400 and "Illegal characters found in parameter 'symbol'" in str(response.json()): print(f"交易对: {config.symbol} 交易对配置错误,无法识别") break elif http_code == 400 and "This action disabled is on this account" in str(response.json()): print(f"交易对: {config.symbol} 账号无权此操作") break else: print("未知请求错误:") print(response.json(), http_code) except Exception as error: print(f"请求:{path}, 发生了错误: {error}") time.sleep(3) def get_ping(self): #测试服务器连通性 #测试能否联通 Rest API。 path = '/api/v3/ping' return self.request(req_method=RequestMethod.GET, path=path) def get_server_time(self): #获取服务器时间 #测试能否联通 Rest API 并 获取服务器时间。 path = '/api/v3/time' return self.request(req_method=RequestMethod.GET, path=path) def get_exchange_info(self): #交易规范信息 #获取交易规则和交易对信息。 """ return: the exchange info in json format: {'timezone': 'UTC', 'serverTime': 1570802268092, 'rateLimits': [{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}, {'rateLimitType': 'ORDERS', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}], 'exchangeFilters': [], 'symbols': [{'symbol': 'BTCUSDT', 'status': 'TRADING', 'maintMarginPercent': '2.5000', 'requiredMarginPercent': '5.0000', 'baseAsset': 'BTC', 'quoteAsset': 'USDT', 'pricePrecision': 2, 'quantityPrecision': 3, 'baseAssetPrecision': 8, 'quotePrecision': 8, 'filters': [{'minPrice': '0.01', 'maxPrice': '100000', 'filterType': 'PRICE_FILTER', 'tickSize': '0.01'}, {'stepSize': '0.001', 'filterType': 'LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, {'stepSize': '0.001', 'filterType': 'MARKET_LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, {'limit': 200, 'filterType': 'MAX_NUM_ORDERS'}, {'multiplierDown': '0.8500', 'multiplierUp': '1.1500', 'multiplierDecimal': '4', 'filterType': 'PERCENT_PRICE'}], 'orderTypes': ['LIMIT', 'MARKET', 'STOP'], 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX']}]} """ path = '/api/v3/exchangeInfo' return self.request(req_method=RequestMethod.GET, path=path) def get_order_book(self, symbol, limit=5): """ 深度信息 https://binance-docs.github.io/apidocs/spot/cn/#e7746f7d60 :param symbol: BTCUSDT, BNBUSDT ect, 交易对. :param limit: market depth. 默认 100; 最大 5000. 可选值:[5, 10, 20, 50, 100, 500, 1000, 5000] :return: return order_book in json 返回订单簿,json数据格式. """ limits = [5, 10, 20, 50, 100, 500, 1000] if limit not in limits: limit = 5 path = "/api/v3/depth" query_dict = {"symbol": symbol, "limit": limit } return self.request(RequestMethod.GET, path, query_dict) def get_recent_trades(self, symbol, limit=5): """ 近期成交列表 https://binance-docs.github.io/apidocs/spot/cn/#38a975b802 :param symbol: BTCUSDT, BNBUSDT ect, 交易对. :param limit: 默认 500; 最大值 1000. :return: return order_book in json 返回订单簿,json数据格式. """ if limit >= 1000: limit = 1000 path = "/api/v3/trades" query_dict = {"symbol": symbol, "limit": limit } return self.request(RequestMethod.GET, path, query_dict) def get_kline(self, symbol, interval: Interval, start_time=None, end_time=None, limit=500, max_try_time=10): """ 获取K线数据. 每根K线代表一个交易对。 每根K线的开盘时间可视为唯一ID https://binance-docs.github.io/apidocs/spot/cn/#c59e471e81 :param symbol: :param interval: 间隔 :param start_time: :param end_time: :param limit: 默认 500; 最大 1000 :param max_try_time: :return: [ [ 1499040000000, // 开盘时间 "0.01634790", // 开盘价 "0.80000000", // 最高价 "0.01575800", // 最低价 "0.01577100", // 收盘价(当前K线未结束的即为最新价) "148976.11427815", // 成交量 1499644799999, // 收盘时间 "2434.19055334", // 成交额 308, // 成交笔数 "1756.87402397", // 主动买入成交量 "28.46694368", // 主动买入成交额 "17928899.62484339" // 请忽略该参数 ] ] """ path = "/api/v3/klines" query_dict = { "symbol": symbol, "interval": interval, "limit": limit } # query_dict = { # "symbol": symbol, # "interval": interval.value, # "limit": limit # } if start_time: query_dict['startTime'] = start_time if end_time: query_dict['endTime'] = end_time for i in range(max_try_time): data = self.request(RequestMethod.GET, path, query_dict) if isinstance(data, list) and len(data): return data def get_latest_price(self, symbol): """ 获取交易对最新价格 不发送交易对参数,则会返回所有交易对信息 权重: 1 单一交易对; 2 交易对参数缺失 :param symbol: 获取最新的价格. :return: {'symbol': 'BTCUSDT', 'price': '9168.90000000'} """ path = "/api/v3/ticker/price" query_dict = {"symbol": symbol} return self.request(RequestMethod.GET, path, query_dict) def get_avg_price_5min(self, symbol): """ 当前平均价格 in last 5min 权重: 1 :param symbol: :return: { "mins": 5, "price": "9.35751834" } """ path = "/api/v3/avgPrice" query_dict = {"symbol": symbol} return self.request(RequestMethod.GET, path, query_dict) def get_price_info_24h(self, symbol): """ 24hr 价格变动情况 24 小时滚动窗口价格变动数据。 请注意,不携带symbol参数会返回全部交易对数据,不仅数据庞大,而且权重极高 权重: 1 单一交易对; 40 交易对参数缺失; 请注意,不携带symbol参数会返回全部交易对数据 :param symbol: :return: { "symbol": "BNBBTC", "priceChange": "-94.99999800", "priceChangePercent": "-95.960", "weightedAvgPrice": "0.29628482", "prevClosePrice": "0.10002000", "lastPrice": "4.00000200", "lastQty": "200.00000000", "bidPrice": "4.00000000", "askPrice": "4.00000200", "openPrice": "99.00000000", "highPrice": "100.00000000", "lowPrice": "0.10000000", "volume": "8913.30000000", "quoteVolume": "15.30000000", "openTime": 1499783499040, "closeTime": 1499869899040, "firstId": 28385, // 首笔成交id "lastId": 28460, // 末笔成交id "count": 76 // 成交笔数 } """ path = "/api/v3/ticker/24hr" query_dict = {"symbol": symbol} return self.request(RequestMethod.GET, path, query_dict) def get_ticker(self, symbol): """ 当前最优挂单 返回当前最优的挂单(最高买单,最低卖单) bid price的本意是出价 最高买价 ask price的本意是要价 最低卖价 ask是“要”的意思,就卖家喊出来的价 bid是“投标”的意思,是买家愿意出的最高价。 :param symbol: 交易对 :return: 返回的数据如下: { 'symbol': 'BTCUSDT', 'bidPrice': '9168.50000000', 'bidQty': '1.27689900', 'askPrice': '9168.51000000', 'askQty': '0.93307800' } """ path = "/api/v3/ticker/bookTicker" query_dict = {"symbol": symbol} return self.request(RequestMethod.GET, path, query_dict) def get_client_order_id(self): """ generate the client_order_id for user. :return: """ with self.order_count_lock: self.order_count += 1 return config.symbol + str(utility.get_current_timestamp()) + str(self.order_count) def _sign(self, params): """ 签名的方法, signature for the private request. :param params: request parameters :return: """ query_string = self.build_parameters(params) hex_digest = hmac.new(self.secret.encode('utf8'), query_string.encode("utf-8"), hashlib.sha256).hexdigest() return query_string + '&signature=' + str(hex_digest) def place_order(self, symbol: str, order_side: OrderSide, order_type: OrderType, quantity: float, price: float, client_order_id: str = None, time_inforce="GTC", stop_price=0): """ :param symbol: 交易对名称 :param order_side: 买或者卖, BUY or SELL :param order_type: 订单类型 LIMIT or other order type. :param quantity: 数量 :param price: 价格. :param client_order_id: 用户的订单ID :param time_inforce: :param stop_price: :return: """ path = '/api/v3/order' if client_order_id is None: client_order_id = self.get_client_order_id() params = { "symbol": symbol, "side": order_side.value, "type": order_type.value, "quantity": quantity, "price": price, "recvWindow": self.recv_window, "timestamp": utility.get_current_timestamp(), "newClientOrderId": client_order_id } if order_type == OrderType.LIMIT: params['timeInForce'] = time_inforce if order_type == OrderType.MARKET: if params.get('price'): del params['price'] if order_type == OrderType.STOP: if stop_price > 0: params["stopPrice"] = stop_price else: raise ValueError("stopPrice must greater than 0") return self.request(RequestMethod.POST, path=path, requery_dict=params, verify=True) def get_order(self, symbol: str, client_order_id: str): """ 获取订单状态. :param symbol: :param client_order_id: :return: """ path = "/api/v3/order" prams = {"symbol": symbol, "timestamp": utility.get_current_timestamp(), "origClientOrderId": client_order_id} return self.request(RequestMethod.GET, path, prams, verify=True) def cancel_order(self, symbol, client_order_id): """ 撤销订单. :param symbol: :param client_order_id: :return: """ path = "/api/v3/order" params = {"symbol": symbol, "timestamp": utility.get_current_timestamp(), "origClientOrderId": client_order_id } for i in range(0, 3): try: order = self.request(RequestMethod.DELETE, path, params, verify=True) return order except Exception as error: print(f'取消订单失败:{error}') return def get_open_orders(self, symbol=None): """ 获取所有的订单. :param symbol: BNBUSDT, or BTCUSDT etc. :return: """ path = "/api/v3/openOrders" params = {"timestamp": utility.get_current_timestamp()} if symbol: params["symbol"] = symbol return self.request(RequestMethod.GET, path, params, verify=True) def cancel_open_orders(self, symbol): """ 撤销某个交易对的所有挂单 :param symbol: symbol :return: return a list of orders. """ path = "/api/v3/openOrders" params = {"timestamp": utility.get_current_timestamp(), "recvWindow": self.recv_window, "symbol": symbol } return self.request(RequestMethod.DELETE, path, params, verify=True) def get_account_info(self): """ {'feeTier': 2, 'canTrade': True, 'canDeposit': True, 'canWithdraw': True, 'updateTime': 0, 'totalInitialMargin': '0.00000000', 'totalMaintMargin': '0.00000000', 'totalWalletBalance': '530.21334791', 'totalUnrealizedProfit': '0.00000000', 'totalMarginBalance': '530.21334791', 'totalPositionInitialMargin': '0.00000000', 'totalOpenOrderInitialMargin': '0.00000000', 'maxWithdrawAmount': '530.2133479100000', 'assets': [{'asset': 'USDT', 'walletBalance': '530.21334791', 'unrealizedProfit': '0.00000000', 'marginBalance': '530.21334791', 'maintMargin': '0.00000000', 'initialMargin': '0.00000000', 'positionInitialMargin': '0.00000000', 'openOrderInitialMargin': '0.00000000', 'maxWithdrawAmount': '530.2133479100000'}]} :return: """ path = "/api/v3/account" params = {"timestamp": utility.get_current_timestamp(), "recvWindow": self.recv_window } return self.request(RequestMethod.GET, path, params, verify=True)