Exemple #1
0
def test_bitfinex_exchange_assets_are_known(mock_bitfinex):
    """This tests only exchange (trades) assets (not margin, nor futures ones).
    """
    currencies_response = mock_bitfinex._query_currencies()
    if currencies_response.success is False:
        response = currencies_response.response
        test_warnings.warn(
            UserWarning(
                f'Failed to request {mock_bitfinex.name} currencies list. '
                f'Response status code: {response.status_code}. '
                f'Response text: {response.text}. Xfailing this test', ))
        pytest.xfail('Failed to request {mock_bitfinex.name} currencies list')

    exchange_pairs_response = mock_bitfinex._query_exchange_pairs()
    if exchange_pairs_response.success is False:
        response = exchange_pairs_response.response
        test_warnings.warn(
            UserWarning(
                f'Failed to request {mock_bitfinex.name} exchange pairs list. '
                f'Response status code: {response.status_code}. '
                f'Response text: {response.text}. Xfailing this test', ))
        pytest.xfail(
            'Failed to request {mock_bitfinex.name} exchange pairs list')

    currency_map_response = mock_bitfinex._query_currency_map()
    if currency_map_response.success is False:
        response = currency_map_response.response
        test_warnings.warn(
            UserWarning(
                f'Failed to request {mock_bitfinex.name} currency map. '
                f'Response status code: {response.status_code}. '
                f'Response text: {response.text}. Xfailing this test', ))
        pytest.xfail('Failed to request {mock_bitfinex.name} currency map')

    test_assets = set(BITFINEX_EXCHANGE_TEST_ASSETS)
    unsupported_assets = set(UNSUPPORTED_BITFINEX_ASSETS)
    currency_map = currency_map_response.currency_map
    symbols = set()
    for symbol in currencies_response.currencies:
        if symbol in test_assets:
            continue
        for pair in exchange_pairs_response.pairs:
            if pair.startswith(symbol) or pair.endswith(symbol):
                symbols.add(symbol)
                break

    for symbol in symbols:
        try:
            asset_from_bitfinex(
                bitfinex_name=symbol,
                currency_map=currency_map,
            )
        except UnsupportedAsset:
            assert symbol in unsupported_assets
        except UnknownAsset as e:
            test_warnings.warn(
                UserWarning(
                    f'Found unknown asset {e.asset_name} in {mock_bitfinex.name}. '
                    f'Support for it has to be added', ))
Exemple #2
0
    def _deserialize_trade(self, raw_result: List[Any]) -> Trade:
        """Process a trade result from Bitfinex and deserialize it.

        The base and quote assets are instantiated using the `fee_currency_symbol`
        (from raw_result[10]) over the pair (from raw_result[1]).

        Known pairs format: 'tETHUST', 'tETH:UST'.

        Can raise DeserializationError.

        Schema reference in:
        https://docs.bitfinex.com/reference#rest-auth-trades
        """
        amount = deserialize_asset_amount(raw_result[4])
        trade_type = TradeType.BUY if amount >= ZERO else TradeType.SELL
        bfx_pair = self._process_bfx_pair(raw_result[1])
        if bfx_pair not in self.pair_bfx_symbols_map:
            raise DeserializationError(
                f'Could not deserialize bitfinex trade pair {raw_result[1]}. '
                f'Raw trade: {raw_result}', )

        bfx_base_asset_symbol, bfx_quote_asset_symbol = self.pair_bfx_symbols_map[
            bfx_pair]
        try:
            base_asset = asset_from_bitfinex(
                bitfinex_name=bfx_base_asset_symbol,
                currency_map=self.currency_map,
            )
            quote_asset = asset_from_bitfinex(
                bitfinex_name=bfx_quote_asset_symbol,
                currency_map=self.currency_map,
            )
            fee_asset = asset_from_bitfinex(
                bitfinex_name=raw_result[10],
                currency_map=self.currency_map,
            )
        except (UnknownAsset, UnsupportedAsset) as e:
            asset_tag = 'Unknown' if isinstance(
                e, UnknownAsset) else 'Unsupported'
            raise DeserializationError(
                f'{asset_tag} {e.asset_name} found while processing trade pair due to: {str(e)}',
            ) from e

        trade = Trade(
            timestamp=Timestamp(int(raw_result[2] / 1000)),
            location=Location.BITFINEX,
            base_asset=base_asset,
            quote_asset=quote_asset,
            trade_type=trade_type,
            amount=AssetAmount(abs(amount)),
            rate=deserialize_price(raw_result[5]),
            fee=Fee(abs(deserialize_fee(raw_result[9]))),
            fee_currency=fee_asset,
            link=str(raw_result[0]),
            notes='',
        )
        return trade
