Beispiel #1
0
    def choose_pair(
            self,
            timestamp: Timestamp,
            price_query: Callable[[Asset, Asset, Timestamp], FVal],
    ) -> TradePair:
        """Choose a random pair to trade from the available pairs at the selected timestamp"""
        choices = set(self._symbols_to_pair.keys())
        found = False
        while len(choices) != 0:
            pair = random.choice(tuple(choices))
            choices.remove(pair)
            binance_pair = self._symbols_to_pair[pair]
            bbase = binance_pair.binance_base_asset
            bquote = binance_pair.binance_quote_asset
            try:
                base = asset_from_binance(bbase)
                quote = asset_from_binance(bquote)
            except UnsupportedAsset:
                continue

            if bbase in self.balances_dict or bquote in self.balances_dict:
                if bbase in DISALLOWED_ASSETS or bquote in DISALLOWED_ASSETS:
                    continue

                # Before choosing make sure that at the selected timestamp both of
                # the pair assets exist (had a price)
                if not assets_exist_at_time(base, quote, timestamp, price_query):
                    continue

                found = True
                break

        if not found:
            raise ValueError('Could not find a pair to trade with the current funds')
        return trade_pair_from_assets(base, quote)
Beispiel #2
0
def test_binance_assets_are_known(
        database,
        inquirer,  # pylint: disable=unused-argument
):
    # use a real binance instance so that we always get the latest data
    binance = Binance(
        api_key=make_api_key(),
        secret=make_api_secret(),
        database=database,
        msg_aggregator=MessagesAggregator(),
    )

    mapping = binance.symbols_to_pair
    binance_assets = set()
    for _, pair in mapping.items():
        binance_assets.add(pair.binance_base_asset)
        binance_assets.add(pair.binance_quote_asset)

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        try:
            _ = asset_from_binance(binance_asset)
        except UnsupportedAsset:
            assert binance_asset in UNSUPPORTED_BINANCE_ASSETS
        except UnknownAsset as e:
            test_warnings.warn(
                UserWarning(
                    f'Found unknown asset {e.asset_name} in Binance. Support for it has to be added',
                ))

    # also check that lending assets are properly processed
    assert asset_from_binance('LDBNB') == Asset('BNB')
    assert asset_from_binance('LDUSDT') == Asset('USDT')
Beispiel #3
0
def trade_from_binance(
    binance_trade: Dict,
    binance_symbols_to_pair: Dict[str, BinancePair],
) -> Trade:
    """Turn a binance trade returned from trade history to our common trade
    history format

    From the official binance api docs (01/09/18):
    https://github.com/binance-exchange/binance-official-api-docs/blob/62ff32d27bb32d9cc74d63d547c286bb3c9707ef/rest-api.md#terminology

    base asset refers to the asset that is the quantity of a symbol.
    quote asset refers to the asset that is the price of a symbol.

    Can throw UnsupportedAsset due to asset_from_binance
    """
    amount = FVal(binance_trade['qty'])
    rate = FVal(binance_trade['price'])
    binance_pair = binance_symbols_to_pair[binance_trade['symbol']]
    timestamp = binance_trade['time']

    base_asset = asset_from_binance(binance_pair.binance_base_asset)
    quote_asset = asset_from_binance(binance_pair.binance_quote_asset)

    if binance_trade['isBuyer']:
        order_type = TradeType.BUY
        # e.g. in RDNETH we buy RDN by paying ETH
    else:
        order_type = TradeType.SELL

    fee_currency = asset_from_binance(binance_trade['commissionAsset'])
    fee = FVal(binance_trade['commission'])

    log.debug(
        'Processing binance Trade',
        sensitive_log=True,
        amount=amount,
        rate=rate,
        timestamp=timestamp,
        pair=binance_trade['symbol'],
        base_asset=base_asset,
        quote=quote_asset,
        order_type=order_type,
        commision_asset=binance_trade['commissionAsset'],
        fee=fee,
    )

    return Trade(
        timestamp=timestamp,
        location='binance',
        pair=trade_pair_from_assets(base_asset, quote_asset),
        trade_type=order_type,
        amount=amount,
        rate=rate,
        fee=fee,
        fee_currency=fee_currency,
    )
 def deserialize_from_db(cls,
                         entry: BINANCE_PAIR_DB_TUPLE) -> 'BinancePair':
     """Create a BinancePair from data in the database. May raise:
     - DeserializationError
     - UnsupportedAsset
     - UnknownAsset
     """
     return BinancePair(
         symbol=entry[0],
         base_asset=asset_from_binance(entry[1]),
         quote_asset=asset_from_binance(entry[2]),
         location=Location.deserialize_from_db(entry[3]),
     )
