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 query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, historical_data_start: Timestamp, ) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. May raise: - PriceQueryUnsupportedAsset if from/to asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server """ try: data = self.get_historical_data( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, historical_data_start=historical_data_start, ) except UnsupportedAsset as e: raise PriceQueryUnsupportedAsset(e.asset_name) # 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: # convert_to_int can't raise here due to its input index = convert_to_int((timestamp - data[0].time) / 3600, accept_only_exact=False) # print("timestamp: {} index: {} data_length: {}".format(timestamp, index, len(data))) 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 None or data[index].low is None: # If we get some None in the hourly set price to 0 so that we check alternatives price = Price(ZERO) else: 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) if price == 0: if from_asset != 'BTC' and to_asset != 'BTC': log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp}. Comparing with BTC...", ) # Just get the BTC price asset_btc_price = PriceHistorian().query_historical_price( from_asset=from_asset, to_asset=A_BTC, timestamp=timestamp, ) btc_to_asset_price = PriceHistorian().query_historical_price( from_asset=A_BTC, to_asset=to_asset, timestamp=timestamp, ) price = Price(asset_btc_price * btc_to_asset_price) else: log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp} through cryptocompare." f" Attempting to get daily price...", ) price = self.query_endpoint_pricehistorical( from_asset, to_asset, timestamp) comparison_to_nonusd_fiat = ((to_asset.is_fiat() and to_asset != A_USD) or (from_asset.is_fiat() and from_asset != A_USD)) if comparison_to_nonusd_fiat: price = self._adjust_to_cryptocompare_price_incosistencies( price=price, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price == 0: raise NoPriceForGivenTimestamp( from_asset=from_asset, to_asset=to_asset, date=timestamp_to_date(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'), ) log.debug( 'Got historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price, ) return price
def query_historical_price( from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. 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. """ log.debug( 'Querying historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if from_asset == to_asset: return Price(FVal('1')) # Querying historical forex data is attempted first via exchangerates API, # and then via any price oracle that has fiat to fiat. if from_asset.is_fiat() and to_asset.is_fiat(): price = Inquirer().query_historical_fiat_exchange_rates( from_fiat_currency=from_asset, to_fiat_currency=to_asset, timestamp=timestamp, ) if price is not None: return price # else cryptocompare also has historical fiat to fiat data instance = PriceHistorian() oracles = instance._oracles oracle_instances = instance._oracle_instances assert isinstance(oracles, list) and isinstance( oracle_instances, list ), ('PriceHistorian should never be called before the setting the oracles' ) for oracle, oracle_instance in zip(oracles, oracle_instances): can_query_history = oracle_instance.can_query_history( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if can_query_history is False: continue try: price = oracle_instance.query_historical_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) except (PriceQueryUnsupportedAsset, NoPriceForGivenTimestamp, RemoteError) as e: log.warning( f'Historical price oracle {oracle} failed to request ' f'due to: {str(e)}.', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) continue if price != Price(ZERO): log.debug( f'Historical price oracle {oracle} got price', price=price, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) return 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 query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, historical_data_start: Timestamp, ) -> Price: if from_asset in KNOWN_TO_MISS_FROM_CRYPTOCOMPARE: raise PriceQueryUnknownFromAsset(from_asset) data = self.get_historical_data( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, historical_data_start=historical_data_start, ) # 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 = convert_to_int((timestamp - data[0].time) / 3600, accept_only_exact=False) # print("timestamp: {} index: {} data_length: {}".format(timestamp, index, len(data))) 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 None or data[index].low is None: # If we get some None in the hourly set price to 0 so that we check alternatives price = Price(ZERO) else: price = (data[index].high + data[index].low) / 2 else: # no price found in the historical data from/to asset, try alternatives price = Price(ZERO) if price == 0: if from_asset != 'BTC' and to_asset != 'BTC': log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp}. Comparing with BTC...", ) # Just get the BTC price asset_btc_price = PriceHistorian().query_historical_price( from_asset=from_asset, to_asset=A_BTC, timestamp=timestamp, ) btc_to_asset_price = PriceHistorian().query_historical_price( from_asset=A_BTC, to_asset=to_asset, timestamp=timestamp, ) price = asset_btc_price * btc_to_asset_price else: log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp} through cryptocompare." f" Attempting to get daily price...", ) price = self.query_endpoint_pricehistorical( from_asset, to_asset, timestamp) comparison_to_nonusd_fiat = ((to_asset.is_fiat() and to_asset != A_USD) or (from_asset.is_fiat() and from_asset != A_USD)) if comparison_to_nonusd_fiat: price = self._adjust_to_cryptocompare_price_incosistencies( price=price, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price == 0: raise NoPriceForGivenTimestamp( from_asset, to_asset, timestamp_to_date(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'), ) log.debug( 'Got historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price, ) return price
def query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, historical_data_start: Timestamp, ) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. May raise: - PriceQueryUnsupportedAsset if from/to asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server """ try: data = self.get_historical_data( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, historical_data_start=historical_data_start, ) except UnsupportedAsset as e: raise PriceQueryUnsupportedAsset(e.asset_name) price = Price(ZERO) # 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) if price == 0: if from_asset != 'BTC' and to_asset != 'BTC': log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp}. Comparing with BTC...", ) # Just get the BTC price asset_btc_price = PriceHistorian().query_historical_price( from_asset=from_asset, to_asset=A_BTC, timestamp=timestamp, ) btc_to_asset_price = PriceHistorian().query_historical_price( from_asset=A_BTC, to_asset=to_asset, timestamp=timestamp, ) price = Price(asset_btc_price * btc_to_asset_price) else: log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp} through cryptocompare." f" Attempting to get daily price...", ) price = self.query_endpoint_pricehistorical( from_asset, to_asset, timestamp) comparison_to_nonusd_fiat = ((to_asset.is_fiat() and to_asset != A_USD) or (from_asset.is_fiat() and from_asset != A_USD)) if comparison_to_nonusd_fiat: price = self._adjust_to_cryptocompare_price_incosistencies( price=price, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price == 0: raise NoPriceForGivenTimestamp( from_asset=from_asset, to_asset=to_asset, date=timestamp_to_date(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'), ) log.debug( 'Got historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price, ) return price
def query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. This tries to: 1. Find cached cryptocompare values and return them 2. If none exist at the moment try the normal historical price endpoint 3. Else fail May raise: - PriceQueryUnsupportedAsset if from/to asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server """ # TODO: Figure out a better way to log and return. Only thing I can imagine # is nested ifs (ugly af) or a different function (meh + performance). # NB: check if the from..to asset price (or viceversa) is a special # histohour API case. price = self._check_and_get_special_histohour_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price != Price(ZERO): log.debug('Got historical price from cryptocompare', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price) # noqa: E501 return price # check DB cache price_cache_entry = GlobalDBHandler().get_historical_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, max_seconds_distance=3600, source=HistoricalPriceOracle.CRYPTOCOMPARE, ) if price_cache_entry and price_cache_entry.price != Price(ZERO): log.debug('Got historical price from cryptocompare', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price) # noqa: E501 return price_cache_entry.price # else log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp} through cryptocompare." f" Attempting to get daily price...", ) price = self.query_endpoint_pricehistorical(from_asset, to_asset, timestamp) if price == Price(ZERO): 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, ), ) log.debug('Got historical price from cryptocompare', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price) # noqa: E501 return price
def query_historical_price(from_asset: Asset, to_asset: Asset, timestamp: Timestamp) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. 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: - PriceQueryUnsupportedAsset if from/to asset is missing from price oracles - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from the external service. - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ log.debug( 'Querying historical price', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if from_asset == to_asset: return Price(FVal('1')) if from_asset.is_fiat() and to_asset.is_fiat(): # if we are querying historical forex data then try something other than cryptocompare price = Inquirer().query_historical_fiat_exchange_rates( from_fiat_currency=from_asset, to_fiat_currency=to_asset, timestamp=timestamp, ) if price is not None: return price # else cryptocompare also has historical fiat to fiat data instance = PriceHistorian() price = None if Inquirer()._cryptocompare.rate_limited_in_last() is False: try: price = instance._cryptocompare.query_historical_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) except (PriceQueryUnsupportedAsset, NoPriceForGivenTimestamp, RemoteError): # then use coingecko pass if price and price != Price(ZERO): return price try: price = instance._coingecko.historical_price( from_asset=from_asset, to_asset=to_asset, time=timestamp, ) if price != Price(ZERO): return price except RemoteError: pass # nothing found in any price oracle raise NoPriceForGivenTimestamp( from_asset=from_asset, to_asset=to_asset, date=timestamp_to_date(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'), )
def query_historical_price( self, from_asset: Asset, to_asset: Asset, timestamp: Timestamp, ) -> Price: """ Query the historical price on `timestamp` for `from_asset` in `to_asset`. So how much `to_asset` does 1 unit of `from_asset` cost. This tries to: 1. Find cached cryptocompare values and return them 2. If none exist at the moment try the normal historical price endpoint 3. Else fail May raise: - PriceQueryUnsupportedAsset if from/to asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the cryptocompare server or with reading the response returned by the server """ # NB: check if the from..to asset price (or viceversa) is a special # histohour API case. price = self._check_and_get_special_histohour_price( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price != Price(ZERO): return price try: data = self.get_historical_data( from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, only_check_cache=True, ) except UnsupportedAsset as e: raise PriceQueryUnsupportedAsset(e.asset_name) from e price = self._retrieve_price_from_data( data=data, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price == Price(ZERO): log.debug( f"Couldn't find historical price from {from_asset} to " f"{to_asset} at timestamp {timestamp} through cryptocompare." f" Attempting to get daily price...", ) price = self.query_endpoint_pricehistorical( from_asset, to_asset, timestamp) comparison_to_nonusd_fiat = ((to_asset.is_fiat() and to_asset != A_USD) or (from_asset.is_fiat() and from_asset != A_USD)) if comparison_to_nonusd_fiat: price = self._adjust_to_cryptocompare_price_incosistencies( price=price, from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, ) if price == Price(ZERO): raise NoPriceForGivenTimestamp( from_asset=from_asset, to_asset=to_asset, date=timestamp_to_date(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'), ) log.debug( 'Got historical price from cryptocompare', from_asset=from_asset, to_asset=to_asset, timestamp=timestamp, price=price, ) return price