def get_open_orders_resp(self, req): response = self.resp(req) open_orders = [] try: raw_open_orders = response['open'] for order_id, raw_order in raw_open_orders.iteritems(): if raw_order['status'] == 'open': mode = self._order_mode_to_const(raw_order['descr']['type']) volume = Money(raw_order['vol'], 'BTC') volume_executed = Money(raw_order['vol_exec'], 'BTC') price = Money(raw_order['descr']['price'], self.currency) order = { 'mode': mode, 'id': order_id, 'price': price, 'volume_remaining': volume - volume_executed, } open_orders.append(order) except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'Open orders format incorrect', ) return open_orders
def get_balance_resp(self, req): raw_balances = self.resp(req) volume_currency_available = None price_currency_available = None for raw_balance in raw_balances: if raw_balance['currency'] == self.volume_currency: volume_currency_available = Money( raw_balance['available'], self.volume_currency, ) elif raw_balance['currency'] == self.currency: price_currency_available = Money( raw_balance['available'], self.currency, ) if volume_currency_available == None or price_currency_available == None: raise exceptions.ExchangeAPIErrorException( self, 'missing expected balances', ) balance = Balance() balance[self.volume_currency] = volume_currency_available balance[self.currency] = price_currency_available return balance
def cancel_order_resp(self, req): response = self.resp(req) if response['status'] == 'CANCELED': return {'success': True} else: raise exceptions.ExchangeAPIErrorException( self, 'canceled order does not have canceled status', )
def create_trade_resp(self, req): response = self.resp(req) try: order_id = str(response['order_id']) return {'success': True, 'order_id': order_id} except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'response does not contain an order id', )
def cancel_order_resp(self, req): response = self.resp(req) if response.get('count', 0) > 0: return {'success': True} else: raise exceptions.ExchangeAPIErrorException( self, 'cancelled count should be > 0', )
def place_order_resp(self, req): response = self.resp(req) try: return {'success': True, 'order_id': unicode(response['id'])} except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'response does not contain an order id', )
def withdraw_crypto_resp(self, req): response = self.resp(req) # not sure why this comes back in an array # [{u'status': u'error', u'message': u'Insufficient ...', u'withdrawal_id': 0}] response = response[0] if response['status'] != 'success': raise exceptions.ExchangeAPIErrorException(self, response['message']) return {'success': True, 'exchange_withdrawal_id': response['withdrawal_id']}
def _get_orderbook_from_api_resp(self, req): order_book = self.resp(req) timestamp = int(order_book['timestamp']) now = Delorean() if epoch(timestamp) < now.last_minute(10): raise exceptions.ExchangeAPIErrorException( self, 'Orderbook is more than 10 minutes old', ) return order_book
def resp(self, req): response = super(BitfinexBTCUSDExchange, self).resp(req) if 'message' in response: errors_string = str(response['message']) if 'not enough balance' in errors_string: raise exceptions.InsufficientFundsError() elif 'Order could not be cancelled' in errors_string: raise exceptions.CancelOrderNotFoundError() elif 'Nonce is too small' in errors_string: raise exceptions.NonceError() else: raise exceptions.ExchangeAPIErrorException(self, errors_string) return response
def resp(self, req): response = super(ItbitBTCUSDExchange, self).resp(req) if 'error' in response and response['error']: raise exceptions.ExchangeAPIErrorException(self, response['error']) if 'code' in response: errors_string = str(response['description']) error_code = int(response['code']) if error_code == 81001: raise exceptions.InsufficientFundsError() elif error_code == 10002: raise exceptions.NonceError() elif error_code == 81002: raise exceptions.CancelOrderNotFoundError() else: raise exceptions.ExchangeAPIErrorException( self, 'Code %s: %s' % ( error_code, errors_string, )) return response
def resp(self, req): response = super(GeminiBTCUSDExchange, self).resp(req) if 'message' in response: errors_string = str(response['message']) if 'InsufficientFunds' or 'insufficient funds' in errors_string: raise exceptions.InsufficientFundsError() elif 'Order' in errors_string and 'not found' in errors_string: raise exceptions.CancelOrderNotFoundError() elif 'InvalidNonce' in errors_string: raise exceptions.NonceError() else: raise exceptions.ExchangeAPIErrorException(self, errors_string) return response
def resp(self, req): response = super(OKCoinBTCUSDExchange, self).resp(req) error_code = response.get('error_code', None) if error_code: if error_code == 10009: raise exceptions.CancelOrderNotFoundError() elif error_code in [10016, 10010]: raise exceptions.InsufficientFundsError() else: raise exceptions.ExchangeAPIErrorException( self, self.errors[error_code], ) return response
def get_balance_resp(self, req): response, headers = self.resp(req) balance = Balance() try: for account in response: if account['currency'] == 'BTC': balance['BTC'] = Money(account['available'], 'BTC') elif account['currency'] == self.currency: balance[self.currency] = Money(account['available'], self.currency) except KeyError: raise exceptions.ExchangeAPIErrorException(self, 'malformed response') return balance
def balance_resp(self, req): response = self.resp(req) try: balances = response['info']['funds'] btc_available = Money(balances['free']['btc'], 'BTC') usd_available = Money(balances['free']['usd'], 'USD') except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'Balance missing expected keys', ) balance = Balance() balance['BTC'] = btc_available balance['USD'] = usd_available return balance
def resp(self, req): response = super(KrakenBTCEURExchange, self).resp(req) if response.get('error'): errors_string = str(response['error']) if 'Insufficient funds' in errors_string: raise exceptions.InsufficientFundsError() elif 'Unknown order' in errors_string: raise exceptions.CancelOrderNotFoundError() elif 'Invalid nonce' in errors_string: raise exceptions.NonceError() else: raise exceptions.ExchangeAPIErrorException(self, errors_string) try: return response['result'] except KeyError: raise exceptions.ExchangeAPIFailureException(self, response)
def withdraw_crypto_req(self, address, volume): if not isinstance(address, basestring): raise TypeError('Withdrawal address must be a string') if self.volume_currency != 'BTC': raise TypeError('Kraken withdrawals are only supported for BTC right now.') if not isinstance(volume, Money) or volume.currency != self.volume_currency: raise TypeError('Withdrawal volume must be in %s' % self.volume_currency) # The Kraken API only takes names which are mapped to address. The mapping can # be adjusted here: https://www.kraken.com/u/funding/withdraw?asset=XXBT # Since our system passes around addresses, we search through the ENV vars to # find the corresponding exchange name, which we then pass to Kraken. deposit_addresses = { name: addr for name, addr in os.environ.iteritems() if '_DEPOSIT_ADDRESS' in name } address_to_name_map = { addr: name.replace('_DEPOSIT_ADDRESS', '') for name, addr in deposit_addresses.iteritems() } try: destination_exchange_name = address_to_name_map[address] except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'Could not find matching exchange for %s' % address, ) volume += self.withdrawal_fee payload = { 'asset': 'XXBT', 'key': destination_exchange_name, 'amount': volume.amount, } return self.req('post', '/private/Withdraw', data=payload)
def get_balance_resp(self, req): response = self.resp(req) balance = Balance() try: vol_currency_key = '%s_available' % self.volume_currency.lower() price_currency_key = '%s_available' % self.currency.lower() balance[self.volume_currency] = Money( response[vol_currency_key], self.volume_currency, ) balance[self.currency] = Money(response[price_currency_key], self.currency) except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'Balance missing expected keys', ) return balance
def resp(self, req): response = super(QuadrigaBTCCADExchange, self).resp(req) try: if 'error' in response: errors_string = response['error']['message'] if 'exceeds available' in errors_string: raise exceptions.InsufficientFundsError() elif 'Cannot perform request - not found' in errors_string: raise exceptions.CancelOrderNotFoundError() elif 'Nonce' in errors_string: raise exceptions.NonceError() else: logger.info(response['error']) raise exceptions.ExchangeAPIErrorException( self, errors_string) return response except KeyError: raise exceptions.ExchangeAPIFailureException(self, response)
def balance_resp(self, req): raw_balances = self.resp(req) btc_available = None usd_available = None for raw_balance in raw_balances: if raw_balance['type'] == 'exchange': if raw_balance['currency'] == 'btc': btc_available = Money(raw_balance['available'], 'BTC') elif raw_balance['currency'] == 'usd': usd_available = Money(raw_balance['available'], 'USD') if btc_available == None or usd_available == None: raise exceptions.ExchangeAPIErrorException( self, 'missing expected balances', ) balance = Balance() balance['BTC'] = btc_available balance['USD'] = usd_available return balance
def resp(self, req): response = super(BitstampBTCUSDExchange, self).resp(req) try: errors = response.get('error', None) except AttributeError: # Some endpoints return a list. errors = None if errors: errors_string = str(errors) if 'You have only' in errors_string: raise exceptions.InsufficientFundsError() elif 'Order not found' in errors_string: raise exceptions.CancelOrderNotFoundError() elif 'Minimum order size' in errors_string: raise exceptions.MinimumOrderSizeError() elif 'Invalid nonce' in errors_string: raise exceptions.NonceError() else: raise exceptions.ExchangeAPIErrorException(self, errors_string) return response
def resp(self, req): response = req.result() try: data = response.json(parse_float=Decimal) except ValueError: raise exceptions.ExchangeAPIFailureException(self, response) headers = response.headers if response.status_code < 200 or response.status_code >= 300: try: error_string = data['message'] except KeyError: error_string = str(data) error_string = error_string.lower() if 'notfound' in error_string: raise exceptions.NoEffectOrderCancelledError() elif ('order already done' in error_string or 'order not found' in error_string): raise exceptions.CancelOrderNotFoundError() elif 'order size is too small' in error_string: raise exceptions.MinimumOrderSizeError() elif 'insufficient funds' in error_string: raise exceptions.InsufficientFundsError() # These errors occur randomly (usually when Coinbase under heavy load). # We want to return an ExchangeAPIFailureException so that requests get # retried. elif ('request timestamp expired' in error_string or 'internal server error' in error_string): raise exceptions.ExchangeAPIFailureException(self, response) else: raise exceptions.ExchangeAPIErrorException(self, error_string) else: return data, headers
def get_balance_resp(self, req): response = self.resp(req) raw_balances = response['balances'] btc_available = None usd_available = None for raw_balance in raw_balances: if raw_balance['currency'] == 'XBT': btc_available = Money(raw_balance['availableBalance'], 'BTC') elif raw_balance['currency'] == 'USD': usd_available = Money(raw_balance['availableBalance'], 'USD') if btc_available is None or usd_available is None: raise exceptions.ExchangeAPIErrorException( self, 'missing expected balances', ) balance = Balance() balance['BTC'] = btc_available balance['USD'] = usd_available return balance
def get_trades_info_from_ledger(self, trade_ids, order_open_timestamp, order_close_timestamp): """ Check the ledger entries to get accurate numbers for how much our balance was changed. The ledger is Kraken's only real source of truth, the trades/order endpoints lie to us. """ # We add a 0.1s buffer to make sure we get entries right on the boundary # timestamps. ledger_start = order_open_timestamp - Decimal('0.1') # We add a 1s buffer because it takes Kraken a bit of time to write to the # ledger. ledger_end = order_close_timestamp + Decimal('1') entries = self.get_ledger_entries( start=ledger_start, end=ledger_end, ) trades_info = {} for trade_id in trade_ids: trades_info[trade_id] = { 'btc': Money.loads('BTC 0'), 'btc_fee': Money.loads('BTC 0'), 'fiat': Money(0, self.currency), 'fiat_fee': Money(0, self.currency), } for ledger_id, entry in entries.iteritems(): trade_id = entry['refid'] if trade_id not in trade_ids: continue amount = Decimal(entry['amount']) if entry['type'] == 'credit': # Credit ledger entries show up when we dip into our line of credit. # They have opposite signs, and need to be included along side the # trade ledger entries to get accurate trade amounts. amount = -amount elif entry['type'] == 'trade': pass else: raise exceptions.ExchangeAPIErrorException( self, 'Unexpected ledger entry type "%s"' % entry['type'], ) currency = self.convert_from_kraken_currency(entry['asset']) if currency == 'BTC': trades_info[trade_id]['btc'] += Money(amount, 'BTC') trades_info[trade_id]['btc_fee'] += Money(entry['fee'], 'BTC') else: trades_info[trade_id]['fiat'] += Money(amount, currency) trades_info[trade_id]['fiat_fee'] += Money(entry['fee'], currency) # There are multiple ledger entries per trade, but they should all be going #through at the same time, so we can just take the timestamp of the last # one. trades_info[trade_id]['time'] = entry['time'] return trades_info
def get_multi_order_details_resp(self, req, order_ids): multi_trades = self.resp(req) data = {} for order_id in order_ids: total_fiat = Money('0', self.currency) total_btc = Money('0', 'BTC') our_trades = [] if order_id in multi_trades: order = multi_trades[order_id] trade_ids = order.get('trades', []) if trade_ids: opentm = order['opentm'] # Partially-executed orders haven't "closed" yet so they don't # have a closetm. We only need the already executed trades, so we # end the interval at NOW(). if 'closetm' in order: closetm = order['closetm'] else: closetm = Decimal(Delorean().epoch) trades = self.get_trades_info_from_ledger( trade_ids, opentm, closetm, ) for t_id, t in trades.iteritems(): fiat = abs(t['fiat']) btc = abs(t['btc']) if t['btc_fee'] and t['fiat_fee']: raise exceptions.ExchangeAPIErrorException( self, '#%s charged fees in both fiat (%s) and BTC (%s)' % ( order_id, t['fiat_fee'], t['btc_fee'], )) elif t['btc_fee']: fee = t['btc_fee'] else: fee = t['fiat_fee'] total_fiat += fiat total_btc += btc our_trades.append({ 'time': int(t['time']), 'trade_id': unicode(t_id), 'fee': fee, 'btc': btc, 'fiat': fiat, }) data[order_id] = { 'time_created': int(order['opentm']), 'type': self._order_mode_to_const(order['descr']['type']), 'btc_total': total_btc, 'fiat_total': total_fiat, 'trades': our_trades, } return data
def trades(self): raw_transactions = self.transactions() trades = [] for raw_transaction in raw_transactions: # It is a trade if an order_id is present in the transaction. if int(raw_transaction['type']) == 2: btc = Money(raw_transaction['btc'], 'BTC') fiat = Money(raw_transaction[self.currency.lower()], self.currency) # Bids look like this: {'cad':'-5075.50','btc':'15.77622250'} # and asks like this: {'cad':'256.88','btc':'-0.80000000'} if btc > 0: # Bid # Bids have BTC fees fee = Money(raw_transaction['fee'], 'BTC') # Quadriga returns amounts which have fees removed # we need to add them back in to keep our accounting accurate btc += fee if fiat > 0: raise exceptions.ExchangeAPIErrorException( self, 'Trade #%s does not have opposite btc and fiat signs' % (raw_transaction['id'], )) fiat = abs(fiat) elif btc < 0: # Ask. # Asks have CAD fees. fee = Money(raw_transaction['fee'], self.currency) fiat += fee if fiat < 0: raise exceptions.ExchangeAPIErrorException( self, 'Trade #%s does not have opposite btc and fiat signs' % (raw_transaction['id'], )) btc = abs(btc) else: raise exceptions.ExchangeAPIErrorException( self, 'Zero volume Trade #%s, so API doesn\'t tell us the side.' % (raw_transaction['id'], )) trades.append({ 'time': self._datetime_to_timestamp(raw_transaction['datetime']), 'trade_id': unicode(raw_transaction['id']), 'order_id': unicode(raw_transaction['order_id']), 'btc': btc, 'fiat': fiat, 'fee': fee, }) return trades