def on_ping(self, msg): try: if msg in self.books_following: book = self.get_order_book(msg) internal_book = {'bids': [], 'asks': []} bids = book['Z'] asks = book['S'] for bid in bids: internal_book['bids'].append( [str(bid['R']), str(bid['Q'])]) for ask in asks: internal_book['asks'].append( [str(ask['R']), str(ask['Q'])]) base, quote = OrderBookSocket.to_base_quote(msg) internal_book['base'] = base internal_book['quote'] = quote internal_book['exchange'] = 'bittrex' self.owner.notify_callbacks('order_book', data=internal_book) except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('bittrex order book socket failed with error: ' + str(e))
def create_order(self, base, quote, price, quantity, side, order_type, internal_order_id, request_id=None, requester_id=None, **kwargs): try: self.client.create_order(symbol=base + quote, side=side, type=order_type, timeInForce='GTC', quantity=quantity, price=price, newClientOrderId=internal_order_id) message = dict() message['internal_order_id'] = internal_order_id message['request_id'] = request_id message['requester_id'] = requester_id message['action'] = 'order_sent' message['exchange'] = self.name self.notify_callbacks('trade_lifecycle', trade_lifecycle_type='sent', data=message) except BinanceAPIException as e: logger().error( 'Failed to create order. Exception was: {}'.format(e))
def get_balances(self): try: account = self.client.get_account() self.notify_callbacks('account', account_type='balance', data=account['balances']) return account['balances'] except BinanceAPIException as e: logger().error( 'Failed to get balances. Exception was: {}'.format(e))
def follow_order_book(self, base, quote): cross = base + quote if cross not in self.order_book_services: logger().info('Subscribing to ' + cross) self.order_book_services[cross] = OrderBookService( self.client, base, quote, self.notify_callbacks, self.name) self.order_book_services[cross].start() return True else: logger().warning('Already subscribed to ' + base + quote) pass
def cancel_all(self, base, quote): product_id = self.get_product_id(base, quote) live_orders = self.client.get_orders(product_id=product_id, status='live') partially_filled_orders = self.client.get_orders( product_id=product_id, status='partially_filled') orders = live_orders['models'] + partially_filled_orders['models'] for order in orders: try: self.client.cancel_order(order['id']) except Exception as e: logger().error('Failed to cancel order with error: {}'.format( str(e))) time.sleep(2)
def cancel_order(self, base, quote, internal_order_id, request_id, requester_id=None, exchange_order_id=None): try: self.client.cancel_order(symbol=base + quote, origClientOrderId=internal_order_id) message = dict() message['action'] = 'cancel_sent' message['internal_order_id'] = internal_order_id message['request_id'] = request_id message['exchange'] = self.name self.user_data_service.pending_cancel[internal_order_id] = True self.notify_callbacks('trade_lifecycle', trade_lifecycle_type='cancel_sent', data=message) except BinanceAPIException as e: logger().error( 'Failed to cancel order. Exception was: {}'.format(e)) if str(e.code) == '-2011': self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'reason': 'order_not_found', 'base': base, 'quote': quote, 'exchange': self.name, 'exchange_order_id': exchange_order_id, 'internal_order_id': internal_order_id, 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) })
def cancel_all(self, base, quote): market = base + '/' + quote exchange_orders, error = self.client.get_openorders(market) for exchange_order in exchange_orders: try: exchange_order_id = str(exchange_order['OrderId']) resp, error = self.client.cancel_trade('Trade', exchange_order_id, None) if exchange_order_id in self.open_orders_by_exchange_id: self.open_orders_by_exchange_id.pop( exchange_order_id, None) internal_id = self.external_to_internal_id.pop( exchange_order, None) self.internal_to_external_id.pop(internal_id, None) if error is not None: raise Exception( 'Failed to cancel order with reason: {}'.format( str(error))) except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('cancel_all failed with error: ' + str(e))
def on_tick(self): try: logger().info('tick') if len(self.callbacks) > 0 and len(self.markets_following) > 0: for market in self.markets_following.copy().values(): base = market['base'] quote = market['quote'] self._send_executions_to_cb(base, quote) self._send_order_book_to_cb(base, quote) else: logger().info('Not following any markets') except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('on_tick failed with error: ' + str(e)) logger().info('tock') threading.Timer(self.poll_time_s, self.on_tick).start()
def get_fees_paid(self, base, quote, start_s, end_s): if start_s > end_s: logger().error('Start time cannot be after end time') return Decimal(0) product_id = self.get_product_id(base, quote) response = self.client.get_orders(product_id=product_id, limit=100) # Find start page via binary search total_pages = self.find_actual_total_pages(product_id, response['total_pages'], response['total_pages'] + 1, 0) first_page = self.find_first_or_last_page_of_orders( product_id, int(total_pages / 2), start_s, end_s, 'first', total_pages) if first_page == -1: logger().warn( 'Unable to find any orders on or after epoch time: {}'.format( start_s)) return Decimal('0') last_page = self.find_first_or_last_page_of_orders( product_id, int(total_pages / 2), start_s, end_s, 'last', total_pages) if last_page == -1: logger().warn( 'Unable to find any orders on or before epoch time: {}'.format( end_s)) return Decimal('0') # Request all pages in range [start, end] orders_in_range_with_fills = [] for page in range(first_page, last_page + 1): response = self.client.get_orders(product_id=product_id, page=page, limit=100) orders = response['models'] for order in orders: if start_s <= order['created_at'] <= end_s and order[ 'filled_quantity'] != '0.0': orders_in_range_with_fills.append(order) fees_in_quote_currency = Decimal('0') for order in orders_in_range_with_fills: fees_in_quote_currency += order['order_fee'] return fees_in_quote_currency
def on_tick(self): logger().info('tick') if len(self.callbacks) > 0: for product_id, details in self.markets_following.copy().items(): try: base = details['base'] quote = details['quote'] self._send_executions_to_cb(base, quote) self._send_order_book_to_cb(base, quote) except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('on_tick failed with error: ' + str(e)) logger().info('tock') if len(self.markets_following) > 0: threading.Timer(self.poll_time_s, self.on_tick).start()
def on_private(self, msg): try: if 'TY' in msg: update_type = msg['TY'] if update_type == 1 or update_type == 2: order = msg['o'] market = order['E'] base, quote = ExecutionsSocket.to_base_quote(market) if market in self.markets_following: exchange_order_id = str(order['OU']) internal_order = self.owner.get_order_by_exchange_id( exchange_order_id) if internal_order is not None: quantity = Decimal(str(order['Q'])) quantity_remaining = Decimal(str(order['q'])) price = str(order['PU']) if update_type == 1: status = 'PARTIALLY_FILLED' else: status = 'FILLED' new_fill_amount = str( quantity - quantity_remaining - Decimal( str(internal_order['cum_quantity_filled'])) ) internal_order['cum_quantity_filled'] = str( Decimal(str(internal_order['quantity'])) - quantity_remaining) message = { 'action': 'EXECUTION', 'exchange': self.owner.name, 'base': base, 'quote': quote, 'exchange_order_id': str(internal_order['exchange_order_id']), 'internal_order_id': str(internal_order['internal_order_id']), 'side': internal_order['side'], 'quantity': internal_order['quantity'], 'price': internal_order['price'], 'cum_quantity_filled': internal_order['cum_quantity_filled'], 'order_status': status, 'server_ms': int(round(time() * 1000)), 'received_ms': int(round(time() * 1000)), 'last_executed_quantity': new_fill_amount, 'last_executed_price': price, 'fee_base': 0, 'fee_quote': 0, 'trade_id': '-1' } if status == 'FILLED': index_to_del = None i = 0 for order in self.owner.open_orders: if order[ 'exchange_order_id'] == exchange_order_id: index_to_del = i break i += 1 if index_to_del is not None: self.owner.open_orders.pop(index_to_del) self.owner.notify_callbacks( 'trade_lifecycle', trade_lifecycle_type=message['action'], data=message) else: logger().error( 'Failed to get order with exchange id: ' + exchange_order_id) except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('bittrex execution socket failed with error: ' + str(e))
def create_order(self, base, quote, price, quantity, side, order_type, internal_order_id, request_id=None, requester_id=None, **kwargs): product_id = self.get_product_id(base, quote) if product_id is None: raise LookupError( 'Could not find a product with a base [{}] and quote [{}]'. format(base, quote)) try: exchange_side = self.client.SIDE_BUY if str.lower( side) == 'buy' else self.client.SIDE_SELL response = self.client.create_order(order_type, product_id, exchange_side, str(quantity), price=str(price)) if ('message' in response and len(response['message']) > 0) or \ ('errors' in response and len(response['errors']) > 0): self.notify_callbacks('trade_lifecycle', data={ 'action': 'CREATE_FAILED', 'reason': 'Unknown exception type', 'exchange': self.name, 'base': base, 'quote': quote, 'internal_order_id': str(internal_order_id), 'side': side, 'quantity': quantity, 'price': price, 'cum_quantity_filled': 0, 'received_ms': int(round(time.time() * 1000)) }) return except QuoineAPIException as e: logger().error('Failed to create order due to error: {}'.format(e)) self.notify_callbacks('trade_lifecycle', data={ 'action': 'CREATE_FAILED', 'reason': 'Unknown exception type', 'exchange': self.name, 'base': base, 'quote': quote, 'internal_order_id': str(internal_order_id), 'side': side, 'quantity': quantity, 'price': price, 'cum_quantity_filled': 0, 'received_ms': int(round(time.time() * 1000)) }) return self.internal_to_external_id[str(internal_order_id)] = str( response['id']) self.external_to_internal_id[str( response['id'])] = str(internal_order_id) internal_response = { 'action': 'CREATED', 'exchange': self.name, 'base': base, 'quote': quote, 'exchange_order_id': str(response['id']), 'internal_order_id': str(internal_order_id), 'side': side, 'quantity': Decimal(str(quantity)), 'price': Decimal(str(price)), 'cum_quantity_filled': Decimal('0'), 'order_status': 'OPEN', 'server_ms': response['created_at'] * 1000, 'received_ms': int(round(time.time() * 1000)) } self.open_orders_by_exchange_id[str( response['id'])] = internal_response self.notify_callbacks('trade_lifecycle', data=internal_response)
def cancel_order(self, base, quote, internal_order_id, request_id, requester_id=None, exchange_order_id=None): try: if exchange_order_id is None: exchange_order_id = self.get_exchange_id(internal_order_id) except LookupError: self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'base': base, 'quote': quote, 'reason': 'order_not_found', 'exchange': self.name, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) }) return try: response = self.client.cancel_order(exchange_order_id) if ('message' in response and len(response['message']) > 0) or \ ('errors' in response and len(response['errors']) > 0): self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'reason': 'order_not_found', 'base': base, 'quote': quote, 'exchange': self.name, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) }) self.internal_to_external_id.pop(str(internal_order_id), None) self.external_to_internal_id.pop(str(exchange_order_id), None) self.open_orders_by_exchange_id.pop(str(exchange_order_id), None) return except QuoineAPIException as e: logger().error('Failed to cancel order with error: {}'.format(e)) self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'reason': 'Unknown exception type', 'base': base, 'quote': quote, 'exchange': self.name, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) }) # If fails due to "already closed" or "not found", then popping is fine # TODO - If it fails due to a rate limit, we probably don't want this here? self.internal_to_external_id.pop(str(internal_order_id), None) self.external_to_internal_id.pop(str(exchange_order_id), None) self.open_orders_by_exchange_id.pop(str(exchange_order_id), None) return self.internal_to_external_id.pop(str(internal_order_id), None) self.external_to_internal_id.pop(str(exchange_order_id), None) self.open_orders_by_exchange_id.pop(str(exchange_order_id), None) time.sleep(2) self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCELED', 'exchange': self.name, 'base': base, 'quote': quote, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'CANCELED', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) })
def cancel_order(self, base, quote, internal_order_id, request_id, requester_id=None, exchange_order_id=None): try: if exchange_order_id is None: exchange_order_id = self.get_exchange_id(internal_order_id) except LookupError: self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'reason': 'order_not_found', 'base': base, 'quote': quote, 'exchange': self.name, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) }) return resp, error = self.client.cancel_trade('Trade', exchange_order_id, None) if error is not None: logger().error( 'Failed to cancel order with error: {}'.format(error)) reason = str(error) if error == 'No matching trades found': reason = 'order_not_found' self.internal_to_external_id.pop(str(internal_order_id), None) self.external_to_internal_id.pop(str(exchange_order_id), None) self.open_orders_by_exchange_id.pop(str(exchange_order_id), None) self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCEL_FAILED', 'reason': reason, 'base': base, 'quote': quote, 'exchange': self.name, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'UNKNOWN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) }) return self.internal_to_external_id.pop(str(internal_order_id), None) self.external_to_internal_id.pop(str(exchange_order_id), None) self.open_orders_by_exchange_id.pop(str(exchange_order_id), None) time.sleep(2) self.notify_callbacks('trade_lifecycle', data={ 'action': 'CANCELED', 'exchange': self.name, 'base': base, 'quote': quote, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'order_status': 'CANCELED', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) })
def create_order(self, base, quote, price, quantity, side, order_type, internal_order_id, request_id=None, requester_id=None, **kwargs): market = base + '/' + quote response, error = self.client.submit_trade(market, side, str(price), str(quantity)) if error is not None: self.notify_callbacks('trade_lifecycle', data={ 'action': 'CREATE_FAILED', 'reason': 'Unknown exception type', 'exchange': self.name, 'base': base, 'quote': quote, 'internal_order_id': str(internal_order_id), 'side': side, 'quantity': quantity, 'price': price, 'cum_quantity_filled': 0, 'received_ms': int(round(time.time() * 1000)) }) logger().error( 'Failed to create cryptopia order with error: {}'.format( str(error))) return exchange_order_id = response['OrderId'] self.internal_to_external_id[str(internal_order_id)] = str( exchange_order_id) self.external_to_internal_id[str(exchange_order_id)] = str( internal_order_id) internal_response = { 'action': 'CREATED', 'exchange': self.name, 'base': base, 'quote': quote, 'exchange_order_id': str(exchange_order_id), 'internal_order_id': str(internal_order_id), 'side': side, 'quantity': Decimal(str(quantity)), 'price': Decimal(str(price)), 'cum_quantity_filled': Decimal('0'), 'order_status': 'OPEN', 'server_ms': int(round(time.time() * 1000)), 'received_ms': int(round(time.time() * 1000)) } self.open_orders_by_exchange_id[str( exchange_order_id)] = internal_response self.notify_callbacks('trade_lifecycle', data=internal_response)
def __process_user_data(self, event): try: if event['e'] == 'executionReport': symbol = event['s'] base = symbol[0:3] quote = symbol[3:] # TODO - Do this properly, so SALT/USDT would work message = { 'action': 'UNKNOWN', 'exchange': self.name, 'symbol': event['s'], 'base': base, 'quote': quote, 'exchange_order_id': event['i'], 'internal_order_id': event['c'], 'side': str.lower(event['S']), 'quantity': event['q'], 'price': event['p'], 'cum_quantity_filled': event['z'], 'order_status': event['X'], 'server_ms': event['T'], 'received_ms': int(round(time.time() * 1000)) } if event['x'] == 'TRADE': message['action'] = 'EXECUTION' message['last_executed_quantity'] = event['l'] message['last_executed_price'] = event['L'] message['trade_id'] = event['t'] # TODO Check this logic is correct (both symbol split and if fee is an amount or a percent) commission_amount = event['n'] commission_asset = event['N'] if commission_asset == base: fee_base = Decimal(commission_amount) fee_quote = Decimal('0') message['fee_base'] = fee_base message['fee_quote'] = fee_quote if commission_asset == quote: fee_base = Decimal('0') fee_quote = Decimal(commission_amount) message['fee_base'] = fee_base message['fee_quote'] = fee_quote if message['cum_quantity_filled'] == message['quantity']: self.open_orders.pop(message['exchange_order_id'], None) self.pending_cancel.pop(message['internal_order_id'], None) self.internal_to_external_id.pop(message['internal_order_id'], None) elif event['x'] == 'NEW': self.open_orders[message['exchange_order_id']] = message self.internal_to_external_id[message['internal_order_id']] = message['exchange_order_id'] message['action'] = 'CREATED' elif event['x'] == 'CANCELED': self.open_orders.pop(message['exchange_order_id'], None) self.pending_cancel.pop(message['internal_order_id'], None) self.internal_to_external_id.pop(message['internal_order_id'], None) message['action'] = 'CANCELED' elif event['x'] == 'REJECTED': message['action'] = 'REJECTED' # TODO map to internal error code message['rejected_reason'] = event['r'] elif event['x'] == 'EXPIRED': message['action'] = 'EXPIRED' self.callback('trade_lifecycle', trade_lifecycle_type=message['action'], data=message) except Exception as e: traceback.print_exc(file=sys.stdout) logger().error('__process_user_data failed with error: ' + str(e))