async def do_kline_update(self, symbol, kline_type): if kline_type == const.MARKET_TYPE_KLINE_5M: range_type = "5min" elif kline_type == const.MARKET_TYPE_KLINE_15M: range_type = "15min" else: return result, error = await self._rest_api.get_kline(symbol, 1, range_type) if error: return kline = { "platform": self._platform, "symbol": symbol, "open": "%.8f" % float(result[0]["open"]), "high": "%.8f" % float(result[0]["high"]), "low": "%.8f" % float(result[0]["low"]), "close": "%.8f" % float(result[0]["close"]), "volume": "%.8f" % float(result[0]["volume"]), "timestamp": result[0]["timestamp"], "kline_type": kline_type } EventKline(**kline).publish() logger.info("symbol:", symbol, "kline:", kline, caller=self)
async def connected_callback(self): """After create connection to Websocket server successfully, we will subscribe orderbook/kline/trade event.""" ches = [] for ch in self._channels: if ch == "orderbook": for symbol in self._symbols: ch = "futures/depth:{s}".format(s=symbol) ches.append(ch) elif ch == "trade": for symbol in self._symbols: ch = "futures/trade:{s}".format(s=symbol.replace("/", '-')) ches.append(ch) elif ch == "kline": for symbol in self._symbols: ch = "futures/candle60s:{s}".format( s=symbol.replace("/", '-')) ches.append(ch) # else: # logger.error("channel error! channel:", ch, caller=self) if len(ches) > 0: msg = {"op": "subscribe", "args": ches} await self._ws.send(msg) logger.info("subscribe orderbook/kline/trade success.", caller=self)
async def check_asset_update(self, *args, **kwargs): """Fetch asset information.""" result, error = await self._rest_api.get_user_account() if error: return assets = {} for name, value in result["asset"].items(): free = float(value.get("available")) total = float(value.get("total")) if not total: continue d = { "free": "%.8f" % free, "locked": "%.8f" % (total - free), "total": "%.8f" % total } assets[name] = d 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 done(self): """ 回测或者数据矩阵工作完毕 """ if config.backtest: #回测模式 #保存成交列表到csv文件 result_dir_path = os.path.dirname(os.path.abspath( sys.argv[0])) + "/result_report" csv_file = result_dir_path + "/trades.csv" tools.create_dir(csv_file) if os.path.isdir(csv_file) or os.path.ismount( csv_file) or os.path.islink(csv_file): logger.error("无效的csv文件") return if os.path.isfile(csv_file): os.remove(csv_file) result = [] for param in config.platforms: platform = param["platform"] account = param["account"] symbols = param["symbols"] for sym in symbols: fills = self.pm.get_fills_by_symbol(platform, account, sym) for f in fills: result.append(vars(f)) #保存到csv df = pd.DataFrame(result) df.to_csv(csv_file) #接下来读取csv文件进行分析,生成回测报告 analyzer = Analyzer() #回测结果分析器 if await analyzer.initialize(file_folder=result_dir_path): analyzer.do_analyze(result_dir=result_dir_path) logger.info("回测完毕", caller=self) self.stop() elif config.datamatrix: #数据矩阵模式 logger.info("datamatrix 完毕", caller=self) self.stop()
async def on_state_update_callback(self, state: State, **kwargs): """ 状态变化(底层交易所接口,框架等)通知回调函数 """ logger.info("on_state_update_callback:", state, caller=self) if state.code == State.STATE_CODE_DB_SUCCESS: #数据库连接成功 pass elif state.code == State.STATE_CODE_READY: #交易接口准备好 #收到此状态通知,证明指定交易接口准备就绪,可以对其进行操作,比如下单 self.syminfo, e = await self.get_symbol_info( self.gw, self.symbols[0]) elif state.code == State.STATE_CODE_CONNECT_SUCCESS: #交易接口连接成功 pass #仅仅是通知一下,实际策略可以不用过于关注此状态 elif state.code == State.STATE_CODE_CONNECT_FAILED: #交易接口连接失败 pass #不需要过于关注此状态,因为底层接口会自动重新连接 elif state.code == State.STATE_CODE_DISCONNECT: #交易接口连接断开 pass #不需要过于关注此状态,因为底层接口会自动重新连接 elif state.code == State.STATE_CODE_RECONNECTING: #交易接口重新连接中 pass #比如说可以记录重连次数,如果一段时间内一直在重连可能交易所出问题,可以酌情处理,如结束本策略进程等 elif state.code == State.STATE_CODE_PARAM_MISS: #交易接口初始化过程缺少参数 pass #收到此状态通知,证明无法正常初始化,应该结束本策略进程 elif state.code == State.STATE_CODE_GENERAL_ERROR: #交易接口常规错误 ... #策略进程运行过程中如果收到某些错误通知,可以根据实际情况判断,比如可以做一些策略善后工作,然后结束本策略进程 return
async def on_kline_update_callback(self, kline: Kline): """ 市场K线更新 """ logger.info("kline:", kline, caller=self) #行情保存进数据库 kwargs = { "open": kline.open, "high": kline.high, "low": kline.low, "close": kline.close, "volume": kline.volume, "begin_dt": kline.timestamp, "end_dt": kline.timestamp + 60 * 1000 - 1 } async def save(kwargs): t_kline = self.t_kline_map[kline.symbol] if t_kline: s, e = await t_kline.insert(kwargs) if e: logger.error("insert kline:", e, caller=self) SingleTask.run(save, kwargs) #发布行情到消息队列 kwargs = { "platform": kline.platform, "symbol": kline.symbol, "open": kline.open, "high": kline.high, "low": kline.low, "close": kline.close, "volume": kline.volume, "timestamp": kline.timestamp, "kline_type": kline.kline_type } EventKline(**kwargs).publish()
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_binary(self, raw): """ 处理websocket上接收到的消息 @param raw 原始的压缩数据 """ msg = json.loads(gzip.decompress(raw).decode()) logger.debug("msg:", msg, caller=self) op = msg.get("op") if op == "auth": # 授权 if msg["err-code"] != 0: e = Error( "Websocket connection authorized failed: {}".format(msg)) logger.error(e, caller=self) SingleTask.run(self._init_success_callback, False, e) return logger.info("Websocket connection authorized successfully.", caller=self) await self._auth_success_callback() elif op == "ping": # ping params = {"op": "pong", "ts": msg["ts"]} await self._ws.send(params) elif op == "sub": # 订阅频道返回消息 if msg["topic"] != self._order_channel: return if msg["err-code"] != 0: e = Error("subscribe order event error: {}".format(msg)) SingleTask.run(self._init_success_callback, False, e) else: SingleTask.run(self._init_success_callback, True, None) elif op == "notify": # 订单更新通知 if msg["topic"] != self._order_channel: return data = msg["data"] data["utime"] = msg["ts"] self._update_order(data)
async def connected_callback(self): """After create connection to Websocket server successfully, we will subscribe orderbook/trade/kline event.""" channels = [] for ch in self._channels: if ch == "orderbook": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "orderBook10") channels.append(channel) if ch == "trade": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "trade") channels.append(channel) if ch == "kline": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "tradeBin1m") channels.append(channel) while channels: data = { "op": "subscribe", "args": channels[:10] } await self._ws.send(data) channels = channels[10:] logger.info("subscribe orderbook/trade/kline successfully.", caller=self)
async def connected_callback(self): """After create Websocket connection successfully, we will subscribing orderbook/trade/kline.""" ches = [] for ch in self._channels: if ch == "orderbook": for symbol in self._symbols: ch = "spot/depth:{s}".format(s=symbol.replace("/", '-')) ches.append(ch) elif ch == "trade": for symbol in self._symbols: ch = "spot/trade:{s}".format(s=symbol.replace("/", '-')) ches.append(ch) elif ch == "kline": for symbol in self._symbols: ch = "spot/candle60s:{s}".format( s=symbol.replace("/", '-')) ches.append(ch) else: logger.error("channel error! channel:", ch, caller=self) if ches: msg = {"op": "subscribe", "args": ches} await self._ws.send(msg) logger.info("subscribe orderbook/trade/kline success.", caller=self)
async def process_trade_update(self, data): """ Deal with trade data, and publish trade message to EventCenter via TradeEvent. Args: data: Newest trade data. """ symbol = data[0].replace("_", "/") for item in data[1]: action = ORDER_ACTION_BUY if item["type"] == "buy" else ORDER_ACTION_SELL price = "%.8f" % float(item["price"]) quantity = "%.8f" % float(item["amount"]) timestamp = int(item["time"] * 1000) # Publish EventTrade. trade = { "platform": self._platform, "symbol": symbol, "action": action, "price": price, "quantity": quantity, "timestamp": timestamp } EventTrade(**trade).publish() logger.info("symbol:", symbol, "trade:", trade, caller=self)
async def process(self, msg): """ 处理websocket上接收到的消息 """ logger.debug("msg:", msg, caller=self) if not isinstance(msg, dict): return table = msg.get("table") if table == "orderBook10": # 订单薄数据 for item in msg["data"]: symbol = item.get("symbol") orderbook = { "platform": self._platform, "symbol": symbol, "asks": item.get("asks"), "bids": item.get("bids"), "timestamp": tools.utctime_str_to_mts(item["timestamp"]) } EventOrderbook(**orderbook).publish() logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self) elif table == "trade": # 成交数据 for item in msg["data"]: symbol = item["symbol"] trade = { "platform": self._platform, "symbol": symbol, "action": ORDER_ACTION_BUY if item["side"] else ORDER_ACTION_SELL, "price": "%.1f" % item["price"], "quantity": str(item["size"]), "timestamp": tools.utctime_str_to_mts(item["timestamp"]) } EventTrade(**trade).publish() logger.info("symbol:", symbol, "trade:", trade, caller=self) elif table == "tradeBin1m": # 1分钟K线数据 for item in msg["data"]: symbol = item["symbol"] kline = { "platform": self._platform, "symbol": symbol, "open": "%.1f" % item["open"], # 开盘价 "high": "%.1f" % item["high"], # 最高价 "low": "%.1f" % item["low"], # 最低价 "close": "%.1f" % item["close"], # 收盘价 "volume": str(item["volume"]), # 交易量 "timestamp": tools.utctime_str_to_mts(item["timestamp"]), # 时间戳 "kline_type": MARKET_TYPE_KLINE } EventKline(**kline).publish() logger.info("symbol:", symbol, "kline:", kline, caller=self)
async def on_event_order_update(self, order: Order): """ 订单状态更新 """ logger.info("order update:", order, caller=self) if order.status == ORDER_STATUS_FILLED: if order.order_no == self.buy_open_order_no: # 开仓委托单已经完全成交 logger.info("buy open completed.", caller=self) self.sell_close_time_down = 60 * 5 # 设置平仓倒计时 5分钟 if order.order_no == self.sell_close_order_no: # 平仓委托单已经完全成交 logger.info("sell close completed.", caller=self) exit(0)
async def process(self, msg): """ 处理websocket上接收到的消息 """ if not isinstance(msg, dict): return logger.debug("msg:", json.dumps(msg), caller=self) # 请求授权、订阅 if msg.get("request"): if msg["request"]["op"] == "authKeyExpires": # 授权 if msg["success"]: # 订阅order和position data = { "op": "subscribe", "args": [self._order_channel, self._position_channel] } await self.ws.send_json(data) logger.info("Websocket connection authorized successfully.", caller=self) else: e = Error("Websocket connection authorized failed: {}".format(msg)) logger.error(e, caller=self) if self._init_success_callback: SingleTask.run(self._init_success_callback, False, e) if msg["request"]["op"] == "subscribe": # 订阅 if msg["subscribe"] == self._order_channel and msg["success"]: self._subscribe_order_ok = True logger.info("subscribe order successfully.", caller=self) if msg["subscribe"] == self._position_channel and msg["success"]: self._subscribe_position_ok = True logger.info("subscribe position successfully.", caller=self) if self._subscribe_order_ok and self._subscribe_position_ok: if self._init_success_callback: SingleTask.run(self._init_success_callback, True, None) return # 订单更新 if msg.get("table") == "order": for data in msg["data"]: order = self._update_order(data) if order and self._order_update_callback: SingleTask.run(self._order_update_callback, order) return # 持仓更新 if msg.get("table") == "position": for data in msg["data"]: self._update_position(data) if self._position_update_callback: SingleTask.run(self._position_update_callback, self.position)
async def connect(self, reconnect=False): """ 建立TCP连接 @param reconnect 是否是断线重连 """ logger.info("host:", self._host, "port:", self._port, caller=self) if self._connected: return # 建立连接 try: transport, protocol = await aioamqp.connect( host=self._host, port=self._port, login=self._username, password=self._password) except Exception as e: logger.error("connection error:", e, caller=self) return finally: # 如果已经有连接已经建立好,那么直接返回(此情况在连续发送了多个连接请求后,若干个连接建立好了连接) if self._connected: return channel = await protocol.channel() self._protocol = protocol self._channel = channel self._connected = True logger.info("Rabbitmq initialize success!", caller=self) # 创建默认的交换机 exchanges = [ "Orderbook", "Trade", "Kline", ] for name in exchanges: await self._channel.exchange_declare(exchange_name=name, type_name="topic") logger.info("create default exchanges success!", caller=self) # 如果是断线重连,那么直接绑定队列并开始消费数据,如果是首次连接,那么等待5秒再绑定消费(等待程序各个模块初始化完成) if reconnect: self._bind_and_consume() else: asyncio.get_event_loop().call_later(5, self._bind_and_consume)
async def start_orders(self, actions, callback): logger.info('start orders...', actions) # self.actions = [['BTC/BUSD', 'Buy', self.six_price[4], amount_usdt / self.six_price[2] / self.six_price[1]], # ['BTC/USDT', 'Sell', self.six_price[3], amount_usdt / self.six_price[3]], # ['BUSD/USDT', 'Buy', self.six_price[0], amount_usdt / self.six_price[1]]] # 先交易第个 for action in actions: symbol = action[0] side = action[1] new_price = tools.float_to_str(action[2]) decimal_places = 2 if symbol == "BUSD/USDT" else 6 quantity = tools.round2(action[3], decimal_places) logger.info('order: ', symbol, new_price, quantity) order_no, error = await self.trader[symbol].create_order(side, new_price, quantity) if error: logger.error("create order error! error:", error, caller=self) break logger.info("create sell close order:", order_no, caller=self) await asyncio.sleep(0.01) if callback: SingleTask.run(callback, order_no)
async def connected_callback(self): """ After create Websocket connection successfully, we will subscribing orderbook/trade/kline events. """ if not self._symbols: logger.warn("symbols not found in config file.", caller=self) return if not self._channels: logger.warn("channels not found in config file.", caller=self) return for ch in self._channels: if ch == "trade": params = [] for s in self._symbols: params.append(s.replace("/", "_")) request_id = await self._generate_request_id() d = {"id": request_id, "method": "trades.subscribe", "params": params} await self.ws.send_json(d) logger.info("subscribe trade success.", caller=self) elif ch == "orderbook": params = [] for s in self._symbols: params.append([s.replace("/", "_"), int(self._orderbook_length), str(self._orderbook_price_precious)]) request_id = await self._generate_request_id() d = {"id": request_id, "method": "depth.subscribe", "params": params} await self.ws.send_json(d) logger.info("subscribe orderbook success.", caller=self) elif ch == "kline": for s in self._symbols: params = [s.replace("/", "_"), 60] request_id = await self._generate_request_id() d = {"id": request_id, "method": "kline.subscribe", "params": params} await self.ws.send_json(d) logger.info("subscribe kline success.", caller=self) else: logger.error("channel error:", ch, caller=self) continue
async def on_time(self): """ 每秒钟执行一次. 因为是异步并发架构,这个函数执行的时候交易通道链接不一定已经建立好 """ logger.info("on_time ...", caller=self)
async def on_asset_update_callback(self, asset: Asset): """ 账户资产更新 """ logger.info("asset:", asset, caller=self)
async def on_position_update_callback(self, position: Position): """ 持仓更新 """ logger.info("position:", position, caller=self)
async def on_fill_update_callback(self, fill: Fill): """ 订单成交通知 """ logger.info("fill:", fill, caller=self)
async def on_order_update_callback(self, order: Order): """ 订单状态更新 """ logger.info("order:", order, caller=self)
async def on_ticker_update_callback(self, ticker: Ticker): """ 市场行情tick更新 """ logger.info("ticker:", ticker, caller=self)
async def on_kline_update_callback(self, kline: Kline): """ 市场K线更新 """ logger.info("kline:", kline, caller=self)
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)) if self._init_success_callback: SingleTask.run(self._init_success_callback, False, e) return for order_info in order_infos: order_no = "{}_{}".format(order_info["orderId"], order_info["clientOrderId"]) 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) continue info = { "platform": self._platform, "account": self._account, "strategy": self._strategy, "order_no": order_no, "action": order_info["side"], "order_type": order_info["type"], "symbol": self._symbol, "price": order_info["price"], "quantity": order_info["origQty"], "remain": float(order_info["origQty"]) - float(order_info["executedQty"]), "status": status, "ctime": order_info["time"], "utime": order_info["updateTime"] } order = Order(**info) self._orders[order_no] = order if self._order_update_callback: SingleTask.run(self._order_update_callback, copy.copy(order)) if self._init_success_callback: SingleTask.run(self._init_success_callback, True, None)
def stop(self): """Stop the event loop.""" logger.info("stop io loop.", caller=self) self.loop.stop()
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._init_success_callback, False, e) return logger.info("Websocket connection authorized successfully.", caller=self) # Fetch orders from server. (open + partially filled) result, error = await self._rest_api.get_order_list( self._symbol, 6) if error: e = Error("get open orders error: {}".format(error)) SingleTask.run(self._init_success_callback, False, e) return if len(result) > 100: logger.warn("order length too long! (more than 100)", caller=self) for order_info in result["order_info"]: self._update_order(order_info) # Fetch positions from server. position, error = await self._rest_api.get_position(self._symbol) if error: e = Error("get position error: {}".format(error)) SingleTask.run(self._init_success_callback, False, e) return if len(position["holding"]) > 0: self._update_position(position["holding"][0]) # Subscribe order channel and position channel. data = { "op": "subscribe", "args": [self._order_channel, self._position_channel] } await self.ws.send_json(data) return # Subscribe response message received. if msg.get("event") == "subscribe": if msg.get("channel") == self._order_channel: self._subscribe_order_ok = True if msg.get("channel") == self._position_channel: self._subscribe_position_ok = True if self._subscribe_order_ok and self._subscribe_position_ok: SingleTask.run(self._init_success_callback, True, None) return # Order update message received. if msg.get("table") == "futures/order": for data in msg["data"]: self._update_order(data) return # Position update message receive. if msg.get("table") == "futures/position": for data in msg["data"]: self._update_position(data)
async def on_state_update_callback(self, state: State, **kwargs): """ 状态变化(底层交易所接口,框架等)通知回调函数 """ logger.info("on_state_update_callback:", state, caller=self)
async def process_binary(self, raw): """ 处理websocket上接收到的消息 @param raw 原始的压缩数据 """ decompress = zlib.decompressobj(-zlib.MAX_WBITS) msg = decompress.decompress(raw) msg += decompress.flush() msg = msg.decode() if msg == "pong": # 心跳返回 return msg = json.loads(msg) logger.debug("msg:", msg, caller=self) # 登陆成功之后再订阅数据 if msg.get("event") == "login": if not msg.get("success"): e = Error( "Websocket connection authorized failed: {}".format(msg)) logger.error(e, caller=self) if self._init_success_callback: SingleTask.run(self._init_success_callback, False, e) return logger.info("Websocket connection authorized successfully.", caller=self) # 获取当前等待成交和部分成交的订单信息 result, error = await self._rest_api.get_order_list( self._symbol, 6) if error: e = Error("get open orders error: {}".format(error)) if self._init_success_callback: SingleTask.run(self._init_success_callback, False, e) return for order_info in result["order_info"]: order = self._update_order(order_info) if self._order_update_callback: SingleTask.run(self._order_update_callback, order) # 获取当前持仓 position, error = await self._rest_api.get_position(self._symbol) if error: e = Error("get position error: {}".format(error)) if self._init_success_callback: SingleTask.run(self._init_success_callback, False, e) return if len(position["holding"]) > 0: self._update_position(position["holding"][0]) if self._position_update_callback: SingleTask.run(self._position_update_callback, self.position) # 订阅account, order, position data = { "op": "subscribe", "args": [self._order_channel, self._position_channel] } await self.ws.send_json(data) return # 订阅返回消息 if msg.get("event") == "subscribe": if msg.get("channel") == self._order_channel: self._subscribe_order_ok = True if msg.get("channel") == self._position_channel: self._subscribe_position_ok = True if self._subscribe_order_ok and self._subscribe_position_ok: if self._init_success_callback: SingleTask.run(self._init_success_callback, True, None) return # 订单更新 if msg.get("table") == "futures/order": for data in msg["data"]: order = self._update_order(data) if order and self._order_update_callback: SingleTask.run(self._order_update_callback, order) return # 持仓更新 if msg.get("table") == "futures/position": for data in msg["data"]: self._update_position(data) if self._position_update_callback: SingleTask.run(self._position_update_callback, self.position)
async def on_trade_update_callback(self, trade: Trade): """ 市场最新成交更新 """ logger.info("trade:", trade, caller=self)