def gemini_symbol_to_base_quote(symbol: str) -> Tuple[Asset, Asset]: """Turns a gemini symbol product into a base/quote asset tuple - Can raise UnprocessableTradePair if symbol is in unexpected format - Case raise UnknownAsset if any of the pair assets are not known to rotki """ five_letter_assets = ('sushi', '1inch', 'storj', 'matic') if len(symbol) == 6: base_asset = asset_from_gemini(symbol[:3].upper()) quote_asset = asset_from_gemini(symbol[3:].upper()) elif len(symbol) == 7: try: base_asset = asset_from_gemini(symbol[:4].upper()) quote_asset = asset_from_gemini(symbol[4:].upper()) except UnknownAsset: base_asset = asset_from_gemini(symbol[:3].upper()) quote_asset = asset_from_gemini(symbol[3:].upper()) elif len(symbol) == 8: if any([asset in symbol for asset in five_letter_assets]): base_asset = asset_from_gemini(symbol[:5].upper()) quote_asset = asset_from_gemini(symbol[5:].upper()) else: base_asset = asset_from_gemini(symbol[:4].upper()) quote_asset = asset_from_gemini(symbol[4:].upper()) else: raise UnprocessableTradePair(symbol) return base_asset, quote_asset
def query_online_deposits_withdrawals( self, start_ts: Timestamp, end_ts: Timestamp, ) -> List[AssetMovement]: result = self._get_paginated_query( endpoint='transfers', start_ts=start_ts, end_ts=end_ts, ) movements = [] for entry in result: try: timestamp = deserialize_timestamp(entry['timestampms']) timestamp = Timestamp(int(timestamp / 1000)) asset = asset_from_gemini(entry['currency']) movement = AssetMovement( location=Location.GEMINI, category=deserialize_asset_movement_category(entry['type']), address=deserialize_asset_movement_address(entry, 'destination', asset), transaction_id=get_key_if_has_val(entry, 'txHash'), timestamp=timestamp, asset=asset, amount=deserialize_asset_amount_force_positive(entry['amount']), fee_asset=asset, # Gemini does not include withdrawal fees neither in the API nor in their UI fee=Fee(ZERO), link=str(entry['eid']), ) except UnknownAsset as e: self.msg_aggregator.add_warning( f'Found gemini deposit/withdrawal with unknown asset ' f'{e.asset_name}. Ignoring it.', ) continue except UnsupportedAsset as e: self.msg_aggregator.add_warning( f'Found gemini deposit/withdrawal 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 a gemini deposit/withdrawal. Check logs ' 'for details. Ignoring it.', ) log.error( 'Error processing a gemini deposit_withdrawal', asset_movement=entry, error=msg, ) continue movements.append(movement) return movements
def query_balances(self) -> ExchangeQueryBalances: try: balances = self._private_api_query('balances') balances.extend(self._private_api_query('balances/earn')) except (GeminiPermissionError, RemoteError) as e: msg = f'Gemini API request failed. {str(e)}' log.error(msg) return None, msg returned_balances: DefaultDict[Asset, Balance] = defaultdict(Balance) for entry in balances: try: balance_type = entry['type'] if balance_type == 'exchange': amount = deserialize_asset_amount(entry['amount']) else: # should be 'Earn' amount = deserialize_asset_amount(entry['balance']) # ignore empty balances if amount == ZERO: continue asset = asset_from_gemini(entry['currency']) try: usd_price = Inquirer().find_usd_price(asset=asset) except RemoteError as e: self.msg_aggregator.add_error( f'Error processing gemini {balance_type} balance result due to ' f'inability to 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 gemini balance result with unknown asset ' f'{e.asset_name}. Ignoring it.', ) continue except UnsupportedAsset as e: self.msg_aggregator.add_warning( f'Found gemini {balance_type} balance result with unsupported ' f'asset {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( f'Error processing a gemini {balance_type} balance. Check logs ' f'for details. Ignoring it.', ) log.error( f'Error processing a gemini {balance_type} balance', error=msg, ) continue return returned_balances, ''
def query_online_trade_history( self, start_ts: Timestamp, end_ts: Timestamp, ) -> Tuple[List[Trade], Tuple[Timestamp, Timestamp]]: """Queries gemini for trades """ log.debug('Query gemini trade history', start_ts=start_ts, end_ts=end_ts) trades = [] gemini_trades = [] for symbol in self.symbols: gemini_trades = self._get_trades_for_symbol( symbol=symbol, start_ts=start_ts, end_ts=end_ts, ) for entry in gemini_trades: try: timestamp = deserialize_timestamp(entry['timestamp']) if timestamp > end_ts: break base, quote = gemini_symbol_to_base_quote(symbol) trades.append(Trade( timestamp=timestamp, location=Location.GEMINI, base_asset=base, quote_asset=quote, trade_type=TradeType.deserialize(entry['type']), amount=deserialize_asset_amount(entry['amount']), rate=deserialize_price(entry['price']), fee=deserialize_fee(entry['fee_amount']), fee_currency=asset_from_gemini(entry['fee_currency']), link=str(entry['tid']), notes='', )) except UnprocessableTradePair as e: self.msg_aggregator.add_warning( f'Found unprocessable Gemini pair {e.pair}. Ignoring the trade.', ) continue except UnknownAsset as e: self.msg_aggregator.add_warning( f'Found unknown Gemini asset {e.asset_name}. ' f'Ignoring the trade.', ) 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( 'Failed to deserialize a gemini trade. ' 'Check logs for details. Ignoring it.', ) log.error( 'Error processing a gemini trade.', raw_trade=entry, error=msg, ) continue return trades, (start_ts, end_ts)