예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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',
            )
예제 #4
0
 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',
         )
예제 #5
0
    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',
            )
예제 #6
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',
            )
예제 #7
0
    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']}
예제 #8
0
    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
예제 #9
0
    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
예제 #10
0
    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
예제 #11
0
    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
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
    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
예제 #15
0
    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)
예제 #16
0
    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)
예제 #17
0
    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
예제 #18
0
    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)
예제 #19
0
    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
예제 #20
0
    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
예제 #21
0
    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
예제 #22
0
    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
예제 #23
0
    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
예제 #24
0
    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
예제 #25
0
    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