Beispiel #5
0
def test_binance_assets_are_known(
        database,
        inquirer,  # pylint: disable=unused-argument
):
    # use a real binance instance so that we always get the latest data
    binance = Binance(
        name='binance1',
        api_key=make_api_key(),
        secret=make_api_secret(),
        database=database,
        msg_aggregator=MessagesAggregator(),
        uri=BINANCEUS_BASE_URL,
    )

    mapping = binance.symbols_to_pair
    binance_assets = set()
    for _, pair in mapping.items():
        binance_assets.add(pair.binance_base_asset)
        binance_assets.add(pair.binance_quote_asset)

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        try:
            _ = asset_from_binance(binance_asset)
        except UnsupportedAsset:
            assert binance_asset in UNSUPPORTED_BINANCE_ASSETS
        except UnknownAsset as e:
            test_warnings.warn(
                UserWarning(
                    f'Found unknown asset {e.asset_name} in binanceus. '
                    f'Support for it has to be added', ))
Beispiel #6
0
    def _query_margined_futures_balances(
        self,
        api_type: Literal['fapi', 'dapi'],
        balances: Dict,
    ) -> Dict:
        try:
            response = self.api_query_list(api_type, 'balance')
        except BinancePermissionError as e:
            log.warning(
                f'Insufficient permission to query {self.name} {api_type} balances.'
                f'Skipping query. Response details: {str(e)}', )
            return balances

        try:
            for entry in response:
                amount = FVal(entry['balance'])
                if amount == ZERO:
                    continue

                try:
                    asset = asset_from_binance(entry['asset'])
                except UnsupportedAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unsupported {self.name} asset {e.asset_name}. '
                        f'Ignoring its margined futures balance query.', )
                    continue
                except UnknownAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unknown {self.name} asset {e.asset_name}. '
                        f'Ignoring its margined futures balance query.', )
                    continue
                except DeserializationError:
                    self.msg_aggregator.add_error(
                        f'Found {self.name} asset with non-string type '
                        f'{type(entry["asset"])}. Ignoring its margined futures balance query.',
                    )
                    continue

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

                balance = Balance(amount=amount, usd_value=amount * usd_price)
                if asset not in balances:
                    balances[asset] = balance.to_dict()
                else:
                    balances[asset]['amount'] += balance.amount
                    balances[asset]['usd_value'] += balance.usd_value

        except KeyError as e:
            self.msg_aggregator.add_error(
                f'At {self.name} margined futures balance query did not find '
                f'expected key {str(e)}. Skipping margined futures query...', )

        return balances
Beispiel #7
0
    def _query_cross_collateral_futures_balances(
            self,
            balances: DefaultDict[Asset, Balance],
    ) -> DefaultDict[Asset, Balance]:
        """Queries binance collateral future balances and if any found adds them to `balances`

        May raise:
        - RemoteError
        """
        futures_response = self.api_query_dict('sapi', 'futures/loan/wallet')
        try:
            cross_collaterals = futures_response['crossCollaterals']
            for entry in cross_collaterals:
                amount = deserialize_asset_amount(entry['locked'])
                if amount == ZERO:
                    continue

                try:
                    asset = asset_from_binance(entry['collateralCoin'])
                except UnsupportedAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unsupported {self.name} asset {e.asset_name}. '
                        f'Ignoring its futures balance query.',
                    )
                    continue
                except UnknownAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unknown {self.name} asset {e.asset_name}. '
                        f'Ignoring its futures balance query.',
                    )
                    continue
                except DeserializationError:
                    self.msg_aggregator.add_error(
                        f'Found {self.name} asset with non-string type '
                        f'{type(entry["asset"])}. Ignoring its futures balance query.',
                    )
                    continue

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

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

        except KeyError as e:
            self.msg_aggregator.add_error(
                f'At {self.name} futures balance query did not find expected key '
                f'{str(e)}. Skipping futures query...',
            )

        return balances
