def test_fallback_to_coingecko(inquirer): # pylint: disable=unused-argument """Cryptocompare does not return current prices for some assets. For those we are going to be using coingecko""" price = inquirer.find_usd_price(Asset('RARI')) assert price != Price(ZERO) price = inquirer.find_usd_price(Asset('TLN')) assert price != Price(ZERO)
def query_cryptocompare_for_fiat_price(asset: Asset) -> Price: log.debug('Get usd price from cryptocompare', asset=asset) cc_asset_str = asset.to_cryptocompare() resp = retry_calls( 5, 'find_usd_price', 'requests.get', requests.get, u'https://min-api.cryptocompare.com/data/price?' 'fsym={}&tsyms=USD'.format(cc_asset_str), ) if resp.status_code != 200: raise RemoteError( 'Cant reach cryptocompare to get USD value of {}'.format(asset)) resp = rlk_jsonloads_dict(resp.text) # If there is an error in the response skip this token if 'USD' not in resp: error_message = '' if resp['Response'] == 'Error': error_message = resp['Message'] log.error( 'Cryptocompare usd price query failed', asset=asset, error=error_message, ) return Price(ZERO) price = Price(FVal(resp['USD'])) log.debug('Got usd price from cryptocompare', asset=asset, price=price) return price
def _check_and_get_special_histohour_price( from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """For the given timestamp, check whether the from..to asset price (or viceversa) is a special histohour API case. If so, return the price based on the assets pair, otherwise return zero. NB: special histohour API cases are the ones where this Cryptocompare API returns zero prices per hour. """ price = Price(ZERO) if (from_asset in CRYPTOCOMPARE_SPECIAL_HISTOHOUR_CASES and to_asset == A_USD or from_asset == A_USD and to_asset in CRYPTOCOMPARE_SPECIAL_HISTOHOUR_CASES): asset_data = (CRYPTOCOMPARE_SPECIAL_HISTOHOUR_CASES[from_asset] if to_asset == A_USD else CRYPTOCOMPARE_SPECIAL_HISTOHOUR_CASES[to_asset]) if timestamp <= asset_data.timestamp: price = (asset_data.usd_price if to_asset == A_USD else Price( FVal('1') / asset_data.usd_price)) log.warning( f'Query price of: {from_asset.identifier} in {to_asset.identifier} ' f'at timestamp {timestamp} may return zero price. ' f'Setting price to {price}, from timestamp {asset_data.timestamp}.', ) return price
def query_current_price( self, from_asset: Asset, to_asset: Asset, handling_special_case: bool = False, ) -> Price: """Returns the current price of an asset compared to another asset - May raise RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server - May raise PriceQueryUnsupportedAsset if from/to assets are not known to cryptocompare """ special_asset = (from_asset in CRYPTOCOMPARE_SPECIAL_CASES or to_asset in CRYPTOCOMPARE_SPECIAL_CASES) if special_asset and not handling_special_case: return self._special_case_handling( method_name='query_current_price', from_asset=from_asset, to_asset=to_asset, ) try: cc_from_asset_symbol = from_asset.to_cryptocompare() cc_to_asset_symbol = to_asset.to_cryptocompare() except UnsupportedAsset as e: raise PriceQueryUnsupportedAsset(e.asset_name) from e query_path = f'price?fsym={cc_from_asset_symbol}&tsyms={cc_to_asset_symbol}' result = self._api_query(path=query_path) # Up until 23/09/2020 cryptocompare may return {} due to bug. # Handle that case by assuming 0 if that happens if cc_to_asset_symbol not in result: return Price(ZERO) return Price(FVal(result[cc_to_asset_symbol]))
def _update_assets_prices_in_address_balances( address_balances: AddressBalances, known_asset_price: AssetPrice, unknown_asset_price: AssetPrice, ) -> None: """Update the pools underlying assets prices in USD (prices obtained via Inquirer and the Uniswap subgraph) """ for lps in address_balances.values(): for lp in lps: # Try to get price from either known or unknown asset price. # Otherwise keep existing price (zero) total_user_balance = FVal(0) for asset in lp.assets: asset_ethereum_address = asset.asset.ethereum_address asset_usd_price = known_asset_price.get( asset_ethereum_address, unknown_asset_price.get(asset_ethereum_address, Price(ZERO)), ) # Update <LiquidityPoolAsset> if asset USD price exists if asset_usd_price != Price(ZERO): asset.usd_price = asset_usd_price asset.user_balance.usd_value = FVal( asset.user_balance.amount * asset_usd_price, ) total_user_balance += asset.user_balance.usd_value # Update <LiquidityPool> total balance in USD lp.user_balance.usd_value = total_user_balance
def test_fallback_to_cached_values_within_a_month(inquirer): # pylint: disable=unused-argument def mock_api_remote_fail(url, timeout): # pylint: disable=unused-argument return MockResponse(500, '{"msg": "shit hit the fan"') # Get a date 15 days ago and insert a cached entry for EUR JPY then # Get a date 31 days ago and insert a cache entry for EUR CNY then now = ts_now() eurjpy_val = Price(FVal('124.123')) cache_data = [ HistoricalPrice( from_asset=A_EUR, to_asset=A_JPY, source=HistoricalPriceOracle.XRATESCOM, timestamp=Timestamp(now - 86400 * 15), price=eurjpy_val, ), HistoricalPrice( from_asset=A_EUR, to_asset=A_CNY, source=HistoricalPriceOracle.XRATESCOM, timestamp=Timestamp(now - 86400 * 31), price=Price(FVal('7.719')), ) ] GlobalDBHandler().add_historical_prices(cache_data) with patch('requests.get', side_effect=mock_api_remote_fail): # We fail to find a response but then go back 15 days and find the cached response result = inquirer._query_fiat_pair(A_EUR, A_JPY) assert result == eurjpy_val # The cached response for EUR CNY is too old so we will fail here with pytest.raises(RemoteError): result = inquirer._query_fiat_pair(A_EUR, A_CNY)
def mock_query_cryptocompare_for_fiat_price(asset: Asset) -> Price: if asset == A_ETH: return Price(eth_usd_rate) elif asset == A_BTC: return Price(btc_usd_rate) # else raise AssertionError(f'Unexpected asset {asset} at mock cryptocompare query')
def _dict_history_to_entries(data: List[Dict[str, Any]]) -> List[PriceHistoryEntry]: """Turns a list of dict of history entries to a list of proper objects""" return [ PriceHistoryEntry( time=Timestamp(entry['time']), low=Price(FVal(entry['low'])), high=Price(FVal(entry['high'])), ) for entry in data ]
def test_fallback_to_coingecko(inquirer): # pylint: disable=unused-argument """Cryptocompare does not return current prices for some assets. For those we are going to be using coingecko""" price = inquirer.find_usd_price( EthereumToken('0xFca59Cd816aB1eaD66534D82bc21E7515cE441CF') ) # RARRI # noqa: E501 assert price != Price(ZERO) price = inquirer.find_usd_price( EthereumToken( '0x679131F591B4f369acB8cd8c51E68596806c3916')) # TLN # noqa: E501 assert price != Price(ZERO)
def get_underlying_asset_price(token: EthereumToken) -> Optional[Price]: """Gets the underlying asset price for the given ethereum token TODO: This should be eventually pulled from the assets DB. All of these need to be updated, to contain proper protocol, and underlying assets. This function is neither in inquirer.py or chain/ethereum/defi.py due to recursive import problems """ price = None if token.protocol == UNISWAP_PROTOCOL: price = Inquirer().find_uniswap_v2_lp_price(token) elif token.protocol == CURVE_POOL_PROTOCOL: price = Inquirer().find_curve_pool_price(token) elif token.protocol == YEARN_VAULTS_V2_PROTOCOL: price = Inquirer().find_yearn_price(token) if token == A_YV1_ALINK: price = Inquirer().find_usd_price(A_ALINK_V1) elif token == A_YV1_GUSD: price = Inquirer().find_usd_price(A_GUSD) elif token in (A_YV1_DAI, A_FARM_DAI): price = Inquirer().find_usd_price(A_DAI) elif token in (A_FARM_WETH, A_YV1_WETH): price = Inquirer().find_usd_price(A_ETH) elif token == A_YV1_YFI: price = Inquirer().find_usd_price(A_YFI) elif token in (A_FARM_USDT, A_YV1_USDT): price = Inquirer().find_usd_price(A_USDT) elif token in (A_FARM_USDC, A_YV1_USDC): price = Inquirer().find_usd_price(A_USDC) elif token in (A_FARM_TUSD, A_YV1_TUSD): price = Inquirer().find_usd_price(A_TUSD) elif token in ASSETS_UNDERLYING_BTC: price = Inquirer().find_usd_price(A_BTC) # At this point we have to return the price if it's not None. If we don't do this and got # a price for a token that has underlying assets, the code will enter the if statement after # this block and the value for price will change becoming incorrect. if price is not None: return price custom_token = GlobalDBHandler().get_ethereum_token(token.ethereum_address) if custom_token and custom_token.underlying_tokens is not None: usd_price = ZERO for underlying_token in custom_token.underlying_tokens: token = EthereumToken(underlying_token.address) usd_price += Inquirer().find_usd_price( token) * underlying_token.weight if usd_price != Price(ZERO): price = Price(usd_price) return price
def get_fiat_usd_exchange_rates(currencies: Iterable[Asset]) -> Dict[Asset, Price]: """Gets the USD exchange rate of any of the given assets In case of failure to query a rate it's returned as zero""" rates = {A_USD: Price(FVal(1))} for currency in currencies: try: rates[currency] = Inquirer()._query_fiat_pair(A_USD, currency) except RemoteError: rates[currency] = Price(ZERO) return rates
def query_endpoint_pricehistorical( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, handling_special_case: bool = False, ) -> Price: """Queries the historical daily price of from_asset to to_asset for timestamp - May raise RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server - May raise PriceQueryUnsupportedAsset if from/to assets are not known to cryptocompare """ log.debug( 'Querying cryptocompare for daily historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) special_asset = ( from_asset in CRYPTOCOMPARE_SPECIAL_CASES or to_asset in CRYPTOCOMPARE_SPECIAL_CASES ) if special_asset and not handling_special_case: return self._special_case_handling( method_name='query_endpoint_pricehistorical', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) try: cc_from_asset_symbol = from_asset.to_cryptocompare() cc_to_asset_symbol = to_asset.to_cryptocompare() except UnsupportedAsset as e: raise PriceQueryUnsupportedAsset(e.asset_name) from e query_path = ( f'pricehistorical?fsym={cc_from_asset_symbol}&tsyms={cc_to_asset_symbol}' f'&ts={timestamp}' ) if to_asset == 'BTC': query_path += '&tryConversion=false' result = self._api_query(query_path) # Up until 23/09/2020 cryptocompare may return {} due to bug. # Handle that case by assuming 0 if that happens if ( cc_from_asset_symbol not in result or cc_to_asset_symbol not in result[cc_from_asset_symbol] ): return Price(ZERO) return Price(FVal(result[cc_from_asset_symbol][cc_to_asset_symbol]))
def _retrieve_price_from_data( data: Optional[List[PriceHistoryEntry]], from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """Reads historical price data list returned from cryptocompare histohour or cache and returns a price. If nothing is found it returns Price(0). This can also happen if cryptocompare returns a list of 0s for the timerange. """ price = Price(ZERO) if data is None or len(data) == 0: return price # all data are sorted and timestamps are always increasing by 1 hour # find the closest entry to the provided timestamp if timestamp >= data[0].time: index_in_bounds = True # convert_to_int can't raise here due to its input index = convert_to_int((timestamp - data[0].time) / 3600, accept_only_exact=False) if index > len(data) - 1: # index out of bounds # Try to see if index - 1 is there and if yes take it if index > len(data): index = index - 1 else: # give up. This happened: https://github.com/rotki/rotki/issues/1534 log.error( f'Expected data index in cryptocompare historical hour price ' f'not found. Queried price of: {from_asset.identifier} in ' f'{to_asset.identifier} at {timestamp}. Data ' f'index: {index}. Length of returned data: {len(data)}. ' f'https://github.com/rotki/rotki/issues/1534. Attempting other methods...', ) index_in_bounds = False if index_in_bounds: diff = abs(data[index].time - timestamp) if index + 1 <= len(data) - 1: diff_p1 = abs(data[index + 1].time - timestamp) if diff_p1 < diff: index = index + 1 if data[index].high is not None and data[index].low is not None: price = Price((data[index].high + data[index].low) / 2) else: # no price found in the historical data from/to asset, try alternatives price = Price(ZERO) return price
def mock_query_price(from_asset, to_asset): assert from_asset.identifier == 'ETH' assert to_asset.identifier == 'USD' nonlocal call_count if call_count == 0: price = Price(FVal('1')) elif call_count in (1, 2): price = Price(FVal('2')) else: raise AssertionError('Called too many times for this test') call_count += 1 return price
def query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: vs_currency = Coingecko.check_vs_currencies( from_asset=from_asset, to_asset=to_asset, location='historical price', ) if not vs_currency: return Price(ZERO) try: from_coingecko_id = from_asset.to_coingecko() except UnsupportedAsset: log.warning( f'Tried to query coingecko historical price from {from_asset.identifier} ' f'to {to_asset.identifier}. But from_asset is not supported in coingecko', ) return Price(ZERO) date = timestamp_to_date(timestamp, formatstr='%d-%m-%Y') cached_price = self._get_cached_price(from_asset=from_asset, to_asset=to_asset, date=date) if cached_price is not None: return cached_price result = self._query( module='coins', subpath=f'{from_coingecko_id}/history', options={ 'date': date, 'localization': False, }, ) try: price = Price( FVal(result['market_data']['current_price'][vs_currency])) except KeyError as e: log.warning( f'Queried coingecko historical price from {from_asset.identifier} ' f'to {to_asset.identifier}. But got key error for {str(e)} when ' f'processing the result.', ) return Price(ZERO) self._save_cached_price(from_asset, to_asset, date, price) return price
def test_find_usd_price_no_price_found(inquirer): """Test zero price is returned when all the oracles returned zero price requesting the USD price of an asset. """ inquirer._oracle_instances = [MagicMock() for _ in inquirer._oracles] for oracle_instance in inquirer._oracle_instances: oracle_instance.query_current_price.return_value = Price(ZERO) price = inquirer.find_usd_price(A_BTC) assert price == Price(ZERO) for oracle_instance in inquirer._oracle_instances: assert oracle_instance.query_current_price.call_count == 1
def _deserialize_transaction(grant_id: int, rawtx: Dict[str, Any]) -> LedgerAction: """May raise: - DeserializationError - KeyError - UnknownAsset """ timestamp = deserialize_timestamp_from_date( date=rawtx['timestamp'], formatstr='%Y-%m-%dT%H:%M:%S', location='Gitcoin API', skip_milliseconds=True, ) asset = get_gitcoin_asset(symbol=rawtx['asset'], token_address=rawtx['token_address']) raw_amount = deserialize_int_from_str(symbol=rawtx['amount'], location='gitcoin api') amount = asset_normalized_value(raw_amount, asset) if amount == ZERO: raise ZeroGitcoinAmount() # let's use gitcoin's calculated rate for now since they include it in the response usd_value = Price( ZERO) if rawtx['usd_value'] is None else deserialize_price( rawtx['usd_value']) # noqa: E501 rate = Price(ZERO) if usd_value == ZERO else Price(usd_value / amount) raw_txid = rawtx['tx_hash'] tx_type, tx_id = process_gitcoin_txid(key='tx_hash', entry=rawtx) # until we figure out if we can use it https://github.com/gitcoinco/web/issues/9255#issuecomment-874537144 # noqa: E501 clr_round = _calculate_clr_round(timestamp, rawtx) notes = f'Gitcoin grant {grant_id} event' if not clr_round else f'Gitcoin grant {grant_id} event in clr_round {clr_round}' # noqa: E501 return LedgerAction( identifier=1, # whatever -- does not end up in the DB timestamp=timestamp, action_type=LedgerActionType.DONATION_RECEIVED, location=Location.GITCOIN, amount=AssetAmount(amount), asset=asset, rate=rate, rate_asset=A_USD, link=raw_txid, notes=notes, extra_data=GitcoinEventData( tx_id=tx_id, grant_id=grant_id, clr_round=clr_round, tx_type=tx_type, ), )
def test_cryptocompare_histohour_data_going_backward(data_dir, database, freezer): """Test that the cryptocompare histohour data retrieval works properly This test checks that doing an additional query in the past workd properly and that the cached data are properly appended to the cached result. In production this scenario should not happen often. Only way to happen if cryptocompare somehow adds older data than what was previously queried. """ globaldb = GlobalDBHandler() # first timestamp cryptocompare has histohour BTC/USD when queried from this test is btc_start_ts = 1279936800 # first timestamp cryptocompare has histohour BTC/USD is: 1279940400 now_ts = btc_start_ts + 3600 * 2000 + 122 # create a cache file for BTC_USD cache_data = [ HistoricalPrice( from_asset=A_BTC, to_asset=A_USD, source=HistoricalPriceOracle.CRYPTOCOMPARE, timestamp=Timestamp(1301536800), price=Price(FVal('0.298')), ), HistoricalPrice( from_asset=A_BTC, to_asset=A_USD, source=HistoricalPriceOracle.CRYPTOCOMPARE, timestamp=Timestamp(1301540400), price=Price(FVal('0.298')), ) ] globaldb.add_historical_prices(cache_data) freezer.move_to(datetime.fromtimestamp(now_ts)) cc = Cryptocompare(data_directory=data_dir, database=database) cc.query_and_store_historical_data( from_asset=A_BTC, to_asset=A_USD, timestamp=now_ts - 3600 * 2 - 55, ) result = get_globaldb_cache_entries(from_asset=A_BTC, to_asset=A_USD) assert len(result) == CRYPTOCOMPARE_HOURQUERYLIMIT * 3 + 2 check_cc_result(result, forward=False) data_range = globaldb.get_historical_price_range( A_BTC, A_USD, HistoricalPriceOracle.CRYPTOCOMPARE) # noqa: E501 assert data_range[0] == btc_start_ts assert data_range[ 1] == 1301540400 # that's the closest ts to now_ts cc returns
def test_find_usd_price_cache(inquirer, freezer): # pylint: disable=unused-argument call_count = 0 def mock_query_price(from_asset, to_asset): assert from_asset.identifier == 'ETH' assert to_asset.identifier == 'USD' nonlocal call_count if call_count == 0: price = Price(FVal('1')) elif call_count in (1, 2): price = Price(FVal('2')) else: raise AssertionError('Called too many times for this test') call_count += 1 return price cc_patch = patch.object( inquirer._cryptocompare, 'query_current_price', wraps=mock_query_price, ) inquirer.set_oracles_order(oracles=[CurrentPriceOracle.CRYPTOCOMPARE]) with cc_patch as cc: price = inquirer.find_usd_price(A_ETH) assert cc.call_count == 1 assert price == Price(FVal('1')) # next time we run, make sure it's the cache price = inquirer.find_usd_price(A_ETH) assert cc.call_count == 1 assert price == Price(FVal('1')) # now move forward in time to invalidate the cache freezer.move_to( datetime.fromtimestamp(ts_now() + CURRENT_PRICE_CACHE_SECS + 1)) price = inquirer.find_usd_price(A_ETH) assert cc.call_count == 2 assert price == Price(FVal('2')) # also test that ignore_cache works price = inquirer.find_usd_price(A_ETH) assert cc.call_count == 2 assert price == Price(FVal('2')) price = inquirer.find_usd_price(A_ETH, ignore_cache=True) assert cc.call_count == 3 assert price == Price(FVal('2'))
def _query_exchanges_rateapi(base: Asset, quote: Asset) -> Optional[Price]: assert base.is_fiat(), 'fiat currency should have been provided' assert quote.is_fiat(), 'fiat currency should have been provided' log.debug( 'Querying api.exchangeratesapi.io fiat pair', base_currency=base.identifier, quote_currency=quote.identifier, ) querystr = ( f'https://api.exchangeratesapi.io/latest?base={base.identifier}&symbols={quote.identifier}' ) try: resp = request_get_dict(querystr) return Price(FVal(resp['rates'][quote.identifier])) except ( RemoteError, KeyError, requests.exceptions.TooManyRedirects, UnableToDecryptRemoteData, ): log.error( 'Querying api.exchangeratesapi.io for fiat pair failed', base_currency=base.identifier, quote_currency=quote.identifier, ) return None
def find_yearn_price( self, token: EthereumToken, ) -> Optional[Price]: """ Query price for a yearn vault v2 token using the pricePerShare method and the price of the underlying token. """ assert self._ethereum is not None, 'Inquirer ethereum manager should have been initialized' # noqa: E501 maybe_underlying_token = GlobalDBHandler().fetch_underlying_tokens( token.ethereum_address) if maybe_underlying_token is None or len(maybe_underlying_token) != 1: log.error(f'Yearn vault token {token} without an underlying asset') return None underlying_token = EthereumToken(maybe_underlying_token[0].address) underlying_token_price = self.find_usd_price(underlying_token) # Get the price per share from the yearn contract contract = EthereumContract( address=token.ethereum_address, abi=YEARN_VAULT_V2_ABI, deployed_block=0, ) try: price_per_share = contract.call(self._ethereum, 'pricePerShare') return Price(price_per_share * underlying_token_price / 10**token.decimals) except (RemoteError, BlockchainQueryError) as e: log.error( f'Failed to query pricePerShare method in Yearn v2 Vault. {str(e)}' ) return None
def find_price( from_asset: Asset, to_asset: Asset, ignore_cache: bool = False, ) -> Price: """Returns the current price of 'from_asset' in 'to_asset' valuation. NB: prices for special symbols in any currency but USD are not supported. Returns Price(ZERO) if all options have been exhausted and errors are logged in the logs """ if from_asset == to_asset: return Price(FVal('1')) instance = Inquirer() if to_asset == A_USD: return instance.find_usd_price(asset=from_asset, ignore_cache=ignore_cache) if ignore_cache is False: cache = instance.get_cached_current_price_entry( cache_key=(from_asset, to_asset)) if cache is not None: return cache.price oracle_price = instance._query_oracle_instances(from_asset=from_asset, to_asset=to_asset) return oracle_price
def query_historical_price( cls, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: price_entry = GlobalDBHandler().get_historical_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, max_seconds_distance=3600, source=HistoricalPriceOracle.MANUAL, ) if price_entry and price_entry.price != Price(ZERO): log.debug('Got historical manual price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp) # noqa: E501 return price_entry.price raise NoPriceForGivenTimestamp( from_asset=from_asset, to_asset=to_asset, date=timestamp_to_date( timestamp, formatstr='%d/%m/%Y, %H:%M:%S', treat_as_local=True, ), )
def _get_single_balance( self, entry: Tuple[Tuple[str, str, str, int], int]) -> DefiBalance: metadata = entry[0] balance_value = entry[1] decimals = metadata[3] normalized_value = token_normalized_value(balance_value, decimals) token_symbol = metadata[2] try: asset = Asset(token_symbol) usd_price = Inquirer().find_usd_price(asset) except (UnknownAsset, UnsupportedAsset): if not _is_symbol_non_standard(token_symbol): self.msg_aggregator.add_error( f'Unsupported asset {token_symbol} encountered during DeFi protocol queries', ) usd_price = Price(ZERO) usd_value = normalized_value * usd_price defi_balance = DefiBalance( token_address=to_checksum_address(metadata[0]), token_name=metadata[1], token_symbol=token_symbol, balance=Balance(amount=normalized_value, usd_value=usd_value), ) return defi_balance
def deserialize_price(amount: AcceptableFValInitInput) -> Price: try: result = Price(FVal(amount)) except ValueError as e: raise DeserializationError(f'Failed to deserialize a price/rate entry: {str(e)}') return result
def test_get_associated_locations( rotkehlchen_api_server_with_exchanges, added_exchanges, ethereum_accounts, # pylint: disable=unused-argument start_with_valid_premium, # pylint: disable=unused-argument ): rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen mock_exchange_data_in_db(added_exchanges, rotki) db = rotki.data.db db.add_trades([Trade( timestamp=Timestamp(1595833195), location=Location.NEXO, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('1.0')), rate=Price(FVal('281.14')), fee=Fee(ZERO), fee_currency=A_EUR, link='', notes='', )]) # get locations response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, 'associatedlocations', ), ) result = assert_proper_response_with_result(response) assert set(result) == {'nexo', 'binance', 'poloniex'}
def _get_known_asset_price( known_assets: Set[EthereumToken], unknown_assets: Set[UnknownEthereumToken], ) -> AssetPrice: """Get the tokens prices via Inquirer Given an asset, if `find_usd_price()` returns zero, it will be added into `unknown_assets`. """ asset_price: AssetPrice = {} for known_asset in known_assets: asset_usd_price = Inquirer().find_usd_price(known_asset) if asset_usd_price != Price(ZERO): asset_price[known_asset.ethereum_address] = asset_usd_price else: unknown_asset = UnknownEthereumToken( ethereum_address=known_asset.ethereum_address, symbol=known_asset.identifier, name=known_asset.name, decimals=known_asset.decimals, ) unknown_assets.add(unknown_asset) return asset_price
def test_market_request(): """Test that we can query bisq for market prices""" price = get_bisq_market_price(A_BSQ) assert price != Price(ZERO) # Test that error is correctly raised when there is no market with pytest.raises(RemoteError): get_bisq_market_price(A_3CRV)
def test_deserialize_v1_trade(mock_kucoin): raw_result = { 'id': 'xxxx', 'symbol': 'NANO-ETH', 'dealPrice': '0.015743', 'dealValue': '0.00003441', 'amount': '0.002186', 'fee': '0.00000003', 'side': 'sell', 'createdAt': 1520471876, } expected_trade = Trade( timestamp=Timestamp(1520471876), location=Location.KUCOIN, pair=TradePair('NANO_ETH'), trade_type=TradeType.SELL, amount=AssetAmount(FVal('0.002186')), rate=Price(FVal('0.015743')), fee=Fee(FVal('0.00000003')), fee_currency=Asset('ETH'), link='xxxx', notes='', ) trade = mock_kucoin._deserialize_trade( raw_result=raw_result, case=KucoinCase.OLD_TRADES, ) assert trade == expected_trade
def query_endpoint_pricehistorical( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """Queries the historical daily price of from_asset to to_asset for timestamp - May raise RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server """ log.debug( 'Querying cryptocompare for daily historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) # These two can raise but them raising here is a bug cc_from_asset_symbol = from_asset.to_cryptocompare() cc_to_asset_symbol = to_asset.to_cryptocompare() query_path = ( f'pricehistorical?fsym={cc_from_asset_symbol}&tsyms={cc_to_asset_symbol}' f'&ts={timestamp}' ) if to_asset == 'BTC': query_path += '&tryConversion=false' result = self._api_query(query_path) return Price(FVal(result[cc_from_asset_symbol][cc_to_asset_symbol]))