#Accessing data stored in BtfxWss: ticker_q = wss.tickers('BTCUSD') while not ticker_q.empty(): print(ticker_q.get()[0][0][0]) break ################################################################################## curOrder = wss.orders.get() nbOrders = len(curOrder[0][1]) #number of orders pricy = "8399" #Must be string if nbOrders != 0: #Cancel all orders for item in curOrder[0][1]: wss.cancel_order(id=item[0]) ####################################################################### Morder = { "type": "EXCHANGE LIMIT", "symbol": "tBTCUSD", "amount": "-0.007", "price": "", "hidden": 0 } Zorder = replace_value_with_definition(Morder, 'price', pricy) time.sleep(5) #First make sure to cancel wss.new_order(**Zorder) print(wss.orders_new.get())
class Trader(_Account): """ Class interface for trading on the Bitfinex Exchange :param key: str: Bitfinex api-key :param secret: str: Bitfinex api-secret """ _sleep_time = 0.01 _min_order_value = 35. _max_order_history = 100 _trade_types = {'market', 'limit'} _sources = ('Orders', 'Order New', 'Order Update', 'Order Cancel', 'Wallets') def __init__(self, key, secret): super(Trader, self).__init__() self.symbols = [] self.symbols_gen = get_symbols_as_updated() self.wss = BtfxWss(key=key, secret=secret, log_level='CRITICAL') self._disconnect_event = Event() self._receiver = None def cancel(self, _id): """ Cancel an order :param _id: int: order id :return: None """ self.wss.cancel_order(multi=False, id=_id) def cancel_all(self, older_than=0): """ Cancel all orders older than a certain time :param older_than: int/float: age of order that should be cancelled (in seconds) :return: None """ now = time.time() for _id, order in self._orders.copy().items(): if now - order['timestamp'] > older_than: self.cancel(_id) def connect(self): """Open a connection to a Bitfinex websocket""" self.wss.start() while not self.wss.conn.connected.is_set(): time.sleep(1e-4) self.wss.authenticate() # This thread will wait for 99.999% of its life subscriber_thread = Thread(target=self._subscribe) subscriber_thread.setDaemon(True) subscriber_thread.start() # This thread should be working and is therefore joined self._receiver = Thread(target=self._receive) self._receiver.start() def close(self): """Close the connection to the Bitfinex websocket""" self._disconnect_event.set() for symbol in self.symbols: self.wss.unsubscribe_from_ticker(symbol) self.wss.stop() self._receiver.join() def order(self, symbol, price, *, dollar_amount=None, ratio=None, value_ratio=None, trade_type='limit', pad_price=None, return_id=True): """ Make an exchange order :param symbol: str: ticker symbol to order (e.g. 'BTCUSD', 'ETHUSD'...) :param price: float: Price at which to submit order You can also pass "market" as an argument for price to order at current market value e.g. Trader.order('BTCUSD', 'market', dollar_amount=5) :param dollar_amount: float: Dollar equivalent value of trade amount (negative for selling) e.g. ordering 10 BTCUSD at $5 per coin -> Trader.order('BTCUSD', 5, dollar_amount=50) :param ratio: float: Ratio of available balance for requested symbol (negative for sell) e.g. With $1000 USD in wallet, ordering 100 BTCUSD at $5 per coin -> Trader.order('BTCUSD', 5, ratio=0.5) :param value_ratio: float: Ratio of Trader.value (negative for selling) e.g. With $500 in USD and $500 in BTCUSD (at $5) in wallet, ordering 100 BTCUSD at $5 per coin -> Trader.order('BTCUSD', 5, value_ratio=0.5) :param trade_type: str: (optional) Bitfinex api trade type - one of {'market', 'limit'} see https://www.bitfinex.com/features for details :param pad_price: float: (optional) Ratio based price padding - used to undercut or overshoot price If buying then pad_price * price is added to submitted price If selling then pad_price * price is subtracted from price :param return_id: bool: (optional) If true, Trader.order blocks until order_id is returned from Bitfinex, otherwise returns immediately :return: int: If return_id is True, then order id is returned, otherwise None :raises: AssertionError: If values passed are non-consistent """ assert dollar_amount or ratio or value_ratio, \ 'Must provide either `dollar_amount`, `ratio` or `value_ratio`' assert sum(bool(i) for i in [dollar_amount, ratio, value_ratio]) == 1,\ 'Must provide only 1 of `dollar_amount`, `ratio` or `value_ratio`' assert trade_type.lower() in self._trade_types, \ 'Unknown trade type, try one of these: %s' % str(self._trade_types) symbol_lower = symbol.lower().replace('usd', '') symbol = symbol.upper().split('USD')[0] + 'USD' buying = (dollar_amount or ratio or value_ratio) > 0 if price == 'market' or trade_type == 'market': trade_type = 'market' price = self._prices[symbol] if pad_price: delta = price * pad_price price += max(delta, 0.01) if buying else min(-delta, -0.01) assert price >= 0.01, 'Price cannot be less than $0.01' if buying: max_amount = self.available_balances['usd'] / price else: max_amount = self.available_balances[symbol_lower] if dollar_amount: amount = dollar_amount / price elif ratio: amount = max_amount * ratio else: amount = min(max(-max_amount, self.value * value_ratio / price), max_amount) amount, max_amount = round(amount, 8), round(max_amount, 8) assertion_msg = 'Trade value (${:.2f}) is %s available trade value ' \ '($%.2f)'.format(abs(amount * price)) assert abs(amount) >= self._min_order_value / price, \ assertion_msg % ('below minimum', self._min_order_value) assert abs(amount) <= max_amount, \ assertion_msg % ('above maximum', max_amount * price) current_order_ids = set(self._orders.keys()) if len(self._executed_orders) == 0: last_executed_id = None else: last_executed_id = self._executed_orders[-1][0] self.wss.new_order( cid=int(time.time()), type="EXCHANGE %s" % trade_type.upper(), symbol="t%s" % symbol, amount="%.8f" % amount, price="%.2f" % price, ) while return_id: for _id in self._orders.copy().keys(): # new order arrives in _orders if _id not in current_order_ids: return _id else: # new order is executed immediately if len(self._executed_orders) > 0 \ and self._executed_orders[-1][0] != last_executed_id: return self._executed_orders[-1][0] time.sleep(self._sleep_time) def subscribe(self, symbol): """ Subscribe to a symbol for price watching :param symbol: str: symbol to subscribe to (e.g. 'BTCUSD', 'ETHUSD'...) """ self.symbols.append(symbol) self.wss.subscribe_to_ticker(symbol) def wait_execution(self, _id, seconds=1e9): """ Waits for an order to execute :param _id: int: id of order to wait for :param seconds: int/float: time to wait before raising error :return: dict: order in json format :raises: TimeoutError (if order is not executed in given time) """ start_time = time.time() while time.time() - start_time < seconds: try: ids, trades = tuple(zip(*self._executed_orders)) return trades[ids.index(_id)] except ValueError: time.sleep(self._sleep_time) raise TimeoutError('Waiting for execution of order ' '%d timed out after %d seconds' % (_id, seconds)) def _receive(self): while not self._disconnect_event.is_set(): for source in self._sources: with suppress(Empty): q = self.wss.queue_processor.account[source] cmd, data = q.get_nowait()[0] self._update(cmd, data) for symbol in self.symbols: with suppress(Empty): q = self.wss.queue_processor.tickers[('ticker', symbol)] data = q.get_nowait()[0] self._update('t' + symbol, data) def _subscribe(self): for symbol in self.symbols_gen: self.subscribe(symbol)
class WebSocketApi(object): """ Wrapper to use BtfxWss. """ def __init__(self, symbols=None, callbacks=None): """ Args: symbols: A list used to subscribe tickers. callbacks: A list of functions to handle events: 'reset', 'process_wallet', 'process_order', 'process_tick', 'process_notification' """ required_callbacks = [ 'reset', 'process_wallet', 'process_order', 'process_tick', 'process_notification' ] if not symbols or not callbacks: log(LOG_ERROR, 'Require parameters symbols and callbacks') return for callback in required_callbacks: if callback not in callbacks: log(LOG_ERROR, 'Require %s callback function' % callback) return self._tick_symbols = symbols self._callbacks = callbacks self._received_order_snapshot = False self._received_wallet_snapshot = False self._wss = BtfxWss( key=config.BFX_API_KEY, secret=config.BFX_API_SECRET) self._wss.start() def __connect(self): """ Reset data and subscribe tick data after connect to server. """ log(LOG_INFO, "Server connected") self._received_order_snapshot = False self._received_wallet_snapshot = False self._wss.authenticate() for pair in self._tick_symbols: symbol = 't' + pair self._wss.subscribe_to_ticker(symbol) def __check_system(self): """ Check the connection is established or not. """ try: server_q = self._wss.opened while not server_q.empty(): server_q.get() self._callbacks['reset']() self.__connect() except KeyError: # KeyError means Btfxwss doesn't get related information yet. # It's fine to pass and check in the next time. pass def __check_account_info(self): """ Check account information. """ try: wallets_q = self._wss.wallets while not wallets_q.empty(): self.__received_wallets(wallets_q.get()[0][1]) wallet_update_q = self._wss.wallet_update while not wallet_update_q.empty(): self.__received_wallet_update( wallet_update_q.get()[0][1]) orders_q = self._wss.orders while not orders_q.empty(): self.__received_orders(orders_q.get()[0][1]) order_new_q = self._wss.order_new while not order_new_q.empty(): self.__received_order(order_new_q.get()[0][1]) order_cancel_q = self._wss.order_cancel while not order_cancel_q.empty(): self.__received_order(order_cancel_q.get()[0][1]) notification_q = self._wss.notifications while not notification_q.empty(): self.__received_notification(notification_q.get()[0][1]) except KeyError: # KeyError means Btfxwss doesn't get related information yet. # It's fine to pass and check in the next time. pass def __check_tickers(self): """ Check tick data. """ for symbol in self._tick_symbols: try: trades_q = self._wss.tickers(symbol) while not trades_q.empty(): self.__received_tickers(symbol, trades_q.get()[0]) except KeyError: # KeyError means Btfxwss doesn't get related information # yet. It's fine to pass and check in the next time. pass def __check_unused_info(self): """ Websocket may have many events which are not used. Just pop it and ignore it. Otherwise, the queue may use too many memories. """ def pop_queue(queue): """ pop unused queue """ while not queue.empty(): queue.get() try: queues = [ self._wss.credits, self._wss.offer_new, self._wss.offer_cancel, self._wss.credit_new, self._wss.credit_close, self._wss.credit_update, self._wss.positions, self._wss.offer_update, self._wss.order_update, self._wss.position_update, self._wss.position_close, self._wss.loan_new, self._wss.loan_close, self._wss.loan_update, self._wss.unknown] for queue in queues: pop_queue(queue) except KeyError: # KeyError means Btfxwss doesn't get related information yet. # It's fine to pass and check in the next time. pass def __received_wallets(self, wallets): """ Handle wallet snapshot. Args: wallets: balance of all currencies """ for wallet in wallets: self._callbacks['process_wallet'](bitfinex_v2_rest.Wallet(wallet)) self._received_wallet_snapshot = True def __received_wallet_update(self, wallet): """ Handle wallet update. Args: wallets: balance of one currency """ self._callbacks['process_wallet'](bitfinex_v2_rest.Wallet(wallet)) def __received_orders(self, orders): """ Handle order snapshot. Args: orders: current orders snapshot """ for order in orders: self._callbacks['process_order'](bitfinex_v2_rest.Order(order)) self._received_order_snapshot = True def __received_order(self, order): """ Handle one order Args: order: order status """ self._callbacks['process_order'](bitfinex_v2_rest.Order(order)) def __received_tickers(self, pair, tickers): """ Handle ticks Args: pair: ex BTCUSD tickers: tickers of the pair """ if isinstance(tickers, list): for tick in tickers: self._callbacks['process_tick']( bitfinex_v2_rest.TradingTicker([pair] + tick)) def __received_notification(self, message): """ Handle notification Args: message: notification from web socket """ self._callbacks['process_notification']( bitfinex_v2_rest.Notifications(message)) def new_order(self, pair, price, amount): """ Create an new order. Args: pair: ex BTCUSD. price: 0 means the market order. amount: Positive number means buy order. Negative number means sell order. """ symbol = 't' + pair cid = int(round(time.time() * 1000)) if price > 0: order = { 'cid': cid, 'type': "EXCHANGE LIMIT", 'symbol': symbol, 'amount': str(amount), 'price': str(price), 'hidden': 0 } else: order = { 'cid': cid, 'type': "EXCHANGE MARKET", 'symbol': symbol, 'amount': str(amount), 'hidden': 0 } self._wss.new_order(**order) def cancel_order(self, order_id): """ Cancel an order. Args: order_id: The id of the order. """ value = {'id': order_id} self._wss.cancel_order(False, **value) def check_events(self): """ Check all events from web socket """ self.__check_system() self.__check_account_info() self.__check_tickers() self.__check_unused_info() def is_received_order_snapshot(self): """ Return True if recevied order snapshot """ return self._received_order_snapshot def is_received_wallet_snapshot(self): """ Return True if recevied wallet snapshot """ return self._received_wallet_snapshot