Beispiel #8
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from binance and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if 'insertTime' in raw_data:
                category = AssetMovementCategory.DEPOSIT
                time_key = 'insertTime'
                fee = Fee(ZERO)
            else:
                category = AssetMovementCategory.WITHDRAWAL
                time_key = 'applyTime'
                fee = Fee(deserialize_asset_amount(raw_data['transactionFee']))

            timestamp = deserialize_timestamp_from_binance(raw_data[time_key])
            asset = asset_from_binance(raw_data['asset'])
            tx_id = get_key_if_has_val(raw_data, 'txId')
            internal_id = get_key_if_has_val(raw_data, 'id')
            link_str = str(internal_id) if internal_id else str(
                tx_id) if tx_id else ''
            return AssetMovement(
                location=self.location,
                category=category,
                address=deserialize_asset_movement_address(
                    raw_data, 'address', asset),
                transaction_id=tx_id,
                timestamp=timestamp,
                asset=asset,
                amount=deserialize_asset_amount_force_positive(
                    raw_data['amount']),
                fee_asset=asset,
                fee=fee,
                link=link_str,
            )

        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {str(self.location)} deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {str(self.location)} deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                f'Error processing a {str(self.location)} deposit/withdrawal. Check logs '
                f'for details. Ignoring it.', )
            log.error(
                f'Error processing a {str(self.location)} deposit/withdrawal',
                asset_movement=raw_data,
                error=msg,
            )

        return None
Beispiel #9
0
    def query_balances(self) -> Tuple[Optional[dict], str]:
        self.first_connection()

        try:
            # account data returns a dict as per binance docs
            account_data = self.api_query_dict('account')
        except RemoteError as e:
            msg = ('Binance API request failed. Could not reach binance due '
                   'to {}'.format(e))
            log.error(msg)
            return None, msg

        returned_balances = {}
        for entry in account_data['balances']:
            amount = entry['free'] + entry['locked']
            if amount == FVal(0):
                continue
            try:
                asset = asset_from_binance(entry['asset'])
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported binance asset {e.asset_name}. '
                    f' Ignoring its balance query.', )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown binance asset {e.asset_name}. '
                    f' Ignoring its balance query.', )
                continue
            except DeserializationError:
                self.msg_aggregator.add_error(
                    f'Found binance asset with non-string type {type(entry["asset"])}. '
                    f' Ignoring its balance query.', )
                continue

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

            balance = {}
            balance['amount'] = amount
            balance['usd_value'] = FVal(amount * usd_price)
            returned_balances[asset] = balance

            log.debug(
                'binance balance query result',
                sensitive_log=True,
                asset=asset,
                amount=amount,
                usd_value=balance['usd_value'],
            )

        return returned_balances, ''
