def trade_from_ftx(raw_trade: Dict[str, Any]) -> Optional[Trade]: """Turns an FTX transaction into a rotki Trade. May raise: - UnknownAsset due to Asset instantiation - DeserializationError due to unexpected format of dict entries - KeyError due to dict entries missing an expected key """ # In the case of perpetuals and futures this fields can be None if raw_trade.get('baseCurrency', None) is None: return None if raw_trade.get('quoteCurrency', None) is None: return None timestamp = deserialize_timestamp_from_date(raw_trade['time'], 'iso8601', 'FTX') trade_type = deserialize_trade_type(raw_trade['side']) base_asset = asset_from_ftx(raw_trade['baseCurrency']) quote_asset = asset_from_ftx(raw_trade['quoteCurrency']) amount = deserialize_asset_amount(raw_trade['size']) rate = deserialize_price(raw_trade['price']) fee_currency = asset_from_ftx(raw_trade['feeCurrency']) fee = deserialize_fee(raw_trade['fee']) return Trade( timestamp=timestamp, location=Location.FTX, base_asset=base_asset, quote_asset=quote_asset, trade_type=trade_type, amount=amount, rate=rate, fee=fee, fee_currency=fee_currency, link=str(raw_trade['id']), )
def process_currency(currency: Optional[str]): """Check if a currency is known for the FTX exchange""" if currency is None: return try: if currency not in unknown_assets: asset_from_ftx(base_currency) except UnsupportedAsset: assert base_currency in unsupported_assets except UnknownAsset as e: test_warnings.warn( UserWarning( f'Found unknown asset {e.asset_name} in FTX. ' f'Support for it has to be added', )) unknown_assets.add(base_currency)
def query_balances(self) -> ExchangeQueryBalances: try: resp = self._api_query('wallet/all_balances', paginate=False) except RemoteError as e: msg = f'FTX API request failed. Could not reach FTX due to {str(e)}' log.error(msg) return None, msg # flatten the list that maps accounts to balances balances = [x for _, bal in resp.items() for x in bal] # extract the balances and aggregate them returned_balances: DefaultDict[Asset, Balance] = defaultdict(Balance) for balance_info in balances: try: amount = deserialize_asset_amount(balance_info['total']) # ignore empty balances. FTX returns zero for some coins if amount == ZERO: continue asset = asset_from_ftx(balance_info['coin']) try: usd_price = Inquirer().find_usd_price(asset=asset) except RemoteError as e: self.msg_aggregator.add_error( f'Error processing FTX balance entry due to inability to ' f'query USD price: {str(e)}. Skipping balance entry', ) continue returned_balances[asset] += Balance( amount=amount, usd_value=amount * usd_price, ) except UnknownAsset as e: self.msg_aggregator.add_warning( f'Found FTX balance result with unknown asset ' f'{e.asset_name}. Ignoring it.', ) continue except UnsupportedAsset as e: self.msg_aggregator.add_warning( f'Found FTX balance result with unsupported asset ' f'{e.asset_name}. Ignoring it.', ) 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( 'Error processing an FTX account balance. Check logs ' 'for details. Ignoring it.', ) log.error( 'Error processing an FTX balance', error=msg, ) continue return dict(returned_balances), ''
def _deserialize_asset_movement( self, raw_data: Dict[str, Any], movement_type: AssetMovementCategory, ) -> Optional[AssetMovement]: """Processes a single deposit/withdrawal from FTX and deserializes it Can log error/warning and return None if something went wrong at deserialization """ try: if raw_data['status'] not in ('complete', 'confirmed'): return None timestamp = deserialize_timestamp_from_date( raw_data['time'], 'iso8601', 'FTX') amount = deserialize_asset_amount_force_positive(raw_data['size']) asset = asset_from_ftx(raw_data['coin']) fee = Fee(ZERO) movement_category = movement_type if raw_data.get('fee', None) is not None: fee = deserialize_fee(raw_data['fee']) address = raw_data.get('address', None) if isinstance(address, dict): address = raw_data['address'].get('address', None) transaction_id = raw_data.get('txid', None) return AssetMovement( location=Location.FTX, category=movement_category, address=address, transaction_id=transaction_id, timestamp=timestamp, asset=asset, amount=amount, fee_asset=asset, fee=fee, link=str(raw_data['id']), ) except UnknownAsset as e: self.msg_aggregator.add_warning( f'Found FTX deposit/withdrawal with unknown asset ' f'{e.asset_name}. Ignoring it.', ) except UnsupportedAsset as e: self.msg_aggregator.add_warning( f'Found FTX 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( 'Unexpected data encountered during deserialization of an FTX ' 'asset movement. Check logs for details and open a bug report.', ) log.error( 'Error processing FTX trade', trade=raw_data, error=msg, ) return None