async def _connect(self): logger.info("url:", self._url, caller=self) proxy = config.proxy if not self.session: self.session = aiohttp.ClientSession() try: self._ws = await self.session.ws_connect(self._url, proxy=proxy) except (aiohttp.client_exceptions.ClientConnectorError, aiohttp.client_exceptions.ClientHttpProxyError, aiohttp.client_exceptions.ServerDisconnectedError, aiohttp.client_exceptions.WSServerHandshakeError, asyncio.TimeoutError) as e: logger.error("connect to server error! url:", self._url, caller=self) state = State( self._platform, self._account, "connect to server error! url: {}, error: {}".format( self._url, e), State.STATE_CODE_CONNECT_FAILED) SingleTask.run(self.cb.on_state_update_callback, state) asyncio.get_event_loop().create_task( self._reconnect()) #如果连接出错就重新连接 return state = State(self._platform, self._account, "connect to server success! url: {}".format(self._url), State.STATE_CODE_CONNECT_SUCCESS) SingleTask.run(self.cb.on_state_update_callback, state) asyncio.get_event_loop().create_task(self.connected_callback()) asyncio.get_event_loop().create_task(self.receive())
async def _task(): if indicate_type == INDICATE_ORDER and self.cb.on_order_update_callback: success, error = await self.get_orders(symbol) if error: state = State(self._platform, self._account, "get_orders error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) return for order in success: SingleTask.run(self.cb.on_order_update_callback, order) elif indicate_type == INDICATE_ASSET and self.cb.on_asset_update_callback: success, error = await self.get_assets() if error: state = State(self._platform, self._account, "get_assets error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) return SingleTask.run(self.cb.on_asset_update_callback, success) elif indicate_type == INDICATE_POSITION and self.cb.on_position_update_callback: success, error = await self.get_position(symbol) if error: state = State(self._platform, self._account, "get_position error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) return SingleTask.run(self.cb.on_position_update_callback, success)
async def launch(self): """ 模拟交易接口连接初始化成功 """ state = State(self._platform, self._account, "connect to server success", State.STATE_CODE_CONNECT_SUCCESS) await self.cb.on_state_update_callback(state) await self.init_asset() state = State(self._platform, self._account, "Environment ready", State.STATE_CODE_READY) await self.cb.on_state_update_callback(state)
async def process(self, msg): """ Process message that received from websocket. Args: msg: message received from websocket. Returns: None. """ if not isinstance(msg, dict): return logger.debug("msg:", json.dumps(msg), caller=self) #{"type": "error", "code": 400, "msg": "Invalid login credentials"} if msg["type"] == "error": state = State(self._platform, self._account, "Websocket connection failed: {}".format(msg), State.STATE_CODE_GENERAL_ERROR) logger.error(state, caller=self) SingleTask.run(self.cb.on_state_update_callback, state) elif msg["type"] == "pong": return elif msg["type"] == "info": if msg["code"] == 20001: #交易所重启了,我们就断开连接,websocket会自动重连 @async_method_locker("FTXTrader._ws_close.locker") async def _ws_close(): await self.socket_close() SingleTask.run(_ws_close) elif msg["type"] == "unsubscribed": return #{'type': 'subscribed', 'channel': 'trades', 'market': 'BTC-PERP'} elif msg["type"] == "subscribed": self._subscribe_response_count = self._subscribe_response_count + 1 #每来一次订阅响应计数就加一 if self._subscribe_response_count == 2: #所有的订阅都成功了,通知上层接口都准备好了 state = State(self._platform, self._account, "Environment ready", State.STATE_CODE_READY) SingleTask.run(self.cb.on_state_update_callback, state) elif msg["type"] == "update": channel = msg['channel'] if channel == 'orders': self._update_order(msg) elif channel == 'fills': self._update_fill(msg)
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())
def __init__(self, **kwargs): """Initialize.""" self.cb = kwargs["cb"] self._platform = kwargs.get("databind") self._symbols = kwargs.get("symbols") self._strategy = kwargs.get("strategy") self._account = kwargs.get("account") state = None if not self._platform: state = State(self._platform, self._account, "param platform miss") elif not self._symbols: state = State(self._platform, self._account, "param symbols miss") elif not self._strategy: state = State(self._platform, self._account, "param strategy miss") if state: logger.error(state, caller=self) return super(VirtualTrader, self).__init__(**kwargs)
async def _reconnect(self, delay=5): """ 重新建立websocket连接 """ if delay > 0: await asyncio.sleep(delay) #等待一段时间再重连 logger.warn("reconnecting websocket right now!", caller=self) state = State( self._platform, self._account, "reconnecting websocket right now! url: {}".format(self._url), State.STATE_CODE_RECONNECTING) SingleTask.run(self.cb.on_state_update_callback, state) await self._connect()
async def _check_connection(cls, *args, **kwargs): try: ns = await cls._mongo_client.list_database_names() if ns and isinstance(ns, list) and "admin" in ns: cls._connected = True except Exception as e: cls._connected = False logger.error("mongodb connection ERROR:", e) finally: SingleTask.call_later(cls._check_connection, 2) #开启下一轮检测 #数据库连接状态通知上层策略 if cls._last_state != cls.is_connected(): #状态发生变化 cls._last_state = cls.is_connected() if cls._last_state: state = State(None, None, "mongodb connection SUCCESS", State.STATE_CODE_DB_SUCCESS) else: state = State(None, None, "mongodb connection ERROR", State.STATE_CODE_DB_ERROR) for cb in cls._state_cbs: SingleTask.run(cb, state)
def __init__(self, **kwargs): """Initialize.""" self.cb = kwargs["cb"] self._platform = kwargs.get("databind") self._symbols = kwargs.get("symbols") self._strategy = kwargs.get("strategy") self._account = kwargs.get("account") state = None if not self._platform: state = State(self._platform, self._account, "param platform miss") elif not self._symbols: state = State(self._platform, self._account, "param symbols miss") elif not self._strategy: state = State(self._platform, self._account, "param strategy miss") if state: logger.error(state, caller=self) return #资产列表 self._assets: DefaultDict[str: Dict[str, float]] = defaultdict(lambda: {k: 0.0 for k in {'free', 'locked', 'total'}}) #替换k线回调函数(K线方式驱动回测引擎) self._original_on_kline_update_callback = self.cb.on_kline_update_callback self.cb.on_kline_update_callback = self.on_kline_update_callback #替换订单簿回调函数(订单薄方式驱动回测引擎) self._original_on_orderbook_update_callback = self.cb.on_orderbook_update_callback self.cb.on_orderbook_update_callback = self.on_orderbook_update_callback #替换市场成交回调函数(市场成交方式驱动回测引擎) self._original_on_trade_update_callback = self.cb.on_trade_update_callback self.cb.on_trade_update_callback = self.on_trade_update_callback #为每个交易对绑定回测撮合引擎 self.bind_match_engine(**kwargs) super(BacktestTrader, self).__init__(**kwargs)
async def process(self, msg): """ Process message that received from websocket. Args: msg: message received from websocket. Returns: None. """ if not isinstance(msg, dict): return logger.debug("msg:", json.dumps(msg), caller=self) #{"type": "pong"} if msg.get("type") == "pong": return #{"type": "error", "code": 400, "msg": "Invalid login credentials"} elif msg["type"] == "error": state = State(self._platform, self._account, "Websocket connection failed: {}".format(msg), State.STATE_CODE_GENERAL_ERROR) logger.error(state, caller=self) SingleTask.run(self.cb.on_state_update_callback, state) elif msg["type"] == "info": if msg["code"] == 20001: #交易所重启了,我们就断开连接,websocket会自动重连 @async_method_locker("FTXMarket._ws_close.locker") async def _ws_close(): await self.socket_close() SingleTask.run(_ws_close) elif msg["type"] == "unsubscribed": return #{'type': 'subscribed', 'channel': 'trades', 'market': 'BTC-PERP'} elif msg["type"] == "subscribed": return elif msg["type"] == "update" or msg["type"] == "partial": channel = msg['channel'] if channel == 'orderbook': self._update_orderbook(msg) elif channel == 'trades': self._update_trades(msg) elif channel == 'ticker': self._update_ticker(msg)
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"): state = State( self._platform, self._account, "Websocket connection authorized failed: {}".format(msg), State.STATE_CODE_GENERAL_ERROR) logger.error(state, caller=self) SingleTask.run(self.cb.on_state_update_callback, state) return logger.info("Websocket connection authorized successfully.", caller=self) await self._auth_success_callback() # Subscribe response message received. elif msg.get("event") == "subscribe": #msg.get("channel") self._subscribe_response_count = self._subscribe_response_count + 1 #每来一次订阅响应计数就加一 count = len(self._account_channel) + len( self._order_channel) #应该要返回的订阅响应数 if self._subscribe_response_count == count: #所有的订阅都成功了,通知上层接口都准备好了 state = State(self._platform, self._account, "Environment ready", State.STATE_CODE_READY) SingleTask.run(self.cb.on_state_update_callback, state) elif msg.get("event") == "error": state = State(self._platform, self._account, "Websocket processing failed: {}".format(msg), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) # Order update message received. elif msg.get("table") == "spot/order": """ { "table":"spot/order", "data":[ { "client_oid":"", "filled_notional":"0", "filled_size":"0", "instrument_id":"ETC-USDT", "last_fill_px":"0", "last_fill_qty":"0", "last_fill_time":"1970-01-01T00:00:00.000Z", "margin_trading":"1", "notional":"", "order_id":"3576398568830976", "order_type":"0", "price":"5.826", "side":"buy", "size":"0.1", "state":"0", "status":"open", "timestamp":"2019-09-24T06:45:11.394Z", "type":"limit", "created_at":"2019-09-24T06:45:11.394Z" } ] } """ for data in msg["data"]: self._update_order(data) elif msg.get("table") == "spot/account": self._update_asset(msg["data"])
async def _auth_success_callback(self): """ 授权成功之后回调 """ #获取相关符号信息 """ [ { "base_currency":"BTC", "instrument_id":"BTC-USDT", "min_size":"0.001", "quote_currency":"USDT", "size_increment":"0.00000001", "tick_size":"0.1" }, { "base_currency":"OKB", "instrument_id":"OKB-USDT", "min_size":"1", "quote_currency":"USDT", "size_increment":"0.0001", "tick_size":"0.0001" } ] """ success, error = await self._rest_api.get_symbols_info() if error: state = State(self._platform, self._account, "get_symbols_info error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return for info in success: self._syminfo[info[ "instrument_id"]] = info #符号信息一般不变,获取一次保存好,其他地方要用直接从这个变量获取就可以了 #获取账户余额,更新资产 """ [ { "frozen":"0", "hold":"0", "id": "", "currency":"BTC", "balance":"0.0049925", "available":"0.0049925", "holds":"0" }, { "frozen":"0", "hold":"0", "id": "", "currency":"USDT", "balance":"226.74061435", "available":"226.74061435", "holds":"0" }, { "frozen":"0", "hold":"0", "id": "", "currency":"EOS", "balance":"0.4925", "available":"0.4925", "holds":"0" } ] """ success, error = await self._rest_api.get_account_balance() if error: state = State(self._platform, self._account, "get_account_balance error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return self._update_asset(success) # Fetch orders from server. (open + partially filled) for sym in self._symbols: """ [ { "client_oid":"oktspot86", "created_at":"2019-03-20T03:28:14.000Z", "filled_notional":"0", "filled_size":"0", "funds":"", "instrument_id":"BTC-USDT", "notional":"", "order_id":"2511109744100352", "order_type":"0", "price":"3594.7", "price_avg":"", "product_id":"BTC-USDT", "side":"buy", "size":"0.001", "status":"open", "state":"0", "timestamp":"2019-03-20T03:28:14.000Z", "type":"limit" } ] """ success, error = await self._rest_api.get_open_orders(sym) if error: state = State(self._platform, self._account, "get open orders error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return for order_info in success: self._update_order(order_info) # Subscribe order channel. if self.cb.on_order_update_callback or self.cb.on_fill_update_callback: data = {"op": "subscribe", "args": self._order_channel} await self.send_json(data) #订阅账户余额通知 if self.cb.on_asset_update_callback: sl = [] for sym in self._symbols: si = self._syminfo[sym] if si: sl.append(si["base_currency"]) sl.append(si["quote_currency"]) #set的目的是去重 self._account_channel = [] for s in set(sl): self._account_channel.append( "spot/account:{symbol}".format(symbol=s)) #发送订阅 data = {"op": "subscribe", "args": self._account_channel} await self.send_json(data) #计数初始化0 self._subscribe_response_count = 0
def __init__(self, **kwargs): """Initialize.""" self.cb = kwargs["cb"] state = None self._platform = kwargs.get("platform") self._symbols = kwargs.get("symbols") self._strategy = kwargs.get("strategy") self._account = kwargs.get("account") self._access_key = kwargs.get("access_key") self._secret_key = kwargs.get("secret_key") self._passphrase = kwargs.get("passphrase") if not self._platform: state = State(self._platform, self._account, "param platform miss") elif self._account and (not self._access_key or not self._secret_key or not self._passphrase): state = State(self._platform, self._account, "param access_key or secret_key or passphrase miss") elif not self._strategy: state = State(self._platform, self._account, "param strategy miss") elif not self._symbols: state = State(self._platform, self._account, "param symbols miss") if state: logger.error(state, caller=self) SingleTask.run(self.cb.on_state_update_callback, state) return self._host = "https://www.okex.com" self._wss = "wss://real.okex.com:8443" self._order_channel = [] for sym in self._symbols: self._order_channel.append( "spot/order:{symbol}".format(symbol=sym)) url = self._wss + "/ws/v3" super(OKExTrader, self).__init__(url, send_hb_interval=5, **kwargs) self.heartbeat_msg = "ping" self._syminfo: DefaultDict[str:Dict[str, Any]] = defaultdict(dict) self._assets: DefaultDict[str:Dict[str, float]] = defaultdict( lambda: {k: 0.0 for k in {'free', 'locked', 'total'}}) # Initializing our REST API client. self._rest_api = OKExRestAPI(self._host, self._access_key, self._secret_key, self._passphrase) if self._account != None: self.initialize() #如果四个行情回调函数都为空的话,就根本不需要执行市场行情相关代码 if (self.cb.on_kline_update_callback or self.cb.on_orderbook_update_callback or self.cb.on_trade_update_callback or self.cb.on_ticker_update_callback): #市场行情数据 OKExMarket(**kwargs)
def __init__(self, **kwargs): """initialize trader object. Args: strategy: 策略名称,由哪个策略发起 platform: 交易平台 databind: 这个字段只有在platform等于datamatrix或backtest的时候才有用,代表为矩阵操作或策略回测提供历史数据的交易所 symbols: 策略需要订阅和交易的符号 account: 交易所登陆账号,如果为空就只是订阅市场公共行情数据,不进行登录认证,所以也无法进行交易等 access_key: 登录令牌 secret_key: 令牌密钥 cb: ExchangeGateway.ICallBack { on_state_update_callback: `状态变化`(底层交易所接口,框架等)通知回调函数 on_kline_update_callback: `K线数据`通知回调函数 (值为None就不启用此通知回调) on_orderbook_update_callback: `订单簿深度数据`通知回调函数 (值为None就不启用此通知回调) on_trade_update_callback: `市场最新成交`通知回调函数 (值为None就不启用此通知回调) on_ticker_update_callback: `市场行情tick`通知回调函数 (值为None就不启用此通知回调) on_order_update_callback: `用户挂单`通知回调函数 (值为None就不启用此通知回调) on_fill_update_callback: `用户挂单成交`通知回调函数 (值为None就不启用此通知回调) on_position_update_callback: `用户持仓`通知回调函数 (值为None就不启用此通知回调) on_asset_update_callback: `用户资产`通知回调函数 (值为None就不启用此通知回调) } """ T = gateway_class(kwargs["platform"]) #找到指定的交易接口类 if T == None: logger.error("platform not found:", kwargs["platform"], caller=self) cb = kwargs["cb"] SingleTask.run( cb.on_state_update_callback, State(kwargs["platform"], kwargs.get("account"), "platform not found")) return #------------------------------------------------ #符号映射转换相关 self.is_upper = False #币种符号是否转换成大写 self.system_to_native = {} #'量化平台通用交易符号'转换成'交易所原始符号' self.native_to_system = {} #'交易所原始符号'转换成'量化平台通用交易符号' layer = T.mapping_layer() #从交易所获取符号转换层信息 if layer: self.system_to_native = layer.map_dict #'交易对'符号映射信息,类似 BTC/USDT -> btcusdt self.is_upper = layer.is_upper #'币种'符号是否大写,类似 btc -> BTC for (k, v) in self.system_to_native.items( ): #生成逆转换映射信息,类似 btcusdt -> BTC/USDT self.native_to_system[v] = k #符号转换 if self.system_to_native: #存在映射信息,意味着启用了符号转换功能 native_symbol = [] for sym in kwargs["symbols"]: if not self.system_to_native.get(sym): #如果符号映射信息中没有,返回错误 logger.error("symbols not found:", kwargs["symbols"], caller=self) cb = kwargs["cb"] SingleTask.run( cb.on_state_update_callback, State(kwargs["platform"], kwargs.get("account"), "symbols not found")) return native_symbol.append(self.system_to_native[sym]) kwargs["symbols"] = native_symbol #映射功能启用就需要hook回调函数 if self.system_to_native or self.is_upper: self.hookCB(kwargs["cb"]) # self._t = T(**kwargs)
async def connected_callback(self): """网络链接成功回调 """ if self._account != None: #账号不为空就要进行登录认证,然后订阅2个需要登录后才能订阅的私有频道:用户挂单通知和挂单成交通知(FTX只支持这2个私有频道) await self._login() #登录认证 success, error = await self._rest_api.list_markets() if error: state = State(self._platform, self._account, "list_markets error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return for info in success["result"]: self._syminfo[ info["name"]] = info #符号信息一般不变,获取一次保存好,其他地方要用直接从这个变量获取就可以了 if self.cb.on_order_update_callback != None: for sym in self._symbols: orders, error = await self.get_orders(sym) if error: state = State(self._platform, self._account, "get_orders error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return for o in orders: SingleTask.run(self.cb.on_order_update_callback, o) if self.cb.on_position_update_callback != None: for sym in self._symbols: pos, error = await self.get_position(sym) if error: state = State(self._platform, self._account, "get_position error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return SingleTask.run(self.cb.on_position_update_callback, pos) if self.cb.on_asset_update_callback != None: ast, error = await self.get_assets() if error: state = State(self._platform, self._account, "get_assets error: {}".format(error), State.STATE_CODE_GENERAL_ERROR) SingleTask.run(self.cb.on_state_update_callback, state) #初始化过程中发生错误,关闭网络连接,触发重连机制 await self.socket_close() return SingleTask.run(self.cb.on_asset_update_callback, ast) #`用户挂单通知回调`不为空,就进行订阅 if self.cb.on_order_update_callback != None: await self.send_json({'op': 'subscribe', 'channel': 'orders'}) #`用户挂单成交通知回调`不为空,就进行订阅 if self.cb.on_fill_update_callback != None: await self.send_json({'op': 'subscribe', 'channel': 'fills'}) #计数初始化0 self._subscribe_response_count = 0
def __init__(self, **kwargs): """Initialize.""" self.cb = kwargs["cb"] state = None self._platform = kwargs.get("platform") self._symbols = kwargs.get("symbols") self._strategy = kwargs.get("strategy") self._account = kwargs.get("account") self._access_key = kwargs.get("access_key") self._secret_key = kwargs.get("secret_key") self._subaccount_name = kwargs.get("subaccount_name") if not self._platform: state = State(self._platform, self._account, "param platform miss") elif self._account and (not self._access_key or not self._secret_key): state = State(self._platform, self._account, "param access_key or secret_key miss") elif not self._strategy: state = State(self._platform, self._account, "param strategy miss") elif not self._symbols: state = State(self._platform, self._account, "param symbols miss") if state: logger.error(state, caller=self) SingleTask.run(self.cb.on_state_update_callback, state) return self._host = "https://ftx.com" self._wss = "wss://ftx.com" url = self._wss + "/ws" super(FTXTrader, self).__init__(url, send_hb_interval=15, **kwargs) self.heartbeat_msg = {"op": "ping"} # Initializing our REST API client. self._rest_api = FTXRestAPI(self._host, self._access_key, self._secret_key, self._subaccount_name) #订单簿深度数据 self._orderbooks: DefaultDict[str, Dict[str, DefaultDict[ float, float]]] = defaultdict( lambda: {side: defaultdict(float) for side in {'bids', 'asks'}}) self._assets: DefaultDict[str:Dict[str, float]] = defaultdict( lambda: {k: 0.0 for k in {'free', 'locked', 'total'}}) self._syminfo: DefaultDict[str:Dict[str, Any]] = defaultdict(dict) if self._account != None: self.initialize() #如果四个行情回调函数都为空的话,就根本不需要执行市场行情相关代码 if (self.cb.on_kline_update_callback or self.cb.on_orderbook_update_callback or self.cb.on_trade_update_callback or self.cb.on_ticker_update_callback): #市场行情数据 FTXMarket(**kwargs)