Beispiel #10
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from binance and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if 'insertTime' in raw_data:
                category = AssetMovementCategory.DEPOSIT
                time_key = 'insertTime'
            else:
                category = AssetMovementCategory.WITHDRAWAL
                time_key = 'applyTime'

            timestamp = deserialize_timestamp_from_binance(raw_data[time_key])
            asset = asset_from_binance(raw_data['asset'])
            location = Location.BINANCE if self.name == str(
                Location.BINANCE) else Location.BINANCE_US  # noqa: E501
            return AssetMovement(
                location=location,
                category=category,
                address=deserialize_asset_movement_address(
                    raw_data, 'address', asset),
                transaction_id=get_key_if_has_val(raw_data, 'txId'),
                timestamp=timestamp,
                asset=asset,
                amount=deserialize_asset_amount_force_positive(
                    raw_data['amount']),
                fee_asset=asset,
                # Binance does not include withdrawal fees neither in the API nor in their UI
                fee=Fee(ZERO),
                link=str(raw_data['txId']),
            )

        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {self.name} deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {self.name} deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                f'Error processing a {self.name} deposit/withdrawal. Check logs '
                f'for details. Ignoring it.', )
            log.error(
                f'Error processing a {self.name} deposit_withdrawal',
                asset_movement=raw_data,
                error=msg,
            )

        return None
Beispiel #11
0
    def _query_lending_balances(
        self,
        balances: DefaultDict[Asset, Balance],
    ) -> DefaultDict[Asset, Balance]:
        """Queries binance lending balances and if any found adds them to `balances`

        May raise:
        - RemoteError
        """
        data = self.api_query_dict('sapi', 'lending/union/account')
        positions = data.get('positionAmountVos', None)
        if positions is None:
            raise RemoteError(
                f'Could not find key positionAmountVos in lending account data '
                f'{data} returned by {self.name}.', )

        for entry in positions:
            try:
                amount = deserialize_asset_amount(entry['amount'])
                if amount == ZERO:
                    continue

                asset = asset_from_binance(entry['asset'])
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported {self.name} asset {e.asset_name}. '
                    f'Ignoring its lending balance query.', )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown {self.name} asset {e.asset_name}. '
                    f'Ignoring its lending balance query.', )
                continue
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                self.msg_aggregator.add_error(
                    f'Error at deserializing {self.name} asset. {msg}. '
                    f'Ignoring its lending balance query.', )
                continue

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

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

        return balances
Beispiel #12
0
    def _query_cross_collateral_futures_balances(self, balances: Dict) -> Dict:
        futures_response = self.api_query_dict('sapi', 'futures/loan/wallet')
        try:
            cross_collaterals = futures_response['crossCollaterals']
            for entry in cross_collaterals:
                amount = FVal(entry['locked'])
                if amount == ZERO:
                    continue

                try:
                    asset = asset_from_binance(entry['collateralCoin'])
                except UnsupportedAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unsupported {self.name} asset {e.asset_name}. '
                        f'Ignoring its futures balance query.',
                    )
                    continue
                except UnknownAsset as e:
                    self.msg_aggregator.add_warning(
                        f'Found unknown {self.name} asset {e.asset_name}. '
                        f'Ignoring its futures balance query.',
                    )
                    continue
                except DeserializationError:
                    self.msg_aggregator.add_error(
                        f'Found {self.name} asset with non-string type '
                        f'{type(entry["asset"])}. Ignoring its futures balance query.',
                    )
                    continue

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

                balance = Balance(amount=amount, usd_value=amount * usd_price)
                if asset not in balances:
                    balances[asset] = balance.to_dict()
                else:
                    balances[asset]['amount'] += balance.amount
                    balances[asset]['usd_value'] += balance.usd_value

        except KeyError as e:
            self.msg_aggregator.add_error(
                f'At {self.name} futures balance query did not find expected key '
                f'{str(e)}. Skipping futures query...',
            )

        return balances
