Spaces:
Running
Running
File size: 6,507 Bytes
97873ec 76f2683 fcfadb5 97873ec fd31489 476cee0 c4fd2ca fd31489 c4fd2ca fd31489 19f368e fd31489 7731384 76f2683 fcfadb5 76f2683 fd31489 7731384 c4fd2ca fcfadb5 431e338 19f368e fd31489 76f2683 19f368e 476cee0 fcfadb5 476cee0 fcfadb5 c4fd2ca fcfadb5 431e338 fcfadb5 431e338 fcfadb5 19f368e 431e338 c4fd2ca fcfadb5 c4fd2ca fcfadb5 c4fd2ca fcfadb5 431e338 76f2683 fcfadb5 c4fd2ca 76f2683 c4fd2ca 76f2683 fcfadb5 431e338 76f2683 431e338 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
"""
A professional-grade, multi-asset, multi-oracle price engine.
v13.0 FINAL: This version uses FOUR independent data sources and calculates a
resilient median price to be immune to single-source API failures. This is the
most robust and definitive version.
"""
import asyncio
import logging
from typing import Dict, Optional, List
import httpx
import statistics
logger = logging.getLogger(__name__)
# --- CONFIGURATION ---
ASSET_CONFIG = {
"BTC": {"pyth_id": "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415B43", "coingecko_id": "bitcoin", "coincap_id": "bitcoin", "cryptocompare_id": "BTC"},
"ETH": {"pyth_id": "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", "coingecko_id": "ethereum", "coincap_id": "ethereum", "cryptocompare_id": "ETH"},
"SOL": {"pyth_id": "ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", "coingecko_id": "solana", "coincap_id": "solana", "cryptocompare_id": "SOL"},
"XRP": {"pyth_id": "02a01e69981d314fd8a723be08253181e53b4945b4bf376d15a51980a37330c3", "coingecko_id": "ripple", "coincap_id": "xrp", "cryptocompare_id": "XRP"},
"DOGE": {"pyth_id": "042f02faf4229babc62635593855b6a383d6a4a2a1b9b9a7c385a4a50b86a345", "coingecko_id": "dogecoin", "coincap_id": "dogecoin", "cryptocompare_id": "DOGE"},
"ADA": {"pyth_id": "34f544985c7943c093b5934963505a767f4749445244a852654c6017b28091ea", "coingecko_id": "cardano", "coincap_id": "cardano", "cryptocompare_id": "ADA"},
"AVAX": {"pyth_id": "0x141f2a3c34c8035443a01d64380b52207991b16c14c5145f617eb578a994753c", "coingecko_id": "avalanche-2", "coincap_id": "avalanche", "cryptocompare_id": "AVAX"},
"LINK": {"pyth_id": "0x63f4f4755a5a67c64c781d45763b33a72666a15e6b91c0fbdf3b2f205d5a6b01", "coingecko_id": "chainlink", "coincap_id": "chainlink", "cryptocompare_id": "LINK"},
"DOT": {"pyth_id": "0x00a896677493a74421b33362a7447785b13612f0e340d418641a33716a5067a3", "coingecko_id": "polkadot", "coincap_id": "polkadot", "cryptocompare_id": "DOT"},
"MATIC": {"pyth_id": "0x737ac3c13709b45da8128ff9e1058a984f86a048035656111b8a365e4921648a", "coingecko_id": "matic-network", "coincap_id": "polygon", "cryptocompare_id": "MATIC"},
}
class PriceFetcher:
PYTH_URL = "https://hermes.pyth.network/v2/price_feeds"
COINGECKO_URL = "https://api.coingecko.com/api/v3/simple/price"
COINCAP_URL = "https://api.coincap.io/v2/assets"
CRYPTOCOMPARE_URL = "https://min-api.cryptocompare.com/data/pricemulti"
def __init__(self, client: httpx.AsyncClient):
self.client = client
self._prices: Dict[str, Dict[str, Optional[float]]] = {}
self._lock = asyncio.Lock()
async def _fetch_pyth(self, ids: List[str]) -> Dict[str, float]:
params = [("ids[]", f"0x{pid}") for pid in ids]
try:
resp = await self.client.get(self.PYTH_URL, params=params, timeout=10)
resp.raise_for_status()
id_map = {f"0x{v['pyth_id']}": k for k, v in ASSET_CONFIG.items()}
return {id_map[item['id']]: int(item['price']['price']) / (10 ** abs(int(item['price']['expo']))) for item in resp.json() if item['id'] in id_map}
except Exception as e:
logger.error(f"β Oracle Error (Pyth): {e}")
return {}
async def _fetch_coingecko(self, ids: List[str]) -> Dict[str, float]:
params = {"ids": ",".join(ids), "vs_currencies": "usd"}
try:
resp = await self.client.get(self.COINGECKO_URL, params=params, timeout=10)
resp.raise_for_status()
id_map = {v['coingecko_id']: k for k, v in ASSET_CONFIG.items()}
return {id_map[cg_id]: prices['usd'] for cg_id, prices in resp.json().items()}
except Exception as e:
logger.error(f"β Oracle Error (CoinGecko): {e}")
return {}
async def _fetch_coincap(self, ids: List[str]) -> Dict[str, float]:
# --- THE CRITICAL FIX IS HERE ---
# CoinCap's multi-asset endpoint does not use 'ids', it returns all. We filter.
try:
resp = await self.client.get(self.COINCAP_URL, params={"limit": 200}, timeout=10)
resp.raise_for_status()
id_map = {v['coincap_id']: k for k, v in ASSET_CONFIG.items()}
return {id_map[item['id']]: float(item['priceUsd']) for item in resp.json().get('data', []) if item['id'] in id_map}
except Exception as e:
logger.error(f"β Oracle Error (CoinCap): {e}")
return {}
async def _fetch_cryptocompare(self, ids: List[str]) -> Dict[str, float]:
params = {"fsyms": ",".join(ids), "tsyms": "USD"}
try:
resp = await self.client.get(self.CRYPTOCOMPARE_URL, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
id_map = {v['cryptocompare_id']: k for k, v in ASSET_CONFIG.items()}
return {id_map[cc_id]: price_data['USD'] for cc_id, price_data in data.items() if 'USD' in price_data}
except Exception as e:
logger.error(f"β Oracle Error (CryptoCompare): {e}")
return {}
async def update_prices_async(self):
# Prepare asset ID maps for each source
pyth_ids = [v['pyth_id'] for v in ASSET_CONFIG.values()]
coingecko_ids = [v['coingecko_id'] for v in ASSET_CONFIG.values()]
coincap_ids = [v['coincap_id'] for v in ASSET_CONFIG.values()]
cryptocompare_ids = [v['cryptocompare_id'] for v in ASSET_CONFIG.values()]
tasks = [
self._fetch_pyth(pyth_ids),
self._fetch_coingecko(coingecko_ids),
self._fetch_coincap(coincap_ids),
self._fetch_cryptocompare(cryptocompare_ids),
]
pyth_prices, coingecko_prices, coincap_prices, cryptocompare_prices = await asyncio.gather(*tasks)
async with self._lock:
for symbol in ASSET_CONFIG.keys():
valid_agg_prices = [p for p in [coingecko_prices.get(symbol), coincap_prices.get(symbol), cryptocompare_prices.get(symbol)] if p]
median_agg_price = statistics.median(valid_agg_prices) if valid_agg_prices else None
self._prices[symbol] = {
"pyth": pyth_prices.get(symbol),
"chainlink_agg": median_agg_price
}
logger.info("β
Multi-Source Prices Updated")
def get_all_prices(self) -> Dict[str, Dict[str, Optional[float]]]:
return self._prices.copy() |