async def do_orderbook_update(self, *args, **kwargs): """ Fetch orderbook information.""" for symbol in self._symbols: result, error = await self._rest_api.get_orderbook( symbol, self._orderbook_length) if error: continue bids = [] asks = [] for item in result["asks"]: a = [item["limitPrice"], item["quantity"]] asks.append(a) for item in result["bids"]: b = [item["limitPrice"], item["quantity"]] bids.append(b) if not bids and not asks: logger.warn("no orderbook data", caller=self) continue if len(bids) > 0 and len(asks) > 0 and float(bids[0][0]) >= float( asks[0][0]): logger.warn("symbol:", symbol, "bids one is grate than asks one! asks:", asks, "bids:", bids, caller=self) continue orderbook = { "platform": self._platform, "symbol": symbol, "asks": asks, "bids": bids, "timestamp": tools.get_cur_timestamp_ms() } EventOrderbook(**orderbook).publish() logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self) # await 0.1 second before next request. await asyncio.sleep(0.1)
async def process_orderbook(self, symbol, data): """Process orderbook data and publish OrderbookEvent.""" bids = [] asks = [] for bid in data.get("bids")[:self._orderbook_length]: bids.append(bid[:2]) for ask in data.get("asks")[:self._orderbook_length]: asks.append(ask[:2]) orderbook = { "platform": self._platform, "symbol": symbol, "asks": asks, "bids": bids, "timestamp": tools.get_cur_timestamp_ms() } EventOrderbook(**orderbook).publish() logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)
async def _publish_asset(self, *args, **kwargs): """ Publish asset information.""" if self._last_assets == self._assets: update = False else: update = True self._last_assets = self._assets timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def get_order_status(self, symbol, order_id, client_order_id): """ 获取订单的状态 @param symbol 交易对 @param order_id 订单id @param client_order_id 创建订单返回的客户端信息 """ params = { "symbol": symbol, "orderId": str(order_id), "origClientOrderId": client_order_id, "timestamp": tools.get_cur_timestamp_ms() } success, error = await self.request("GET", "/api/v3/order", params=params, auth=True) return success, error
async def get_assets(self): """ 获取交易账户资产信息 Args: None Returns: assets: Asset if successfully, otherwise it's None. error: Error information, otherwise it's None. """ #{"result": {"backstopProvider": false, "collateral": 110.094266926, "freeCollateral": 109.734306926, "initialMarginRequirement": 0.2, "leverage": 5.0, "liquidating": false, "maintenanceMarginRequirement": 0.03, "makerFee": 0.0002, "marginFraction": 61.1703338848761, "openMarginFraction": 61.170278323147016, "positionLimit": null, "positionLimitUsed": 2.15976, "positions": [{"collateralUsed": 0.35996, "cost": -1.7999, "entryPrice": 179.99, "estimatedLiquidationPrice": 11184.0172926, "future": "ETH-PERP", "initialMarginRequirement": 0.2, "longOrderSize": 0.0, "maintenanceMarginRequirement": 0.03, "netSize": -0.01, "openSize": 0.01, "realizedPnl": 0.01723393, "shortOrderSize": 0.0, "side": "sell", "size": 0.01, "unrealizedPnl": 0.0001}], "takerFee": 0.0007, "totalAccountValue": 110.094366926, "totalPositionSize": 1.7998, "useFttCollateral": true, "username": "******"}, "success": true} success, error = await self._rest_api.get_account_info() if error: return None, error if not success["success"]: return None, "get_account_info error" data = success["result"] assets = {} total = float(data["collateral"]) free = float(data["freeCollateral"]) locked = total - free assets["USD"] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets timestamp = tools.get_cur_timestamp_ms() ast = Asset(self._platform, self._account, self._assets, timestamp, update) #因为ftx websocket接口里面没有资产通知,所以只能这样模拟 SingleTask.run(self.cb.on_asset_update_callback, ast) return ast, None
async def request(self, method, uri, params=None, body=None, headers=None, auth=False): """ Do HTTP request. Args: method: HTTP request method. GET, POST, DELETE, PUT. uri: HTTP request uri. params: HTTP query params. body: HTTP request body. headers: HTTP request headers. auth: If this request requires authentication. Returns: success: Success results, otherwise it"s None. error: Error information, otherwise it"s None. """ if params: query = "&".join( ["{}={}".format(k, params[k]) for k in sorted(params.keys())]) uri += "?" + query url = urljoin(self._host, uri) if auth: if not headers: headers = {} timestamp = str(tools.get_cur_timestamp_ms()) signature = self._generate_signature(timestamp, method, uri, body) headers["KC-API-KEY"] = self._access_key headers["KC-API-SIGN"] = signature headers["KC-API-TIMESTAMP"] = timestamp headers["KC-API-PASSPHRASE"] = self._passphrase _, success, error = await AsyncHttpRequests.fetch(method, url, data=body, headers=headers, timeout=10) if error: return None, error if success["code"] != "200000": return None, success return success["data"], error
async def check_asset_update(self, *args, **kwargs): """ 检查账户资金是否更新 """ result, error = await self._rest_api.get_user_account() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for item in result: symbol = item["currency"] total = float(item["balance"]) free = float(item["available"]) locked = float(item["frozen"]) if total > 0: assets[symbol] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # 推送当前资产 timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_user_account() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for name, item in result["info"].items(): symbol = name.upper() if symbol not in ["BTC", "ETH"]: continue total = float(item["equity"]) locked = float(item["margin"]) if total > 0: assets[symbol] = { "total": "%.8f" % total, "free": "%.8f" % (total - locked), "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def revoke_order(self, symbol, order_id, client_order_id): """ Cancelling an unfilled order. Args: symbol: Symbol name, e.g. BTCUSDT. order_id: Order id. client_order_id: Client order id. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ params = { "symbol": symbol, "orderId": str(order_id), "origClientOrderId": client_order_id, "timestamp": tools.get_cur_timestamp_ms() } success, error = await self.request("DELETE", "/api/v3/order", params=params, auth=True) return success, error
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_user_account() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for item in result["balances"]: name = item.get("asset") free = float(item.get("free")) locked = float(item.get("locked")) total = free + locked if total > 0: assets[name] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_asset_info() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for item in result["data"]: symbol = item["symbol"].upper() total = float(item["margin_balance"]) free = float(item["margin_available"]) locked = float(item["margin_frozen"]) if total > 0: assets[symbol] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def request(self, method, uri, body=None, auth=False): """ Do HTTP request. """ url = urllib.parse.urljoin(self._host, uri) headers = {} if method == "POST": body = body or {} # Sometimes requests can arrive out of order or NTP can cause your clock to rewind. body["nonce"] = tools.get_cur_timestamp_ms() + 3000 if auth: headers = { "API-Key": self._access_key, "API-Sign": self._generate_signature(uri, body) } _, success, error = await AsyncHttpRequests.fetch(method, url, body=body, headers=headers, timeout=10) if error: return None, error if success["error"]: return success, success["error"] return success["result"], None
async def get_order_status(self, symbol, order_id, client_order_id): """ Get order details by order id. Args: symbol: Symbol name, e.g. BTCUSDT. order_id: Order id. client_order_id: Client order id. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ params = { "symbol": symbol, "orderId": str(order_id), "origClientOrderId": client_order_id, "timestamp": tools.get_cur_timestamp_ms() } success, error = await self.request("GET", "/api/v3/order", params=params, auth=True) return success, error
async def put_listen_key(self, listen_key): """ Keepalive a user data stream to prevent a time out. Args: listen_key: Listen key. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ uri = "/fapi/v1/listenKey" params = { "listenKey": listen_key, "timestamp": tools.get_cur_timestamp_ms() } success, error = await self.request("PUT", uri, params=params, auth=True) return success, error
async def delete_listen_key(self, listen_key): """ Delete a listen key. Args: listen_key: Listen key. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ uri = "/fapi/v1/listenKey" params = { "listenKey": listen_key, "timestamp": tools.get_cur_timestamp_ms() } success, error = await self.request("DELETE", uri, params=params, auth=True) return success, error
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_user_account() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for item in result["info"]: symbol = item["instrument_id"].split("-")[0] total = float(item["equity"]) free = float(item["total_avail_balance"]) if total > 0: assets[symbol] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % (total - free) } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def check_asset_update(self, *args, **kwargs): """ 检查账户资金是否更新 """ result, error = await self._rest_api.get_margin() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} name = "XBT" free = result["availableMargin"] / 100000000.0 total = result["marginBalance"] / 100000000.0 locked = total - free assets[name] = { "total": "%.8f" % total, "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # 推送当前资产 timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def on_trade_update_callback(self, trade: Trade): """ 市场最新成交更新 """ logger.info("trade:", trade, caller=self) #行情保存进数据库 kwargs = { "direction": trade.action, "tradeprice": trade.price, "volume": trade.quantity, "tradedt": trade.timestamp, "dt": tools.get_cur_timestamp_ms() } async def save(kwargs): #一秒内会有多次通知,将一秒内的通知都收集在一起,一次性写入数据库,约一秒写一次,提高数据库性能 dlist = self.d_trade_map[trade.symbol] dlist.append(kwargs) if dlist[len(dlist) - 1]["dt"] - dlist[0]["dt"] > 1000: #每秒写一次数据库 #因为是异步并发,所以先清空列表,重新收集新的一秒内的所有通知,而不是等待数据库IO完成再清空(python的变量只是对象的引用) #xxx = copy.deepcopy(dlist) #dlist.clear() #insert xxx self.d_trade_map[trade.symbol] = [] #写数据库 t_trade = self.t_trade_map[trade.symbol] if t_trade: s, e = await t_trade.insert(dlist) if e: logger.error("insert trade:", e, caller=self) SingleTask.run(save, kwargs) #发布行情到消息队列 kwargs = { "platform": trade.platform, "symbol": trade.symbol, "action": trade.action, "price": trade.price, "quantity": trade.quantity, "timestamp": trade.timestamp } EventTrade(**kwargs).publish()
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_account_balance() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for item in result.get("data"): name = item.get("currency").upper() b = float(item.get("balance")) if b <= 0: continue assets[name] = { "free": "%.8f" % item["available"], "locked": "%.8f" % (b - float(item["available"])), "total": "%.8f" % b } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
def __init__(self, platform=None, account=None, assets=None, timestamp=None): """ 初始化 """ timestamp = timestamp or tools.get_cur_timestamp_ms() self.ROUTING_KEY = "{platform}.{account}".format(platform=platform, account=account) self.platform = platform self.account = account self.assets = assets self.timestamp = timestamp data = { "platform": platform, "account": account, "assets": assets, "timestamp": timestamp } super(EventAsset, self).__init__(data)
async def process(self, msg): """ Process message that received from Websocket connection. Args: msg: Message data received from Websocket connection. """ # logger.debug("msg:", msg, caller=self) if tools.get_cur_timestamp_ms() <= self._last_msg_ts: return if not isinstance(msg, dict): return notifications = msg.get("notifications") if not notifications: return for item in notifications: message = item["message"] data = item["result"] if message == "order_book_event": await self.process_orderbook(data) elif message == "trade_event": await self.process_trade(data)
def __init__(self, platform=None, account=None, assets=None, timestamp=None): """ 初始化 """ timestamp = timestamp or tools.get_cur_timestamp_ms() self.ROUTING_KEY = '{platform}.{account}'.format(platform=platform, account=account) self.platform = platform self.account = account self.assets = assets self.timestamp = timestamp data = { 'platform': platform, 'account': account, 'assets': assets, 'timestamp': timestamp } super(EventAsset, self).__init__(data)
async def get_newest_orderbook(self, symbol): """ Get the newest orderbook information. """ result, error = await self._rest_api.get_orderbook( symbol.replace("/", ""), self._orderbook_length) key = list(result.keys())[0] asks, bids = [], [] for item in result.get(key)["asks"]: asks.append(item[:2]) for item in result.get(key)["bids"]: bids.append(item[:2]) orderbook = { "platform": self._platform, "symbol": symbol, "asks": asks, "bids": bids, "timestamp": tools.get_cur_timestamp_ms() } EventOrderbook(**orderbook).publish() logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)
def __init__(self, platform=None, account=None, strategy=None, order_no=None, symbol=None, action=None, price=0, quantity=0, order_type=ORDER_TYPE_LMT): self.platform = platform # 交易平台 self.account = account # 交易账户 self.strategy = strategy # 策略名称 self.order_no = order_no # 委托单号 self.action = action # 买卖类型 SELL-卖,BUY-买 self.order_type = order_type # 委托单类型 MKT-市价,LMT-限价 self.symbol = symbol # 交易对 如: ETH/BTC self.price = price # 委托价格 self.quantity = quantity # 委托数量(限价单) self.remain = quantity # 剩余未成交数量 self.status = ORDER_STATUS_NONE # 委托单状态 self.timestamp = tools.get_cur_timestamp_ms() # 创建订单时间戳(毫秒)
def login(resp, msg): cb = app._my_callback cb.on_kline_update_callback = None cb.on_orderbook_update_callback = None cb.on_trade_update_callback = None cb.on_ticker_update_callback = None kwargs = {} kwargs["cb"] = cb kwargs["strategy"] = "__webservice__" kwargs["platform"] = msg["platform"] kwargs["symbols"] = msg["symbols"] kwargs["account"] = msg["access_key"] + "@" + msg["platform"] kwargs["access_key"] = msg["access_key"] kwargs["secret_key"] = msg["secret_key"] k = kwargs["account"] token = tools.get_uuid5(k+str(tools.get_cur_timestamp_ms())) #生成一个全局唯一token,代表一个交易通道 if cb.ws_dict[k]["token"]: #不允许重复登录 r = { "op": "login", "cid": msg.get("cid", ""), "result": False, "error_message": "Do not log in repeat", "token": "" } SingleTask.run(response, resp, r) return app._my_trader_dict[token]["trader"] = Trader(**kwargs) app._my_trader_dict[token]["account"] = k cb.ws_dict[k]["socket"] = resp cb.ws_dict[k]["token"] = token resp._my_own_token_dict[token] = True r = { "op": "login", "cid": msg.get("cid", ""), "result": True, "error_message": "", "token": token } SingleTask.run(response, resp, r)
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" success, error = await self._rest_api.get_user_account() if error or not success["result"]: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for key, value in success["available"].items(): free = float(value) locked = float(success["locked"][key]) assets[key] = { "total": "%.8f" % (free + locked), "free": "%.8f" % free, "locked": "%.8f" % locked } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_account_balance() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return temps = {} for item in result.get("list"): name = item.get("currency").upper() t = item.get("type") b = float(item.get("balance")) if name not in temps: temps[name] = {} if t == "trade": temps[name]["total"] = b else: temps[name]["locked"] = b assets = {} for name, item in temps.items(): if item["total"] <= 0: continue assets[name] = { "free": "%.8f" % (item["total"] - item["locked"]), "locked": "%.8f" % item["locked"], "total": "%.8f" % item["total"] } if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)
async def update(self, spec, update_fields, upsert=False, multi=False, cursor=None): """ Update (a) document(s). Args: spec: Query params, optional. Specifies selection filter using query operators. To return all documents in a collection, omit this parameter or pass an empty document ({}). update_fields: Fields to be updated. upsert: If server this document if not exist? True or False. multi: Update multiple documents? True or False. cursor: Query cursor, default is `self._cursor`. Return: modified_count: How many documents has been modified. """ if not cursor: cursor = self._cursor update_fields = copy.deepcopy(update_fields) spec[DELETE_FLAG] = {"$ne": True} if "_id" in spec: spec["_id"] = self._convert_id_object(spec["_id"]) set_fields = update_fields.get("$set", {}) set_fields["update_time"] = tools.get_cur_timestamp_ms() update_fields["$set"] = set_fields if not multi: result = await cursor.update_one(spec, update_fields, upsert=upsert) return result.modified_count else: result = await cursor.update_many(spec, update_fields, upsert=upsert) return result.modified_count
async def create_order(self, action, symbol, price, quantity, client_order_id=None, order_type=ORDER_TYPE_LIMIT): """ Create an order. Args: action: Trade direction, BUY or SELL. symbol: Symbol name, e.g. BTCUSDT. price: Price of each contract. quantity: The buying or selling quantity. client_order_id: A unique id for the order. Automatically generated if not sent. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ uri = "/fapi/v1/order" data = { "symbol": symbol, "side": action, "quantity": quantity, "recvWindow": "5000", "timestamp": tools.get_cur_timestamp_ms() } if client_order_id: data["newClientOrderId"] = client_order_id if order_type == ORDER_TYPE_LIMIT: data['type'] = "LIMIT" data['timeInForce'] = "GTC" data['price'] = price else: data['type'] = 'MARKET' success, error = await self.request("POST", uri, body=data, auth=True) return success, error
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_account_balance() if error: logger.warn("platform:", self._platform, "account:", self._account, "get asset info failed!", caller=self) return assets = {} for key, value in result.items(): name = await self.convert_currency_name(key) if not name: logger.warn("convert currency error:", key, caller=self) continue total = float(value) assets[name] = {"total": "%.8f" % total, "free": 0, "locked": 0} if assets == self._assets: update = False else: update = True self._assets = assets # Publish AssetEvent. timestamp = tools.get_cur_timestamp_ms() EventAsset(self._platform, self._account, self._assets, timestamp, update).publish() logger.info("platform:", self._platform, "account:", self._account, "asset:", self._assets, caller=self)