def price_converted_to_usd(self, currency_symbol, exchange_name='aggregate'): result = WeightedAverage() for a in self.alive_exchanges: if a.currency_symbol != currency_symbol: continue if a.price_usd is not None and a.price_usd != 0: price_in_usd = a.price_usd volume_in_usd = 0 if a.volume_usd is None else a.volume_usd elif a.price_dai is not None and a.price_dai != 0: price_in_usd = a.price_dai volume_in_usd = 0 if a.volume_dai is None else a.volume_dai elif a.price_eth is not None and a.price_eth != 0: price_in_usd = a.price_eth * self.eth_price_usd() volume_in_usd = 0 if a.volume_eth is None else a.volume_eth * self.eth_price_usd( ) elif a.price_btc is not None and a.price_btc != 0: price_in_usd = a.price_btc * self.eth_price_usd() volume_in_usd = 0 if a.volume_btc is None else a.volume_btc * self.eth_price_usd( ) if exchange_name == 'aggregate' or a.exchange_name == exchange_name: result.add(price_in_usd, volume_in_usd) return result.average()
async def _update(self, timeout=10.0): if self.market_id_vs_eth is None: self.market_id_vs_eth = await self._get_market_id( self.currency_symbol, "ETH") if self.market_id_vs_btc is None: self.market_id_vs_btc = await self._get_market_id( self.currency_symbol, "BTC") if self.market_id_eth_btc is None: self.market_id_eth_btc = await self._get_market_id("ETH", "BTC") if self.market_id_vs_eth is None or self.market_id_vs_btc is None: raise RuntimeError( "Failed to get market ids for asset code '{}'".format( self.currency_symbol)) # grab market data for the desired currency vs ETH and BTC self.price_eth, self.volume_eth, change_eth = await self._fetch_market_data( self.market_id_vs_eth) self.price_btc, self.volume_btc, change_btc = await self._fetch_market_data( self.market_id_vs_btc) # grab market data for ETH/BTC so we can interpret relative data eth_price_in_btc, _, _ = await self._fetch_market_data( self.market_id_eth_btc) if self.volume_btc == 0: ratio_of_eth_vs_btc_volume = 100000 else: ratio_of_eth_vs_btc_volume = self.volume_eth / (self.volume_btc / eth_price_in_btc) average = WeightedAverage() average.add(change_eth, ratio_of_eth_vs_btc_volume) average.add(change_btc, 1) self.change_24h = average.average()
def btc_price_usd(self, api_name='aggregate'): result = WeightedAverage() for a in self.alive_apis: if a.btc_price_usd == None: continue if api_name == 'aggregate' or a.api_name == api_name: result.add(a.btc_price_usd, a.volume_eth) return result.average()
def change_24h(self, currency_symbol='0xBTC', api_name='aggregate'): result = WeightedAverage() for a in self.alive_apis: if a.currency_symbol != currency_symbol: continue if a.change_24h == None: continue if api_name == 'aggregate' or a.api_name == api_name: result.add(a.change_24h, a.volume_eth) return result.average()
def btc_price_usd(self, exchange_name='aggregate'): result = WeightedAverage() for a in self.alive_exchanges: if a.btc_price_usd == None: continue if exchange_name == 'aggregate' or a.exchange_name == exchange_name: if a.currency_symbol == 'BTC': result.add(a.price_usd, a.volume_usd / a.price_usd) else: result.add(a.btc_price_usd, a.volume_btc) return result.average()
def price_usd(self, currency_symbol, exchange_name='aggregate'): result = WeightedAverage() for a in self.alive_exchanges: if a.currency_symbol != currency_symbol: continue if a.price_usd == None: continue if a.volume_eth == None: continue if exchange_name == 'aggregate' or a.exchange_name == exchange_name: result.add(a.price_usd, a.volume_eth) return result.average()
async def _update_all_values(self, should_update_volume=False, timeout=10): if should_update_volume: current_eth_block = self._w3.eth.blockNumber # get price of eth eth_prices = [ get_price(self._w3, "DAI", "WETH"), get_price(self._w3, "USDT", "WETH"), get_price(self._w3, "USDC", "WETH"), ] self.eth_price_usd = sum(eth_prices) / len(eth_prices) # TODO: should be weighted average # get token price (in USD), liquidity (in tokens), and volume (in tokens) for # each pair. Note if liquidity is low for a pair, its voluem is not checked. price_usd_weighted_average = WeightedAverage() total_liquidity_tokens = 0 total_volume_tokens = 0 for exchange_contract in self._exchanges: try: price_usd, liquidity_tokens = await self._get_price_and_liquidity_at_exchange_contract(exchange_contract) except (NoTokenMatchError, PairNotDefinedError) as e: logging.warning(f"Failed to update quickswap exchange: {str(e)}") continue except NoLiquidityException: # no liquidity is not an error; simply skip this exchange continue else: price_usd_weighted_average.add(price_usd, liquidity_tokens) total_liquidity_tokens += liquidity_tokens if should_update_volume and liquidity_tokens > _MINIMUM_ALLOWED_LIQUIDITY_TOKENS_TO_CHECK_VOLUME: try: volume_tokens, volume_pair = await self._get_volume_at_exchange_contract(exchange_contract, current_eth_block=current_eth_block, timeout=timeout) total_volume_tokens += volume_tokens except requests.exceptions.ReadTimeout: logging.warning(f"Failed to update SushiSwapPolygonAPI volume: ReadTimeout") self.price_usd = price_usd_weighted_average.average() self.price_eth = self.price_usd / self.eth_price_usd self.liquidity_tokens = total_liquidity_tokens self.liquidity_eth = self.liquidity_tokens * self.price_eth if should_update_volume: self.hourly_volume_tokens.append(total_volume_tokens) # trim list to 168 hours (7 days) self.hourly_volume_tokens = self.hourly_volume_tokens[-168:] # use last 24 hours for volume self.volume_tokens = sum(self.hourly_volume_tokens[-24:]) self.volume_eth = self.volume_tokens * self.price_eth # NOTE: this sets _time_volume_last_updated even if all volume updates # failed. This is OK for now, it throttles struggling APIs (matic) but # may not be the ideal behavior. self._mark_volume_as_updated()
def price_eth(self, currency_symbol, exchange_name='aggregate'): result = WeightedAverage() for a in self.alive_exchanges: if a.currency_symbol != currency_symbol: continue if a.price_eth == None: continue if a.volume_eth == None: # use 0 eth as fallback so it does not affect weighted price volume = 0 else: volume = a.volume_eth if exchange_name == 'aggregate' or a.exchange_name == exchange_name: result.add(a.price_eth, volume) return result.average()
def _estimated_hashrate_n_days(self, days): # MUST CALL UPDATE() BEFORE THIS FUNCTION eth_blocks_in_window = int(days * 60 * 60 * 24 / SECONDS_PER_ETH_BLOCK) eth_block_at_start = self._current_eth_block - eth_blocks_in_window epoch_at_start = self._contract.functions.epochCount().call( block_identifier=-eth_blocks_in_window) #epoch_at_start = self._w3.toInt(self._w3.eth.getStorageAt(self.address, 0x7, eth_block_at_start)) epochs_in_window = self._epoch_count - epoch_at_start epochs_per_eth_block = epochs_in_window / eth_blocks_in_window epochs_per_second = epochs_per_eth_block / SECONDS_PER_ETH_BLOCK seconds_per_reward = 1 / epochs_per_second # if current diff started before beginning of the window, the math is # simple - one difficulty only if self.last_difficulty_start_block <= eth_block_at_start: estimated_hashrate_24h = self.difficulty * 2**22 / seconds_per_reward else: # difficulty changed within the window - so calculation must # consider multiple difficulties previous_mining_target = self._contract.functions.getMiningTarget( ).call(block_identifier=self.last_difficulty_start_block - 1) previous_difficulty = int(self.max_target / previous_mining_target) # load the eth block where the difficulty changed to the *previous* # difficulty. If it is inside the window, exit completely, because # it means the window contains 3 or more difficulties. eth_block_two_readjustments_ago = self._contract.functions.latestDifficultyPeriodStarted( ).call(block_identifier=self.last_difficulty_start_block - 1) if eth_block_two_readjustments_ago >= eth_block_at_start: raise RuntimeError( "Average window too large: this function only supports at most two difficulty periods." ) from weighted_average import WeightedAverage wa = WeightedAverage() # add hashrate based on current difficulty weighted by how many eth # blocks occured during that difficulty wa.add(self.difficulty * 2**22 / seconds_per_reward, self._current_eth_block - self.last_difficulty_start_block) # add hashrate based on last difficulty weighted by how many eth # blocks occured during that difficulty wa.add(previous_difficulty * 2**22 / seconds_per_reward, self.last_difficulty_start_block - eth_block_at_start) estimated_hashrate_24h = wa.average() return estimated_hashrate_24h
async def _update(self, timeout=10.0): total_liquidity_eth = 0 total_liquidity_dai = 0 total_liquidity_tokens = 0 price_eth_avg = WeightedAverage() price_dai_avg = WeightedAverage() for exchange_address in self._exchange_addresses: liquidity_eth, liquidity_dai, liquidity_tokens, price_eth, price_dai = await self._get_liquidity_and_price_at_pool_addr( exchange_address) price_eth_avg.add(price_eth, liquidity_eth) price_dai_avg.add(price_dai, liquidity_dai) total_liquidity_eth += liquidity_eth total_liquidity_dai += liquidity_dai total_liquidity_tokens += liquidity_tokens if total_liquidity_tokens == 0: raise NoLiquidityException("Pool has no liquidity") self.liquidity_dai = total_liquidity_dai self.liquidity_eth = total_liquidity_eth self.liquidity_tokens = total_liquidity_tokens self.price_eth = price_eth_avg.average() self.price_usd = price_dai_avg.average( ) # note: this is usually 0 because there are no dai pairs defined on balancer # update volume once every hour since it (potentially) loads eth api if time.time() - self._time_volume_last_updated > 60 * 60: try: await self._update_volume() except requests.exceptions.ReadTimeout: logging.warning( f"Failed to update QuickSwapAPI volume: ReadTimeout") self._time_volume_last_updated = time.time()
async def _update(self, timeout=10.0): eth_prices = [ get_price(self._w3, "DAI", "WETH"), get_price(self._w3, "USDT", "WETH"), get_price(self._w3, "USDC", "WETH"), ] # TODO: weighted average would be better than a simple average self.eth_price_usd = sum(eth_prices) / len(eth_prices) # matic_price_eth = get_price(self._w3, "WETH", "WMATIC") # self.matic_price_usd = matic_price_eth * self.eth_price_usd # swam_price_eth = get_price(self._w3, "WETH", "SWAM") # self.swam_price_usd = swam_price_eth * self.eth_price_usd total_liquidity_tokens = 0 price_usd_weighted_average = WeightedAverage() # check each token that <self.currency_symbol> is paired with for exchange_contract in self._exchanges: token0_address = exchange_contract.functions.token0().call().lower( ) token1_address = exchange_contract.functions.token1().call().lower( ) paired_token_address = token0_address if token1_address.lower( ) == Token().from_symbol( self.currency_symbol).address.lower() else token1_address try: paired_token_symbol = Token().from_address( paired_token_address).symbol except NoTokenMatchError: logging.warning( f"no token with address {paired_token_address} found (need to edit token_class.py); skipping" ) continue try: liquidity_tokens, liquidity_pair = get_reserves( self._w3, self.currency_symbol, paired_token_symbol) except PairNotDefinedError: logging.warning( f"pair {self.currency_symbol}-{paired_token_symbol} not found; skipping" ) continue if liquidity_tokens < 0.001: continue total_liquidity_tokens += liquidity_tokens if paired_token_symbol == "WETH": self.price_eth = get_price(self._w3, paired_token_symbol, self.currency_symbol) price_usd_weighted_average.add( self.price_eth * self.eth_price_usd, liquidity_tokens) self.liquidity_eth = liquidity_pair else: # get the paired token's price in Eth. If there is less than $500 in # liquidity to determine this, then skip this pair when determining price. try: liquidity_eth, _ = get_reserves(self._w3, "WETH", paired_token_symbol) except PairNotDefinedError: logging.warning( f"pair WETH-{paired_token_symbol} not found; skipping") continue if liquidity_eth < 500 / self.eth_price_usd: continue paired_token_price_in_eth = get_price(self._w3, "WETH", paired_token_symbol) paired_token_price_in_usd = paired_token_price_in_eth * self.eth_price_usd # get the price <self.currency_symbol> in terms of the paired token price_in_paired_token = get_price(self._w3, paired_token_symbol, self.currency_symbol) price_usd_weighted_average.add( price_in_paired_token * paired_token_price_in_usd, liquidity_tokens) self.liquidity_tokens = total_liquidity_tokens self.price_usd = price_usd_weighted_average.average() try: self.price_eth = get_price(self._w3, "WETH", self.currency_symbol) except PairNotDefinedError: logging.warning( f"Failed to get WETH pair for {self.currency_symbol}; calculating backwards using average USD price" ) self.price_eth = self.price_usd / self.eth_price_usd self.volume_tokens = await self._update_24h_volume() self.volume_eth = self.volume_tokens * self.price_eth
async def _update_all_values(self, timeout=10.0, should_update_volume=False): if should_update_volume: current_eth_block = self._w3.eth.blockNumber self.price_eth = None eth_prices = [ get_price(self._uniswap_api, "DAI", "WETH", _DEFAULT_PAIR_FEE), get_price(self._uniswap_api, "USDT", "WETH", _DEFAULT_PAIR_FEE), get_price(self._uniswap_api, "USDC", "WETH", _DEFAULT_PAIR_FEE), ] self.eth_price_usd = sum(eth_prices) / len( eth_prices) # TODO: should be weighted average price_usd_weighted_average = WeightedAverage() total_liquidity_tokens = 0 total_volume_tokens = 0 for exchange_address in getExchangeAddressesForToken( self.currency_symbol): token0_name, token1_name, fee = getTokensFromExchangeAddress( exchange_address) token0_address = Token().from_symbol(token0_name).address token1_address = Token().from_symbol(token1_name).address #paired_token_address = token0_address if token1_address.lower() == Token().from_symbol(self.currency_symbol).address.lower() else token1_address #paired_token_symbol = Token().from_address(paired_token_address).symbol try: price_usd, liquidity_tokens = await self._get_price_and_liquidity_for_pair( token0_address, token1_address, fee) except (NoTokenMatchError, PairNotDefinedError) as e: logging.warning( f"Failed to update {self.exchange_name} pair: {str(e)}") continue except NoLiquidityException: # no liquidity is not an error; simply skip this exchange continue else: price_usd_weighted_average.add(price_usd, liquidity_tokens) total_liquidity_tokens += liquidity_tokens if should_update_volume and liquidity_tokens > _MINIMUM_ALLOWED_LIQUIDITY_TOKENS_TO_CHECK_VOLUME: try: volume_tokens, volume_pair = await self._get_volume_for_pair( token0_address, token1_address, fee, current_eth_block=current_eth_block, timeout=timeout) total_volume_tokens += volume_tokens except requests.exceptions.ReadTimeout: logging.warning( f"Failed to update Uniswapv3API volume: ReadTimeout" ) self.price_usd = price_usd_weighted_average.average() self.price_eth = self.price_usd / self.eth_price_usd self.liquidity_tokens = total_liquidity_tokens self.liquidity_eth = self.liquidity_tokens * self.price_eth if should_update_volume: self.hourly_volume_tokens.append(total_volume_tokens) # trim list to 168 hours (7 days) self.hourly_volume_tokens = self.hourly_volume_tokens[-168:] # use last 24 hours for volume self.volume_tokens = sum(self.hourly_volume_tokens[-24:]) self.volume_eth = self.volume_tokens * self.price_eth # NOTE: this sets _time_volume_last_updated even if all volume updates # failed. This is OK for now, it throttles struggling APIs (matic) but # may not be the ideal behavior. self._mark_volume_as_updated()
async def _update(self, timeout=10.0): method = "/coin/{}".format(self.currency_symbol) data = await self._get_json_from_url(self._SERVER_URL+method) volume_usd = 0 volume_usd_eth = 0 volume_usd_btc = 0 wavg_eth_price_usd = WeightedAverage() wavg_btc_price_usd = WeightedAverage() wavg_price_eth = WeightedAverage() wavg_price_btc = WeightedAverage() wavg_price_usd = WeightedAverage() for exchange_data in data['data']: # skip reverse-pairings if exchange_data['base'] != self.currency_symbol: continue # last_price_in_usd = data['data'][0]['usd'] # last_price_in_eth = data['data'][0]['rate'] # last_eth_price = data['data'][0]['lastq'] base_pair = exchange_data['quote'] relative_volume = float(exchange_data['volumep']) # NOTE: this entire if statement is ONLY to collect price of eth and btc if base_pair == "ETH": # average price of base pairs, they are NOT all the same wavg_eth_price_usd.add(exchange_data['lastq'], relative_volume) elif (base_pair in ["AUD", "CAD", "CNY", "DAI", "EUR", "EURO", "GBP", "JPY", "KRW", "RUB", "USDT", "USD"]): # allow all fiat pairings to count towards volume pass elif (base_pair in ["BTC"]): # average price of base pairs, they are NOT all the same wavg_btc_price_usd.add(exchange_data['lastq'], relative_volume) # allow BTC pairings to count towards volume pass else: # if base pair is unknown, don't use for calcualted volume/price continue # only let allowed_apis to count toward price if self.allowed_apis == 'all' or exchange_data['exchange'] in self.allowed_apis: wavg_price_usd.add(exchange_data['usd'], relative_volume) volume_usd += exchange_data['volume'] if base_pair == "ETH": wavg_price_eth.add(exchange_data['rate'], relative_volume) volume_usd_eth += exchange_data['volume'] if base_pair == "BTC": wavg_price_btc.add(exchange_data['rate'], relative_volume) volume_usd_btc += exchange_data['volume'] self.price_usd = wavg_price_usd.average() self.eth_price_usd = wavg_eth_price_usd.average() self.btc_price_usd = wavg_btc_price_usd.average() if self.currency_symbol == "ETH": self.price_eth = 1 self.eth_price_usd = self.price_usd else: self.price_eth = wavg_price_eth.average() self.volume_usd = volume_usd if self.eth_price_usd != None and self.eth_price_usd != 0: # TODO: volume_eth should really represent quantity of volume in eth, # not quantity of all volume converted to price of eth. This # calculation includes btc in eth volume. self.volume_eth = volume_usd_eth / self.eth_price_usd if self.btc_price_usd != None and self.btc_price_usd != 0: # TODO: volume_eth should really represent quantity of volume in eth, # not quantity of all volume converted to price of eth. This # calculation includes btc in eth volume. self.volume_btc = volume_usd_btc / self.btc_price_usd if self.currency_symbol == "BTC": self.price_btc = 1 self.btc_price_usd = self.price_usd else: self.price_btc = wavg_price_btc.average()
def _update(self, timeout=10.0): method = "/coin/{}".format(self.currency_symbol) response = urlopen(self._SERVER_URL+method, timeout=timeout) response = response.read().decode("utf-8") try: data = json.loads(response) except json.decoder.JSONDecodeError: if "be right back" in response: raise TimeoutError("api is down - got 404 page") else: raise TimeoutError("api sent bad data ({})".format(repr(response))) volume_usd = 0 wavg_eth_price_usd = WeightedAverage() wavg_btc_price_usd = WeightedAverage() wavg_price_eth = WeightedAverage() wavg_price_usd = WeightedAverage() for exchange_data in data['data']: # skip reverse-pairings if exchange_data['base'] != self.currency_symbol: continue # last_price_in_usd = data['data'][0]['usd'] # last_price_in_eth = data['data'][0]['rate'] # last_eth_price = data['data'][0]['lastq'] base_pair = exchange_data['quote'] relative_volume = float(exchange_data['volumep']) # NOTE: this entire if statement is ONLY to collect price of eth and btc if base_pair == "ETH": # average price of base pairs, they are NOT all the same wavg_eth_price_usd.add(exchange_data['lastq'], relative_volume) wavg_price_eth.add(exchange_data['rate'], relative_volume) elif (base_pair in ["AUD", "CAD", "CNY", "DAI", "EUR", "EURO", "GBP", "JPY", "KRW", "RUB", "USDT", "USD"]): # allow all fiat pairings to count towards volume pass elif (base_pair in ["BTC"]): # average price of base pairs, they are NOT all the same wavg_btc_price_usd.add(exchange_data['lastq'], relative_volume) # allow BTC pairings to count towards volume pass else: #pprint.pprint(exchange_data) logging.debug('Unknown base_pair {}'.format(base_pair)) # if base pair is unknown, don't use for calcualted volume/price continue # only let allowed_apis to count toward price if self.allowed_apis == 'all' or exchange_data['exchange'] in self.allowed_apis: wavg_price_usd.add(exchange_data['usd'], relative_volume) volume_usd += exchange_data['volume'] self.price_usd = wavg_price_usd.average() self.eth_price_usd = wavg_eth_price_usd.average() self.btc_price_usd = wavg_btc_price_usd.average() if self.currency_symbol == "ETH": self.price_eth = 1 self.eth_price_usd = self.price_usd else: self.price_eth = wavg_price_eth.average() self.volume_usd = volume_usd if self.eth_price_usd != None and self.eth_price_usd != 0: # TODO: volume_eth should really represent quantity of volume in eth, # not quantity of all volume converted to price of eth. This # calculation includes btc in eth volume. self.volume_eth = self.volume_usd / self.eth_price_usd if self.currency_symbol == "BTC": self.btc_price_usd = self.price_usd