Beispiel #13
0
    def _query_spot_balances(self, balances: Dict) -> Dict:
        account_data = self.api_query_dict('api', 'account')
        for entry in account_data['balances']:
            if len(entry['asset']) >= 5 and entry['asset'].startswith('LD'):
                # Some lending coins also appear to start with the LD prefix. Ignore them
                continue

            amount = entry['free'] + entry['locked']
            if amount == ZERO:
                continue

            try:
                asset = asset_from_binance(entry['asset'])
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported {self.name} asset {e.asset_name}. '
                    f'Ignoring its balance query.',
                )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown {self.name} asset {e.asset_name}. '
                    f'Ignoring its balance query.',
                )
                continue
            except DeserializationError:
                self.msg_aggregator.add_error(
                    f'Found {self.name} asset with non-string type {type(entry["asset"])}. '
                    f'Ignoring its balance query.',
                )
                continue

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

            balance = {}
            balance['amount'] = amount
            balance['usd_value'] = FVal(amount * usd_price)

            if asset not in balances:
                balances[asset] = balance
            else:  # Some assets may appear twice in binance balance query for different locations
                # Lending/staking for example
                balances[asset]['amount'] += balance['amount']
                balances[asset]['usd_value'] += balance['usd_value']

        return balances
Beispiel #14
0
def create_binance_symbols_to_pair(
    exchange_data: Dict[str, Any],
    location: Location,
) -> Dict[str, BinancePair]:
    """Parses the result of 'exchangeInfo' endpoint and creates the symbols_to_pair mapping
    """
    result: Dict[str, BinancePair] = {}
    for symbol in exchange_data['symbols']:
        symbol_str = symbol['symbol']
        if isinstance(symbol_str, FVal):
            # the to_int here may raise but should never due to the if check above
            symbol_str = str(symbol_str.to_int(exact=True))
        try:
            result[symbol_str] = BinancePair(
                symbol=symbol_str,
                base_asset=asset_from_binance(symbol['baseAsset']),
                quote_asset=asset_from_binance(symbol['quoteAsset']),
                location=location,
            )
        except (UnknownAsset, UnsupportedAsset) as e:
            log.debug(f'Found binance pair with no processable asset. {str(e)}')
    return result
Beispiel #15
0
    def _query_lending_balances(self, balances: Dict) -> Dict:
        data = self.api_query_dict('lending/union/account')
        positions = data.get('positionAmountVos', None)
        if positions is None:
            raise RemoteError(
                f'Could not find key positionAmountVos in lending account data '
                f'{data} returned by binance', )

        for entry in positions:
            try:
                amount = FVal(entry['amount'])
                if amount == ZERO:
                    continue

                asset = asset_from_binance(entry['asset'])
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported binance asset {e.asset_name}. '
                    f' Ignoring its lending balance query.', )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown binance asset {e.asset_name}. '
                    f' Ignoring its lending balance query.', )
                continue
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                self.msg_aggregator.add_error(
                    f'Error at deserializing binance asset. {msg}.'
                    f' Ignoring its lending balance query.', )
                continue

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

            balance = Balance(amount=amount, usd_value=amount * usd_price)
            if asset not in balances:
                balances[asset] = balance.to_dict()
            else:
                balances[asset]['amount'] += balance.amount
                balances[asset]['usd_value'] += balance.usd_value

        return balances
Beispiel #16
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from binance and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if 'insertTime' in raw_data:
                category = 'deposit'
                time_key = 'insertTime'
            else:
                category = 'withdrawal'
                time_key = 'applyTime'

            timestamp = deserialize_timestamp_from_binance(raw_data[time_key])
            asset = asset_from_binance(raw_data['asset'])
            amount = deserialize_asset_amount(raw_data['amount'])
            return AssetMovement(
                exchange=Exchange.BINANCE,
                category=cast(Literal['deposit', 'withdrawal'], category),
                timestamp=timestamp,
                asset=asset,
                amount=amount,
                # Binance does not include withdrawal fees neither in the API nor in their UI
                fee=Fee(ZERO),
            )

        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found binance deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found binance deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                'Error processing a binance deposit/withdrawal. Check logs '
                'for details. Ignoring it.', )
            log.error(
                'Error processing a binance deposit_withdrawal',
                asset_movement=raw_data,
                error=msg,
            )

        return None
