|
|
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)
|