async def process(self, msg): """ 处理websocket上接收到的消息 """ # logger.debug("msg:", msg, caller=self) if tools.get_cur_timestamp() <= self._last_msg_ts: return if not isinstance(msg, dict): return notifications = msg.get("notifications") if not notifications: return message = notifications[0].get("message") if message != "order_book_event": return symbol = notifications[0].get("result").get("instrument") bids = [] for item in notifications[0].get("result").get("bids")[:10]: b = [item.get("price"), item.get("quantity")] bids.append(b) asks = [] for item in notifications[0].get("result").get("asks")[:10]: a = [item.get("price"), item.get("quantity")] asks.append(a) self._last_msg_ts = tools.get_cur_timestamp() orderbook = { "platform": self._platform, "symbol": symbol, "asks": asks, "bids": bids, "timestamp": self._last_msg_ts } EventOrderbook(**orderbook).publish() logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)
def __init__(self, account=None, platform=None, strategy=None, order_no=None, symbol=None, action=None, price=0, quantity=0, remain=0, status=ORDER_STATUS_NONE, avg_price=0, order_type=ORDER_TYPE_LIMIT, trade_type=TRADE_TYPE_NONE, ctime=None, utime=None): self.platform = platform # 交易平台 self.account = account # 交易账户 self.strategy = strategy # 策略名称 self.order_no = order_no # 委托单号 self.action = action # 买卖类型 SELL-卖,BUY-买 self.order_type = order_type # 委托单类型 MARKET-市价,LIMIT-限价 self.symbol = symbol # 交易对 如: ETH/BTC self.price = price # 委托价格 self.quantity = quantity # 委托数量(限价单) self.remain = remain # 剩余未成交数量 self.status = status # 委托单状态 self.avg_price = avg_price # 成交均价 self.trade_type = trade_type # 合约订单类型 开多/开空/平多/平空 self.ctime = ctime if ctime else tools.get_cur_timestamp() # 创建订单时间戳 self.utime = utime if utime else tools.get_cur_timestamp() # 交易所订单更新时间
async def receive(self): """ 接收消息 """ """ See: client_ws.py async def __anext__(self): msg = await self.receive() if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): raise StopAsyncIteration # NOQA return msg """ self.last_timestamp = tools.get_cur_timestamp() #单位:秒,连接检测时间初始化 async for msg in self.ws: #参考aiohttp的源码,当ws连接被关闭后,本循环将退出 self.last_timestamp = tools.get_cur_timestamp( ) #单位:秒,每收到一个消息就更新一下此变量,用于判断网络是否出问题,是否需要重连 if msg.type == aiohttp.WSMsgType.TEXT: try: data = json.loads(msg.data) except: data = msg.data #await asyncio.get_event_loop().create_task(self.process(data)) #这样写的好处是如果里面这个函数发生异常不会影响本循环,因为它在单独任务里面执行 try: await self.process(data) except Exception as e: logger.error("process ERROR:", e, caller=self) await self.socket_close() #关闭 break #退出循环 elif msg.type == aiohttp.WSMsgType.BINARY: #await asyncio.get_event_loop().create_task(self.process_binary(msg.data)) #好处同上 try: await self.process_binary(msg.data) except Exception as e: logger.error("process_binary ERROR:", e, caller=self) await self.socket_close() #关闭 break #退出循环 elif msg.type == aiohttp.WSMsgType.ERROR: logger.error("receive event ERROR:", msg, caller=self) break #退出循环 else: #aiohttp.WSMsgType.CONTINUATION #aiohttp.WSMsgType.PING #aiohttp.WSMsgType.PONG logger.warn("unhandled msg:", msg, caller=self) #当代码执行到这里的时候ws已经是关闭状态,所以不需要再调用close去关闭ws了. #当ws连接被关闭或者出现任何错误,将重新连接 state = State(self._platform, self._account, "connection lost! url: {}".format(self._url), State.STATE_CODE_DISCONNECT) SingleTask.run(self.cb.on_state_update_callback, state) self.last_timestamp = 0 #先置0,相当于关闭连接检测 asyncio.get_event_loop().create_task(self._reconnect())
async def find_one_and_update(self, spec, update_fields, upsert=False, return_document=False, fields=None, cursor=None): """ 查询一条指定数据,并修改这条数据 @param spec 查询条件 @param update_fields 更新字段 @param upsert 如果不满足条件,是否插入新数据,默认False @param return_document 返回修改之前数据或修改之后数据,默认False为修改之前数据 @param fields 需要返回的字段,默认None为返回全部数据 @return result 修改之前或之后的数据 @param cursor 查询游标,如不指定默认使用self._cursor """ if not cursor: cursor = self._cursor 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() update_fields["$set"] = set_fields result = await cursor.find_one_and_update( spec, update_fields, projection=fields, upsert=upsert, return_document=return_document) if result and "_id" in result: result["_id"] = str(result["_id"]) return result
async def update(self, spec, update_fields, upsert=False, multi=False, cursor=None): """ 更新 @param spec 更新条件 @param update_fields 更新字段 @param upsert 如果不满足条件,是否插入新数据 @param multi 是否批量更新 @return modified_count 更新数据条数 @param cursor 查询游标,如不指定默认使用self._cursor """ 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() 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 insert(self, docs_data, cursor=None): """ 插入数据 @param docs_data 插入数据 dict或list @param ret_ids 插入数据的id列表 @param cursor 查询游标,如不指定默认使用self._cursor """ if not cursor: cursor = self._cursor docs = copy.deepcopy(docs_data) ret_ids = [] is_one = False create_time = tools.get_cur_timestamp() if not isinstance(docs, list): docs = [docs] is_one = True for doc in docs: doc["_id"] = ObjectId() doc["create_time"] = create_time doc["update_time"] = create_time ret_ids.append(str(doc["_id"])) cursor.insert_many(docs) if is_one: return ret_ids[0] else: return ret_ids
async def find_one_and_update(self, spec, update_fields, upsert=False, return_document=False, fields=None, cursor=None): """ Find a document and update this document. Args: spec: Query params. update_fields: Fields to be updated. upsert: If server this document if not exist? True or False. return_document: If return new document? `True` return new document, False return old document. fields: The fields to be return. cursor: Query cursor, default is `self._cursor`. Return: result: Document. """ if not cursor: cursor = self._cursor 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() update_fields["$set"] = set_fields result = await cursor.find_one_and_update(spec, update_fields, projection=fields, upsert=upsert, return_document=return_document) if result and "_id" in result: result["_id"] = str(result["_id"]) return result
async def request(self, method, uri, params=None, headers=None, body=None, auth=False): """ Do HTTP request. Args: method: HTTP request method. GET, POST, DELETE, PUT. uri: HTTP request uri. params: HTTP query params. headers: HTTP request header. body: HTTP request body. auth: If required signature. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ url = urljoin(self._host, uri) if params: query = "&".join(["=".join([str(k), str(v)]) for k, v in params.items()]) url += "?" + query else: query = "" if auth: signature = hmac.new(self._secret_key.encode(), query.encode(), hashlib.sha256).hexdigest() headers = { "ACCESS-TIMESTAMP": str(tools.get_cur_timestamp()), "ACCESS-KEY": self._access_key, "ACCESS-SIGN": signature } _, success, error = await AsyncHttpRequests.fetch(method, url, body=body, headers=headers, timeout=10) if error: return None, error if success.get("code") != 0: return None, success return success, None
async def get_asset_snapshot(self, platform, account, start=None, end=None): """ 获取资产快照 @param platform 交易平台 @param account 账户 @param start 开始时间戳(秒) @param end 结束时间戳(秒) """ if not end: end = tools.get_cur_timestamp() # 截止时间默认当前时间 if not start: start = end - 60 * 60 * 24 # 开始时间默认一天前 spec = { 'platform': platform, 'account': account, 'create_time': { '$gte': start, '$lte': end } } fields = {'platform': 0, 'account': 0, 'update_time': 0} datas = await self.get_list(spec, fields=fields) return datas
async def insert(self, docs, cursor=None): """ Insert (a) document(s). Args: docs: Dict or List to be inserted. cursor: DB cursor, default is `self._cursor`. Return: ret_ids: Document id(s) that already inserted, if insert a dict, `ret_ids` is a id string; if insert a list, `ret_ids` is a id list. """ if not cursor: cursor = self._cursor docs_data = copy.deepcopy(docs) ret_ids = [] is_one = False create_time = tools.get_cur_timestamp() if not isinstance(docs_data, list): docs_data = [docs_data] is_one = True for doc in docs_data: doc["_id"] = ObjectId() doc["create_time"] = create_time doc["update_time"] = create_time ret_ids.append(str(doc["_id"])) cursor.insert_many(docs_data) if is_one: return ret_ids[0] else: return ret_ids
async def get_asset_snapshot(self, platform, account, start=None, end=None): """ Get asset snapshot data from db. Args: platform: Exchange platform name. e.g. binance/bitmex/okex account: Account name. e.g. [email protected] start: Start time, Millisecond timestamp, default is a day ago. end: End time, Millisecond timestamp, default is current timestamp. Returns: datas: Asset data list. e.g. [{"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }, ... ] """ if not end: end = tools.get_cur_timestamp() # Current timestamp if not start: start = end - 60 * 60 * 24 # A day ago. spec = { "platform": platform, "account": account, "timestamp": { "$gte": start, "$lte": end } } fields = {"platform": 0, "account": 0, "update_time": 0} datas = await self._db.get_list(spec, fields=fields) return datas
async def _update_heartbeat_msg(self, *args, **kwargs): """ Update heartbeat message to new request id. """ self.heartbeat_msg = { "event": "ping", "reqid": tools.get_cur_timestamp() }
async def request(self, uri, data): """ 发起请求 """ url = urljoin(self._host, uri) timestamp = tools.get_cur_timestamp() data["accesskey"] = self._access_key data["secretkey"] = self._secret_key data["timestamp"] = timestamp sign = "&".join(["{}={}".format(k, data[k]) for k in sorted(data.keys())]) md5_str = hashlib.md5(sign.encode("utf8")).hexdigest() del data["secretkey"] del data["accesskey"] del data["timestamp"] body = { "common": { "accesskey": self._access_key, "timestamp": timestamp, "sign": md5_str }, "data": data } trace_id = md5_str[:16] # 16位的随机字符串 headers = { "X-B3-Traceid": trace_id, "X-B3-Spanid": trace_id } _, success, error = await AsyncHttpRequests.fetch("POST", url, data=body, headers=headers, timeout=10) if error: return None, error if str(success.get("code")) != "1000": return None, success return success["data"]["result"], None
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() 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 _check_connection(self, *args, **kwargs): """ 检查连接是否正常 """ now = tools.get_cur_timestamp() #当前时间,单位:秒 if self.last_timestamp > 0 and now - self.last_timestamp > self._check_conn_interval: #最后接收数据包(包括pingpong包)时间间隔超过指定值 if self.ws and not self.ws.closed: #连接没有关闭 await self.socket_close() #关闭
def encode(cls, user_id, username): info = { "user_id": user_id, "username": username, "timestamp": tools.get_cur_timestamp() } bdata = json.dumps(info).encode("ascii") token = base64.b64encode(bdata) return token.decode()
async def connected_callback(self): """ 建立连接之后,鉴权、订阅频道 """ # 身份验证 expires = tools.get_cur_timestamp() + 5 signature = self._rest_api.generate_signature("GET", "/realtime", expires, None) data = { "op": "authKeyExpires", "args": [self._access_key, expires, signature] } await self.ws.send_json(data)
def __init__(self, **kwargs): self._platform = kwargs["platform"] self._wss = kwargs.get("wss", "wss://hermes.deribit.com") self._symbols = list(set(kwargs.get("symbols"))) self._channels = kwargs.get("channels") self._access_key = kwargs.get("access_key") self._secret_key = kwargs.get("secret_key") self._last_msg_ts = tools.get_cur_timestamp() # 上次接收到消息的时间戳 url = self._wss + "/ws/api/v1/" super(Deribit, self).__init__(url) self.heartbeat_msg = {"action": "/api/v1/public/ping"} self.initialize()
def __init__(self): self._platform = DERIBIT self._url = config.platforms.get(self._platform).get("wss") self._symbols = list( set(config.platforms.get(self._platform).get("symbols"))) self._access_key = config.platforms.get( self._platform).get("access_key") self._secret_key = config.platforms.get( self._platform).get("secret_key") self._last_msg_ts = tools.get_cur_timestamp() # 上次接收到消息的时间戳 super(Deribit, self).__init__(self._url) self.heartbeat_msg = {"action": "/api/v1/public/ping"} self.initialize()
async def request(self, method, uri, params=None, body=None): """ 发起请求 """ if params: query = "&".join(["{}={}".format(k, params[k]) for k in sorted(params.keys())]) url = uri + "?" + query else: url = uri ts = tools.get_cur_timestamp() + 5 signature = self.generate_signature(method, url, ts, body) headers = { "api-expires": str(ts), "api-key": self.access_key, "api-signature": signature } url = urljoin(self.host, uri) _, success, error = await AsyncHttpRequests.fetch(method, url, headers=headers, data=body, timeout=10) return success, error
async def auth_middleware(request, handler): """ Authentication middleware. """ ext_uri = config.http_server.get("ext_uri", []) if request.path not in ext_uri: token = request.headers.get("Token") if not token: token = request.query.get("Token") if not token: raise exceptions.AuthenticationFailed(msg="Token miss.") try: user_id, username, timestamp = AuthToken.decode(token) except: raise exceptions.AuthenticationFailed(msg="Token error.") if tools.get_cur_timestamp() - timestamp > 60 * 60 * 24: # expire time. raise exceptions.AuthenticationFailed(msg="Token expired.") request.user_id = user_id response = await handler(request) return response
async def request(self, uri, data): """ Do HTTP request. Args: uri: HTTP request uri. data: HTTP request body. Returns: success: Success results, otherwise it's None. error: Error information, otherwise it's None. """ url = urljoin(self._host, uri) timestamp = tools.get_cur_timestamp() data["accesskey"] = self._access_key data["secretkey"] = self._secret_key data["timestamp"] = timestamp sign = "&".join( ["{}={}".format(k, data[k]) for k in sorted(data.keys())]) md5_str = hashlib.md5(sign.encode("utf8")).hexdigest() del data["secretkey"] del data["accesskey"] del data["timestamp"] body = { "common": { "accesskey": self._access_key, "timestamp": timestamp, "sign": md5_str }, "data": data } trace_id = md5_str[:16] # A 16th length of random string. headers = {"X-B3-Traceid": trace_id, "X-B3-Spanid": trace_id} _, success, error = await AsyncHttpRequests.fetch("POST", url, data=body, headers=headers, timeout=10) if error: return None, error if str(success.get("code")) != "1000": return None, success return success["data"]["result"], None
async def receive(self): """ 接收消息 """ async for msg in self.ws: self._last_receive_ts = tools.get_cur_timestamp() if msg.type == aiohttp.WSMsgType.TEXT: try: data = json.loads(msg.data) except: data = msg.data await asyncio.get_event_loop().create_task(self.process(data)) elif msg.type == aiohttp.WSMsgType.BINARY: await asyncio.get_event_loop().create_task( self.process_binary(msg.data)) elif msg.type == aiohttp.WSMsgType.CLOSED: logger.warn('receive event CLOSED:', msg, caller=self) await asyncio.get_event_loop().create_task(self._reconnect()) return elif msg.type == aiohttp.WSMsgType.ERROR: logger.error('receive event ERROR:', msg, caller=self) else: logger.warn('unhandled msg:', msg, caller=self)
async def connected_callback(self): """ 建立连接之后,订阅事件 ticker/deals """ # 身份验证 expires = tools.get_cur_timestamp() + 5 signature = self._rest_api.generate_signature("GET", "/realtime", expires, None) data = { "op": "authKeyExpires", "args": [self._access_key, expires, signature] } await self.ws.send_json(data) logger.info("Websocket connection authorized successfully.", caller=self) # 订阅order和position ch_order = "order:{symbol}".format(symbol=self._symbol) ch_position = "position:{symbol}".format(symbol=self._symbol) data = { "op": "subscribe", "args": [ch_order, ch_position] } await self.ws.send_json(data) logger.info("subscribe account/order/position successfully.", caller=self)
async def send_heartbeat_msg(self, *args, **kwargs): data = {"event": "ping", "reqid": tools.get_cur_timestamp()} if not self._ws: logger.error("Websocket connection not yeah!", caller=self) return await self._ws.send(data)