You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

457 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)