Exemple #3
0
    def _deserialize_asset_movement(self, raw_result: List[Any]) -> AssetMovement:
        """Process an asset movement (i.e. deposit or withdrawal) from Bitfinex
        and deserialize it.

        Bitfinex support confirmed the following in regards to
        DESTINATION_ADDRESS and TRANSACTION_ID:
          - Fiat movement: both attributes won't show any value.
          - Cryptocurrency movement: address and tx id on the blockchain.

        Timestamp is from MTS_STARTED (when the movement was created), and not
        from MTS_UPDATED (when it was completed/cancelled).

        Can raise DeserializationError.

        Schema reference in:
        https://docs.bitfinex.com/reference#rest-auth-movements
        """
        if raw_result[9] != 'COMPLETED':
            raise DeserializationError(
                f'Unexpected bitfinex movement with status: {raw_result[5]}. '
                f'Only completed movements are processed. Raw movement: {raw_result}',
            )
        try:
            fee_asset = asset_from_bitfinex(
                bitfinex_name=raw_result[1],
                currency_map=self.currency_map,
            )
        except (UnknownAsset, UnsupportedAsset) as e:
            asset_tag = 'Unknown' if isinstance(e, UnknownAsset) else 'Unsupported'
            raise DeserializationError(
                f'{asset_tag} {e.asset_name} found while processing movement asset '
                f'due to: {str(e)}',
            ) from e

        amount = deserialize_asset_amount(raw_result[12])
        category = (
            AssetMovementCategory.DEPOSIT
            if amount > ZERO
            else AssetMovementCategory.WITHDRAWAL
        )
        address = None
        transaction_id = None
        if fee_asset.is_fiat() is False:
            address = str(raw_result[16])
            transaction_id = str(raw_result[20])

        asset_movement = AssetMovement(
            timestamp=Timestamp(int(raw_result[5] / 1000)),
            location=Location.BITFINEX,
            category=category,
            address=address,
            transaction_id=transaction_id,
            asset=fee_asset,
            amount=abs(amount),
            fee_asset=fee_asset,
            fee=Fee(abs(deserialize_fee(raw_result[13]))),
            link=str(raw_result[0]),
        )
        return asset_movement
Exemple #4
0
    def query_balances(self) -> ExchangeQueryBalances:
        """Return the account exchange balances on Bitfinex

        The wallets endpoint returns a list where each item is a currency wallet.
        Each currency wallet has type (i.e. exchange, margin, funding), currency,
        balance, etc. Currencies (tickers) are in Bitfinex format and must be
        standardized.

        Endpoint documentation:
        https://docs.bitfinex.com/reference#rest-auth-wallets
        """
        self.first_connection()

        response = self._api_query('wallets')
        if response.status_code != HTTPStatus.OK:
            result, msg = self._process_unsuccessful_response(
                response=response,
                case='balances',
            )
            return result, msg
        try:
            response_list = jsonloads_list(response.text)
        except JSONDecodeError as e:
            msg = f'{self.name} returned invalid JSON response: {response.text}.'
            log.error(msg)
            raise RemoteError(msg) from e

        # Wallet items indices
        currency_index = 1
        balance_index = 2
        assets_balance: DefaultDict[Asset, Balance] = defaultdict(Balance)
        for wallet in response_list:
            if len(wallet) < API_WALLET_MIN_RESULT_LENGTH:
                log.error(
                    f'Error processing a {self.name} balance result. '
                    f'Found less items than expected',
                    wallet=wallet,
                )
                self.msg_aggregator.add_error(
                    f'Failed to deserialize a {self.name} balance result. '
                    f'Check logs for details. Ignoring it.',
                )
                continue

            if wallet[balance_index] <= 0:
                continue  # bitfinex can show small negative balances for some coins. Ignore

            try:
                asset = asset_from_bitfinex(
                    bitfinex_name=wallet[currency_index],
                    currency_map=self.currency_map,
                )
            except (UnknownAsset, UnsupportedAsset) as e:
                asset_tag = 'unknown' if isinstance(e, UnknownAsset) else 'unsupported'
                self.msg_aggregator.add_warning(
                    f'Found {asset_tag} {self.name} asset {e.asset_name} due to: {str(e)}. '
                    f'Ignoring its balance query.',
                )
                continue

            try:
                usd_price = Inquirer().find_usd_price(asset=asset)
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    f'Error processing {self.name} {asset.name} balance result due to inability '
                    f'to query USD price: {str(e)}. Skipping balance result.',
                )
                continue

            try:
                amount = deserialize_asset_amount(wallet[balance_index])
            except DeserializationError as e:
                self.msg_aggregator.add_error(
                    f'Error processing {self.name} {asset.name} balance result due to inability '
                    f'to deserialize asset amount due to {str(e)}. Skipping balance result.',
                )
                continue

            assets_balance[asset] += Balance(
                amount=amount,
                usd_value=amount * usd_price,
            )

        return dict(assets_balance), ''