def _update_assets_prices_in_address_balances( address_balances: AddressToLPBalances, known_asset_price: AssetToPrice, unknown_asset_price: AssetToPrice, ) -> None: """Update the pools underlying assets prices in USD (prices obtained via Inquirer and the 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 = ZERO 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 get_price_for_special_asset( from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Optional[Price]: """ Query the historical price on `timestamp` for `from_asset` in `to_asset` for the case where `from_asset` needs a special handling. Can return None if the from asset is not in the list of special cases Args: from_asset: The ticker symbol of the asset for which we want to know the price. to_asset: The ticker symbol of the asset against which we want to know the price. timestamp: The timestamp at which to query the price May raise: - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from the external service. """ if from_asset == A_KFEE: # For KFEE the price is fixed at 0.01$ usd_price = Price(FVal(0.01)) if to_asset == A_USD: return usd_price price_mapping = PriceHistorian().query_historical_price( from_asset=A_USD, to_asset=to_asset, timestamp=timestamp, ) return Price(usd_price * price_mapping) return None
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 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 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 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.identifier in CRYPTOCOMPARE_SPECIAL_CASES or to_asset.identifier 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 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 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 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 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 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 test_manual_oracle_correctly_returns_price(globaldb, fake_price_historian): """Test that the manual oracle correctly returns price for asset""" price_historian = fake_price_historian # Add price at timestamp globaldb.add_single_historical_price( HistoricalPrice( from_asset=A_BTC, to_asset=A_USD, price=30000, timestamp=Timestamp(1611595470), source=HistoricalPriceOracle.MANUAL, ), ) # Make the other oracles fail expected_price = Price(FVal('30000')) oracle_instances = price_historian._oracle_instances oracle_instances[1].query_historical_price.side_effect = PriceQueryUnsupportedAsset('bitcoin') oracle_instances[2].query_historical_price.side_effect = PriceQueryUnsupportedAsset('bitcoin') # Query price, should return the manual price price = price_historian.query_historical_price( from_asset=A_BTC, to_asset=A_USD, timestamp=Timestamp(1611595466), ) assert price == expected_price # Try to get manual price for a timestamp not in db with pytest.raises(NoPriceForGivenTimestamp): price = price_historian.query_historical_price( from_asset=A_BTC, to_asset=A_USD, timestamp=Timestamp(1610595466), )
def _get_tokens_balance_and_price( self, address: ChecksumEthAddress, tokens: List[EthereumToken], balances: Dict[EthereumToken, FVal], token_usd_price: Dict[EthereumToken, Price], call_order: Optional[Sequence[NodeName]], ) -> None: ret = self._get_multitoken_account_balance( tokens=tokens, account=address, call_order=call_order, ) for token_identifier, value in ret.items(): token = EthereumToken.from_identifier(token_identifier) if token is None: # should not happen log.warning( f'Could not initialize token with identifier {token_identifier}. ' f'Should not happen. Skipping its token balance query', ) continue balances[token] += value if token in token_usd_price: continue # else get the price try: usd_price = Inquirer().find_usd_price(token) except RemoteError: usd_price = Price(ZERO) token_usd_price[token] = usd_price
def test_deserialize_trade_sell(mock_bitfinex): mock_bitfinex.currency_map = {'UST': 'USDt'} mock_bitfinex.pair_bfx_symbols_map = {'ETHUST': ('ETH', 'UST')} raw_result = [ 399251013, 'tETHUST', 1573485493000, 33963608932, -0.26334268, 187.37, 'LIMIT', None, -1, -0.09868591, 'USD', ] expected_trade = Trade( timestamp=Timestamp(1573485493), location=Location.BITFINEX, base_asset=A_ETH, quote_asset=A_USDT, trade_type=TradeType.SELL, amount=AssetAmount(FVal('0.26334268')), rate=Price(FVal('187.37')), fee=Fee(FVal('0.09868591')), fee_currency=A_USD, link='399251013', notes='', ) trade = mock_bitfinex._deserialize_trade(raw_result=raw_result) assert trade == expected_trade
def test_uniswap_oracles_asset_to_asset(inquirer_defi): """ Test that the uniswap oracles return a price close to the one reported by coingecko. """ inch_price = inquirer_defi.find_usd_price(A_1INCH) link_price = inquirer_defi.find_usd_price(A_LINK) for oracle in (CurrentPriceOracle.UNISWAPV2, CurrentPriceOracle.UNISWAPV3): if oracle == CurrentPriceOracle.UNISWAPV2: price_instance = inquirer_defi._uniswapv2 else: price_instance = inquirer_defi._uniswapv3 inquirer_defi.set_oracles_order(oracles=[oracle]) price = price_instance.query_current_price(A_1INCH, A_LINK) assert price != Price(ZERO) assert (inch_price / link_price).is_close(price, max_diff='0.01') defi_price = inquirer_defi.find_usd_price(A_LINK, ignore_cache=True) assert abs(defi_price - link_price) / link_price < FVal( 0.1 ), f'{defi_price=} and {link_price=} have more than 10% difference' # noqa: E501 # test with ethereum tokens but as assets instead of instance of the EthereumToken class a1inch = Asset(A_1INCH.identifier) alink = Asset(A_LINK.identifier) price_as_assets = price_instance.query_current_price(a1inch, alink) assert price_as_assets.is_close(price, max_diff='0.01')
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 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 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 get_manually_tracked_balances( db: 'DBHandler', balance_type: Optional[BalanceType] = None, ) -> List[ManuallyTrackedBalanceWithValue]: """Gets the manually tracked balances""" balances = db.get_manually_tracked_balances(balance_type=balance_type) balances_with_value = [] for entry in balances: try: price = Inquirer().find_usd_price(entry.asset) except RemoteError as e: db.msg_aggregator.add_warning( f'Could not find price for {entry.asset.identifier} during ' f'manually tracked balance querying due to {str(e)}', ) price = Price(ZERO) value = Balance(amount=entry.amount, usd_value=price * entry.amount) balances_with_value.append( ManuallyTrackedBalanceWithValue( id=entry.id, asset=entry.asset, label=entry.label, value=value, location=entry.location, tags=entry.tags, balance_type=entry.balance_type, )) return balances_with_value
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, base_asset=A_NANO, quote_asset=A_ETH, trade_type=TradeType.SELL, amount=AssetAmount(FVal('0.002186')), rate=Price(FVal('0.015743')), fee=Fee(FVal('0.00000003')), fee_currency=A_ETH, link='xxxx', notes='', ) trade = mock_kucoin._deserialize_trade( raw_result=raw_result, case=KucoinCase.OLD_TRADES, ) assert trade == expected_trade
def mock_exchange_data_in_db(exchange_locations, rotki) -> None: db = rotki.data.db for exchange_location in exchange_locations: db.add_trades([ Trade( timestamp=Timestamp(1), location=exchange_location, base_asset=A_BTC, quote_asset=A_ETH, trade_type=TradeType.BUY, amount=AssetAmount(FVal(1)), rate=Price(FVal(1)), fee=Fee(FVal('0.1')), fee_currency=A_ETH, link='foo', notes='boo', ) ]) db.update_used_query_range( name=f'{str(exchange_location)}_trades_{str(exchange_location)}', start_ts=0, end_ts=9999) # noqa: E501 db.update_used_query_range( name=f'{str(exchange_location)}_margins_{str(exchange_location)}', start_ts=0, end_ts=9999) # noqa: E501 db.update_used_query_range( name= f'{str(exchange_location)}_asset_movements_{str(exchange_location)}', start_ts=0, end_ts=9999) # noqa: E501
def test_coingecko_historical_price(session_coingecko): price = session_coingecko.query_historical_price( from_asset=A_ETH, to_asset=A_EUR, timestamp=1483056100, ) assert price == Price(FVal('7.7478028375650725'))
def test_get_trade_with_1_token_pool( rotkehlchen_api_server, ethereum_accounts, # pylint: disable=unused-argument rotki_premium_credentials, # pylint: disable=unused-argument start_with_valid_premium, # pylint: disable=unused-argument ): """ Test the special case of a swap within an 1 token pool. This can probably happen if the controller has since removed tokens from the pool. """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, btc_accounts=None, original_queries=['zerion', 'logs', 'blocknobytime'], ) with ExitStack() as stack: # patch ethereum/etherscan to not autodetect tokens setup.enter_ethereum_patches(stack) response = requests.get( api_url_for(rotkehlchen_api_server, 'balancertradeshistoryresource'), json={ 'from_timestamp': 1621358338, 'to_timestamp': 1621358340, }, ) result = assert_proper_response_with_result(response) db_trades = rotki.data.db.get_amm_swaps() assert len(db_trades) == 29 address_trades = result[BALANCER_TEST_ADDR4] assert len(address_trades) == 1 assert address_trades[0] == AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_WBTC, amount=AssetAmount(FVal('0.205421420618533148')), rate=Price(FVal('0.07606382992071615428519015532')), trade_index=0, swaps=[ AMMSwap( tx_hash='0x4f9e0d8aa660a5d3db276a1ade038f7027f29838dd22d5276571d2e4ea7131ae', # noqa: E501 log_index=84, address=string_to_ethereum_address(BALANCER_TEST_ADDR4), # noqa: E501 from_address=string_to_ethereum_address('0xFD3dFB524B2dA40c8a6D703c62BE36b5D8540626'), # noqa: E501 to_address=string_to_ethereum_address('0x582818356331877553F3E9Cf9557b48e5DdbD54a'), # noqa: E501 timestamp=Timestamp(1621358339), location=Location.BALANCER, token0=A_WBTC, token1=A_WETH, amount0_in=AssetAmount(FVal('0.01562514')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('0.205421420618533148')), ), ], ).serialize()
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)}') from e return result
def query_current_price(self, from_asset: Asset, to_asset: Asset) -> Price: """Returns a simple price for from_asset to to_asset in coingecko Uses the simple/price endpoint of coingecko. If to_asset is not part of the coingecko simple vs currencies or if from_asset is not supported in coingecko price zero is returned. May raise: - RemoteError if there is a problem querying coingecko """ vs_currency = Coingecko.check_vs_currencies( from_asset=from_asset, to_asset=to_asset, location='simple 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 simple price from {from_asset.identifier} ' f'to {to_asset.identifier}. But from_asset is not supported in coingecko', ) return Price(ZERO) result = self._query( module='simple/price', options={ 'ids': from_coingecko_id, 'vs_currencies': vs_currency, }) # https://github.com/PyCQA/pylint/issues/4739 try: return Price(FVal(result[from_coingecko_id][vs_currency])) # pylint: disable=unsubscriptable-object # noqa: E501 except KeyError as e: log.warning( f'Queried coingecko simple 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)
def check_cc_result(result: List, forward: bool): for idx, entry in enumerate(result): if idx != 0: assert entry.timestamp == result[idx - 1].timestamp + 3600 # For some reason there seems to be a discrepancy in the way results # are returned between the different queries. It's only minor but seems # like a cryptocompare issue change_ts_1 = 1287140400 if forward else 1287133200 change_ts_2 = 1294340400 if forward else 1294333200 if entry.timestamp <= change_ts_1: assert entry.price == Price(FVal('0.05454')) elif entry.timestamp <= change_ts_2: assert entry.price == Price(FVal('0.105')) elif entry.timestamp <= 1301544000: assert entry.price == Price(FVal('0.298')) else: raise AssertionError(f'Unexpected time entry {entry.time}')
def _query_oracle_instances( from_asset: Asset, to_asset: Asset, ) -> Price: instance = Inquirer() cache_key = (from_asset, to_asset) oracles = instance._oracles oracle_instances = instance._oracle_instances assert isinstance(oracles, list) and isinstance( oracle_instances, list ), ('Inquirer should never be called before the setting the oracles') price = Price(ZERO) for oracle, oracle_instance in zip(oracles, oracle_instances): if (isinstance(oracle_instance, CurrentPriceOracleInterface) and oracle_instance.rate_limited_in_last() is True): continue try: price = oracle_instance.query_current_price( from_asset=from_asset, to_asset=to_asset, ) except (DefiPoolError, PriceQueryUnsupportedAsset, RemoteError) as e: log.error( f'Current price oracle {oracle} failed to request {to_asset.identifier} ' f'price for {from_asset.identifier} due to: {str(e)}.', ) continue if price != Price(ZERO): log.debug( f'Current price oracle {oracle} got price', from_asset=from_asset, to_asset=to_asset, price=price, ) break Inquirer._cached_current_price[cache_key] = CachedPriceEntry( price=price, time=ts_now()) return price
def trade_from_coinbase(raw_trade: Dict[str, Any]) -> Optional[Trade]: """Turns a coinbase transaction into a rotkehlchen Trade. https://developers.coinbase.com/api/v2?python#buys If the coinbase transaction is not a trade related transaction returns None Mary raise: - UnknownAsset due to Asset instantiation - DeserializationError due to unexpected format of dict entries - KeyError due to dict entries missing an expected entry """ if raw_trade['status'] != 'completed': # We only want to deal with completed trades return None # Contrary to the Coinbase documentation we will use created_at, and never # payout_at. It seems like payout_at is not actually the time where the trade is settled. # Reports generated by Coinbase use created_at as well if raw_trade.get('created_at') is not None: raw_time = raw_trade['created_at'] else: raw_time = raw_trade['payout_at'] timestamp = deserialize_timestamp_from_date(raw_time, 'iso8601', 'coinbase') trade_type = TradeType.deserialize(raw_trade['resource']) tx_amount = deserialize_asset_amount(raw_trade['amount']['amount']) tx_asset = asset_from_coinbase(raw_trade['amount']['currency'], time=timestamp) native_amount = deserialize_asset_amount(raw_trade['subtotal']['amount']) native_asset = asset_from_coinbase(raw_trade['subtotal']['currency'], time=timestamp) amount = tx_amount # The rate is how much you get/give in quotecurrency if you buy/sell 1 unit of base currency rate = Price(native_amount / tx_amount) fee_amount = deserialize_fee(raw_trade['fee']['amount']) fee_asset = asset_from_coinbase(raw_trade['fee']['currency'], time=timestamp) return Trade( timestamp=timestamp, location=Location.COINBASE, # in coinbase you are buying/selling tx_asset for native_asset base_asset=tx_asset, quote_asset=native_asset, trade_type=trade_type, amount=amount, rate=rate, fee=fee_amount, fee_currency=fee_asset, link=str(raw_trade['id']), )