""" Fetches trending coins, global market stats, and price data from CoinGecko's free public API (no key required for these endpoints). """ import logging import time import requests from typing import Any from config import ( COINGECKO_TRENDING_URL, COINGECKO_GLOBAL_URL, COINGECKO_COINS_URL, TOP_COINS_TO_TRACK, ) logger = logging.getLogger(__name__) _HEADERS = {"accept": "application/json"} _RATE_LIMIT_PAUSE = 1.2 # seconds between calls to stay within free tier def _get(url: str, params: dict | None = None) -> Any: time.sleep(_RATE_LIMIT_PAUSE) resp = requests.get(url, headers=_HEADERS, params=params, timeout=15) resp.raise_for_status() return resp.json() class CoinGeckoCollector: def fetch_trending(self) -> list[dict]: """Returns top trending coins on CoinGecko (last 24 h).""" try: data = _get(COINGECKO_TRENDING_URL) coins = data.get("coins", []) return [ { "id": c["item"]["id"], "name": c["item"]["name"], "symbol": c["item"]["symbol"].upper(), "rank": c["item"].get("market_cap_rank"), "score": c["item"].get("score", 0), } for c in coins ] except Exception as exc: logger.warning(f"[CoinGecko] Trending fetch failed: {exc}") return [] def fetch_global(self) -> dict: """Returns global crypto market stats.""" try: data = _get(COINGECKO_GLOBAL_URL) d = data.get("data", {}) return { "total_market_cap_usd": d.get("total_market_cap", {}).get("usd"), "total_volume_usd": d.get("total_volume", {}).get("usd"), "btc_dominance": round(d.get("market_cap_percentage", {}).get("btc", 0), 2), "eth_dominance": round(d.get("market_cap_percentage", {}).get("eth", 0), 2), "active_cryptocurrencies": d.get("active_cryptocurrencies"), "market_cap_change_24h": d.get("market_cap_change_percentage_24h_usd"), } except Exception as exc: logger.warning(f"[CoinGecko] Global fetch failed: {exc}") return {} def fetch_prices(self, coin_ids: list[str] | None = None) -> list[dict]: """Returns price / market data for tracked coins.""" ids = coin_ids or TOP_COINS_TO_TRACK try: data = _get( COINGECKO_COINS_URL, params={ "vs_currency": "usd", "ids": ",".join(ids), "order": "market_cap_desc", "per_page": len(ids), "page": 1, "sparkline": False, "price_change_percentage": "24h,7d", }, ) return [ { "id": c.get("id"), "symbol": c.get("symbol", "").upper(), "name": c.get("name"), "price_usd": c.get("current_price"), "market_cap": c.get("market_cap"), "volume_24h": c.get("total_volume"), "change_24h": c.get("price_change_percentage_24h"), "change_7d": c.get("price_change_percentage_7d_in_currency"), "ath_change": c.get("ath_change_percentage"), } for c in data ] except Exception as exc: logger.warning(f"[CoinGecko] Prices fetch failed: {exc}") return [] def get_trending_ids(self) -> set[str]: """Returns a set of currently trending coin IDs for scoring bonuses.""" return {c["id"] for c in self.fetch_trending()}