async def _check_connection(self, *args, **kwargs) -> None: """Check Websocket connection, if connection closed, re-connect immediately.""" if not self.ws: logger.warn("Websocket connection not connected yet!", caller=self) return if self.ws.closed: SingleTask.run(self.reconnect)
async def revoke_orders(self, symbol, order_ids=None, client_oids=None): """Cancelling multiple open orders with order_id,Maximum 10 orders can be cancelled at a time for each trading pair. Args: symbol: Trading pair, e.g. `BTC-USDT`. order_ids: Order id list, default is `None`. client_oids: Client order id list, default is `None`. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. Notes: `order_ids` and `order_oids` must exist one, using order_ids first. """ uri = "/api/spot/v3/cancel_batch_orders" if order_ids: if len(order_ids) > 10: logger.warn("only revoke 10 orders per request!", caller=self) body = [{"instrument_id": symbol, "order_ids": order_ids[:10]}] elif client_oids: if len(client_oids) > 10: logger.warn("only revoke 10 orders per request!", caller=self) body = [{"instrument_id": symbol, "client_oids": client_oids[:10]}] else: return None, "order id list error!" result, error = await self.request("POST", uri, body=body, auth=True) return result, error
async def process(self, msg): """Process message that received from Websocket connection. Args: msg: message received from Websocket connection. """ logger.debug("msg:", json.dumps(msg), caller=self) e = msg.get("e") if e == "executionReport": # Order update. if msg["s"] != self._raw_symbol: return order_id = str(msg["i"]) if msg["X"] == "NEW": status = ORDER_STATUS_SUBMITTED elif msg["X"] == "PARTIALLY_FILLED": status = ORDER_STATUS_PARTIAL_FILLED elif msg["X"] == "FILLED": status = ORDER_STATUS_FILLED elif msg["X"] == "CANCELED": status = ORDER_STATUS_CANCELED elif msg["X"] == "REJECTED": status = ORDER_STATUS_FAILED elif msg["X"] == "EXPIRED": status = ORDER_STATUS_FAILED else: logger.warn("unknown status:", msg, caller=self) SingleTask.run(self._error_callback, "order status error.") return order = self._orders.get(order_id) if not order: info = { "platform": self._platform, "account": self._account, "strategy": self._strategy, "order_id": order_id, "client_order_id": msg["c"], "action": ORDER_ACTION_BUY if msg["S"] == "BUY" else ORDER_ACTION_SELL, "order_type": ORDER_TYPE_LIMIT if msg["o"] == "LIMIT" else ORDER_TYPE_MARKET, "symbol": self._symbol, "price": msg["p"], "quantity": msg["q"], "ctime": msg["O"] } order = Order(**info) self._orders[order_id] = order order.remain = float(msg["q"]) - float(msg["z"]) order.status = status order.utime = msg["T"] SingleTask.run(self._order_update_callback, copy.copy(order)) if status in [ ORDER_STATUS_FAILED, ORDER_STATUS_CANCELED, ORDER_STATUS_FILLED ]: self._orders.pop(order_id)
async def revoke_order(self, *order_ids): """Revoke (an) order(s). Args: order_ids: Order id list, you can set this param to 0 or multiple items. If you set 0 param, you can cancel all orders for this symbol(initialized in Trade object). If you set 1 param, you can cancel an order. If you set multiple param, you can cancel multiple orders. Do not set param length more than 100. Returns: Success or error, see bellow. NOTEs: DO NOT INPUT MORE THAT 10 ORDER IDs, you can invoke many times. """ # If len(order_ids) == 0, you will cancel all orders for this symbol(initialized in Trade object). if len(order_ids) == 0: order_infos, error = await self._rest_api.get_open_orders( self._raw_symbol) if error: SingleTask.run(self._error_callback, error) return False, error if len(order_infos) > 100: logger.warn("order length too long! (more than 100)", caller=self) for order_info in order_infos: order_id = order_info["order_id"] _, error = await self._rest_api.revoke_order( self._raw_symbol, order_id) if error: SingleTask.run(self._error_callback, error) return False, error return True, None # If len(order_ids) == 1, you will cancel an order. if len(order_ids) == 1: success, error = await self._rest_api.revoke_order( self._raw_symbol, order_ids[0]) if error: SingleTask.run(self._error_callback, error) return order_ids[0], error else: return order_ids[0], None # If len(order_ids) > 1, you will cancel multiple orders. if len(order_ids) > 1: success, error = [], [] for order_id in order_ids: _, e = await self._rest_api.revoke_order( self._raw_symbol, order_id) if e: SingleTask.run(self._error_callback, e) error.append((order_id, e)) else: success.append(order_id) return success, error
async def publish(self, event): """Publish a event. Args: event: A event to publish. """ if not self._connected: logger.warn("RabbitMQ not ready right now!", caller=self) return data = event.dumps() await self._channel.basic_publish(payload=data, exchange_name=event.exchange, routing_key=event.routing_key)
async def connected_callback(self): """After websocket connection created successfully, pull back all open order information.""" logger.info("Websocket connection authorized successfully.", caller=self) order_infos, error = await self._rest_api.get_open_orders(self._raw_symbol) if error: e = Error("get open orders error: {}".format(error)) SingleTask.run(self._error_callback, e) SingleTask.run(self._init_callback, False) return for order_info in order_infos: if order_info["status"] == "NEW": status = ORDER_STATUS_SUBMITTED elif order_info["status"] == "PARTIALLY_FILLED": status = ORDER_STATUS_PARTIAL_FILLED elif order_info["status"] == "FILLED": status = ORDER_STATUS_FILLED elif order_info["status"] == "CANCELED": status = ORDER_STATUS_CANCELED elif order_info["status"] == "REJECTED": status = ORDER_STATUS_FAILED elif order_info["status"] == "EXPIRED": status = ORDER_STATUS_FAILED else: logger.warn("unknown status:", order_info, caller=self) SingleTask.run(self._error_callback, "order status error.") continue order_id = str(order_info["orderId"]) info = { "platform": self._platform, "account": self._account, "strategy": self._strategy, "order_id": order_id, "client_order_id": order_info["clientOrderId"], "action": ORDER_ACTION_BUY if order_info["side"] == "BUY" else ORDER_ACTION_SELL, "order_type": ORDER_TYPE_LIMIT if order_info["type"] == "LIMIT" else ORDER_TYPE_MARKET, "symbol": self._symbol, "price": order_info["price"], "quantity": order_info["origQty"], "remain": float(order_info["origQty"]) - float(order_info["executedQty"]), "status": status, "avg_price": order_info["price"], "ctime": order_info["time"], "utime": order_info["updateTime"] } order = Order(**info) self._orders[order_id] = order SingleTask.run(self._order_update_callback, copy.copy(order)) SingleTask.run(self._init_callback, True)
async def dynamic_trade_with_binance(self, *args, **kwargs): """简化版的吃盘口毛刺策略""" if not self._is_ok: return success, error = await self._trade.rest_api.get_orderbook( self._symbol, 10) if error: # 通过 钉钉、微信等发送通知... # 或 接入风控系统; self._is_ok = False logger.warn("error: ", error, caller=self) return ask6_price = float(success["asks"][5][0]) ask8_price = float(success["asks"][7][0]) average_price = round((ask6_price + ask8_price) / 2, 4) logger.info(f"the average price is {average_price}....", caller=self) await self.strategy_process(ask6_price, ask8_price, average_price)
async def _receive(self): """Receive stream message from Websocket connection.""" async for msg in self.ws: if msg.type == aiohttp.WSMsgType.TEXT: if self._process_callback: try: data = json.loads(msg.data) except: data = msg.data SingleTask.run(self._process_callback, data) elif msg.type == aiohttp.WSMsgType.BINARY: if self._process_binary_callback: SingleTask.run(self._process_binary_callback, msg.data) elif msg.type == aiohttp.WSMsgType.CLOSED: logger.warn("receive event CLOSED:", msg, caller=self) SingleTask.run(self.reconnect) elif msg.type == aiohttp.WSMsgType.ERROR: logger.error("receive event ERROR:", msg, caller=self) else: logger.warn("unhandled msg:", msg, caller=self)
async def on_event_orderbook_update(self, orderbook: Orderbook): """订单薄更新回调""" if orderbook.platform == config.A["platform"] and orderbook.symbol == config.A["symbol"]: self._a_orderbook_ok = True self._a_orderbook = orderbook elif orderbook.platform == config.B["platform"] and orderbook.symbol == config.B["symbol"]: self._b_orderbook_ok = True self._b_orderbook = orderbook elif orderbook.platform == config.C["platform"] and orderbook.symbol == config.C["symbol"]: self._c_orderbook_ok = True self._c_orderbook = orderbook # 判断maker和taker订单薄是否准备就绪 if not self._a_orderbook_ok or not self._b_orderbook_ok or not self._c_orderbook_ok: logger.warn("orderbook not ok.", caller=self) return # A同时进行买入和卖出ETH的检查 SingleTask.run(self.a_do_action_buy) SingleTask.run(self.a_do_action_sell)
async def send(self, data) -> bool: """ Send message to Websocket server. Args: data: Message content, must be dict or string. Returns: If send successfully, return True, otherwise return False. """ if not self.ws: logger.warn("Websocket connection not connected yet!", caller=self) return False if isinstance(data, dict): await self.ws.send_json(data) elif isinstance(data, str): await self.ws.send_str(data) else: logger.error("send message failed:", data, caller=self) return False logger.debug("send message:", data, caller=self) return True
async def get_open_order_ids(self): """Get open order id list. Args: None. Returns: order_ids: Open order id list, otherwise it's None. error: Error information, otherwise it's None. """ success, error = await self._rest_api.get_open_orders(self._raw_symbol) if error: SingleTask.run(self._error_callback, error) return None, error else: if len(success) > 100: logger.warn("order length too long! (more than 100)", caller=self) order_ids = [] for order_info in success: order_ids.append(order_info["order_id"]) return order_ids, None
async def reconnect(self) -> None: """Re-connect to Websocket server.""" logger.warn("reconnecting to Websocket server right now!", caller=self) await self.close() await self._connect()
async def fetch(cls, method, url, params=None, body=None, data=None, headers=None, timeout=30, **kwargs): """ Create a HTTP request. Args: method: HTTP request method. `GET` / `POST` / `PUT` / `DELETE` url: Request url. params: HTTP query params. body: HTTP request body, string or bytes format. data: HTTP request body, dict format. headers: HTTP request header. timeout: HTTP request timeout(seconds), default is 30s. kwargs: proxy: HTTP proxy. Return: code: HTTP response code. success: HTTP response data. If something wrong, this field is None. error: If something wrong, this field will holding a Error information, otherwise it's None. Raises: HTTP request exceptions or response data parse exceptions. All the exceptions will be captured and return Error information. """ session = cls._get_session(url) if not kwargs.get("proxy"): kwargs[ "proxy"] = config.proxy # If there is a `HTTP PROXY` Configuration in config file? try: if method == "GET": response = await session.get(url, params=params, headers=headers, timeout=timeout, **kwargs) elif method == "POST": response = await session.post(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) elif method == "PUT": response = await session.put(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) elif method == "DELETE": response = await session.delete(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) else: error = "http method error!" return None, None, error except Exception as e: logger.error("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "Error:", e, caller=cls) return None, None, e code = response.status if code not in (200, 201, 202, 203, 204, 205, 206): text = await response.text() logger.error("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "code:", code, "result:", text, caller=cls) return code, None, text try: result = await response.json() except: result = await response.text() logger.warn("response data is not json format!", "method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "code:", code, "result:", result, caller=cls) logger.debug("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "code:", code, "result:", json.dumps(result), caller=cls) return code, result, None
async def process_binary(self, raw): """Process binary message that received from websocket. Args: raw: Binary message received from websocket. Returns: None. """ decompress = zlib.decompressobj(-zlib.MAX_WBITS) msg = decompress.decompress(raw) msg += decompress.flush() msg = msg.decode() if msg == "pong": return logger.debug("msg:", msg, caller=self) msg = json.loads(msg) # Authorization message received. if msg.get("event") == "login": if not msg.get("success"): e = Error( "Websocket connection authorized failed: {}".format(msg)) logger.error(e, caller=self) SingleTask.run(self._error_callback, e) SingleTask.run(self._init_callback, False) return logger.info("Websocket connection authorized successfully.", caller=self) # Fetch orders from server. (open + partially filled) order_infos, error = await self._rest_api.get_open_orders( self._raw_symbol) if error: e = Error("get open orders error: {}".format(msg)) SingleTask.run(self._error_callback, e) SingleTask.run(self._init_callback, False) return if len(order_infos) > 100: logger.warn("order length too long! (more than 100)", caller=self) for order_info in order_infos: order_info["ctime"] = order_info["created_at"] order_info["utime"] = order_info["timestamp"] self._update_order(order_info) # Subscribe order channel. data = {"op": "subscribe", "args": [self._order_channel]} await self._ws.send(data) return # Subscribe response message received. if msg.get("event") == "subscribe": if msg.get("channel") == self._order_channel: SingleTask.run(self._init_callback, True) else: e = Error("subscribe order event error: {}".format(msg)) SingleTask.run(self._error_callback, e) SingleTask.run(self._init_callback, False) return # Order update message received. if msg.get("table") == "spot/order": for data in msg["data"]: data["ctime"] = data["timestamp"] data["utime"] = data["last_fill_time"] self._update_order(data)