def test_binance_assets_are_known(inquirer):  # pylint: disable=unused-argument
    exchange_data = requests.get('https://api.binance.us/api/v3/exchangeInfo').json()
    binance_assets = set()
    for pair_symbol in exchange_data['symbols']:
        binance_assets.add(pair_symbol['baseAsset'])
        binance_assets.add(pair_symbol['quoteAsset'])

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        try:
            _ = asset_from_binance(binance_asset)
        except UnsupportedAsset:
            assert binance_asset in UNSUPPORTED_BINANCE_ASSETS
        except UnknownAsset as e:
            test_warnings.warn(UserWarning(
                f'Found unknown asset {e.asset_name} in binanceus. '
                f'Support for it has to be added',
            ))
Beispiel #18
0
def test_binance_assets_are_known(
        accounting_data_dir,
        inquirer,  # pylint: disable=unused-argument
):
    # use a real binance instance so that we always get the latest data
    binance = Binance(
        api_key=base64.b64encode(make_random_b64bytes(128)),
        secret=base64.b64encode(make_random_b64bytes(128)),
        data_dir=accounting_data_dir,
        msg_aggregator=MessagesAggregator(),
    )

    mapping = binance.symbols_to_pair
    binance_assets = set()
    for _, pair in mapping.items():
        binance_assets.add(pair.binance_base_asset)
        binance_assets.add(pair.binance_quote_asset)

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        _ = asset_from_binance(binance_asset)
Beispiel #19
0
def test_binance_assets_are_known(inquirer):  # pylint: disable=unused-argument
    unsupported_assets = set(UNSUPPORTED_BINANCE_ASSETS)
    common_items = unsupported_assets.intersection(set(WORLD_TO_BINANCE.values()))
    assert not common_items, f'Binance assets {common_items} should not be unsupported'

    exchange_data = requests.get('https://api3.binance.com/api/v3/exchangeInfo').json()
    binance_assets = set()
    for pair_symbol in exchange_data['symbols']:
        binance_assets.add(pair_symbol['baseAsset'])
        binance_assets.add(pair_symbol['quoteAsset'])

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        try:
            _ = asset_from_binance(binance_asset)
        except UnsupportedAsset:
            assert binance_asset in UNSUPPORTED_BINANCE_ASSETS
        except UnknownAsset as e:
            test_warnings.warn(UserWarning(
                f'Found unknown asset {e.asset_name} in binance. '
                f'Support for it has to be added',
            ))
Beispiel #20
0
        def process_pool_asset(asset_name: str, asset_amount: FVal) -> None:
            if asset_amount == ZERO:
                return None

            try:
                asset = asset_from_binance(asset_name)
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported {self.name} asset {asset_name}. '
                    f'Ignoring its {self.name} pool balance query. {str(e)}',
                )
                return None
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown {self.name} asset {asset_name}. '
                    f'Ignoring its {self.name} pool balance query. {str(e)}',
                )
                return None
            except DeserializationError as e:
                self.msg_aggregator.add_error(
                    f'{self.name} balance deserialization error '
                    f'for asset {asset_name}: {str(e)}. Skipping entry.',
                )
                return None

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

            balances[asset] += Balance(
                amount=asset_amount,
                usd_value=asset_amount * usd_price,
            )
            return None
Beispiel #21
0
def test_binance_assets_are_known(
        accounting_data_dir,
        inquirer,  # pylint: disable=unused-argument
):
    # use a real binance instance so that we always get the latest data
    binance = Binance(
        api_key=make_api_key(),
        secret=make_api_secret(),
        user_directory=accounting_data_dir,
        msg_aggregator=MessagesAggregator(),
    )

    mapping = binance.symbols_to_pair
    binance_assets = set()
    for _, pair in mapping.items():
        binance_assets.add(pair.binance_base_asset)
        binance_assets.add(pair.binance_quote_asset)

    sorted_assets = sorted(binance_assets)
    for binance_asset in sorted_assets:
        try:
            _ = asset_from_binance(binance_asset)
        except UnsupportedAsset:
            assert binance_asset in UNSUPPORTED_BINANCE_ASSETS
Beispiel #22
0
def trade_from_binance(
        binance_trade: Dict,
        binance_symbols_to_pair: Dict[str, BinancePair],
) -> Trade:
    """Turn a binance trade returned from trade history to our common trade
    history format

    From the official binance api docs (01/09/18):
    https://github.com/binance-exchange/binance-official-api-docs/blob/62ff32d27bb32d9cc74d63d547c286bb3c9707ef/rest-api.md#terminology

    base asset refers to the asset that is the quantity of a symbol.
    quote asset refers to the asset that is the price of a symbol.

    Throws:
        - UnsupportedAsset due to asset_from_binance
        - DeserializationError due to unexpected format of dict entries
        - KeyError due to dict entries missing an expected entry
    """
    amount = deserialize_asset_amount(binance_trade['qty'])
    rate = deserialize_price(binance_trade['price'])
    if binance_trade['symbol'] not in binance_symbols_to_pair:
        raise DeserializationError(
            f'Error reading a binance trade. Could not find '
            f'{binance_trade["symbol"]} in binance_symbols_to_pair',
        )

    binance_pair = binance_symbols_to_pair[binance_trade['symbol']]
    timestamp = deserialize_timestamp_from_binance(binance_trade['time'])

    base_asset = asset_from_binance(binance_pair.binance_base_asset)
    quote_asset = asset_from_binance(binance_pair.binance_quote_asset)

    if binance_trade['isBuyer']:
        order_type = TradeType.BUY
        # e.g. in RDNETH we buy RDN by paying ETH
    else:
        order_type = TradeType.SELL

    fee_currency = asset_from_binance(binance_trade['commissionAsset'])
    fee = deserialize_fee(binance_trade['commission'])

    log.debug(
        'Processing binance Trade',
        sensitive_log=True,
        amount=amount,
        rate=rate,
        timestamp=timestamp,
        pair=binance_trade['symbol'],
        base_asset=base_asset,
        quote=quote_asset,
        order_type=order_type,
        commision_asset=binance_trade['commissionAsset'],
        fee=fee,
    )

    return Trade(
        timestamp=timestamp,
        location=Location.BINANCE,
        pair=trade_pair_from_assets(base_asset, quote_asset),
        trade_type=order_type,
        amount=amount,
        rate=rate,
        fee=fee,
        fee_currency=fee_currency,
        link=str(binance_trade['id']),
    )
Beispiel #23
0
    def _query_spot_balances(
        self,
        balances: DefaultDict[Asset, Balance],
    ) -> DefaultDict[Asset, Balance]:
        account_data = self.api_query_dict('api', 'account')
        binance_balances = account_data.get('balances', None)
        if not binance_balances:
            raise RemoteError(
                'Binance spot balances response did not contain the balances key'
            )

        for entry in binance_balances:
            try:
                # force string https://github.com/rotki/rotki/issues/2342
                asset_symbol = str(entry['asset'])
                free = deserialize_asset_amount(entry['free'])
                locked = deserialize_asset_amount(entry['locked'])
            except KeyError as e:
                raise RemoteError(
                    f'Binance spot balance asset entry did not contain key {str(e)}'
                ) from e  # noqa: E501
            except DeserializationError as e:
                raise RemoteError(
                    'Failed to deserialize an amount from binance spot balance asset entry'
                ) from e  # noqa: E501

            if len(asset_symbol) >= 5 and asset_symbol.startswith('LD'):
                # Some lending coins also appear to start with the LD prefix. Ignore them
                continue

            amount = free + locked
            if amount == ZERO:
                continue

            try:
                asset = asset_from_binance(asset_symbol)
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported {self.name} asset {e.asset_name}. '
                    f'Ignoring its balance query.', )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unknown {self.name} asset {e.asset_name}. '
                    f'Ignoring its balance query.', )
                continue
            except DeserializationError:
                self.msg_aggregator.add_error(
                    f'Found {self.name} asset with non-string type {type(entry["asset"])}. '
                    f'Ignoring its balance query.', )
                continue

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

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

        return balances