import requests import logging from typing import Optional, Dict, Any logger = logging.getLogger(__name__) class CoinGeckoAPI: """CoinGecko implementation for cryptocurrency price data.""" BASE_URL = "https://api.coingecko.com/api/v3" # Common crypto symbol to CoinGecko ID mapping SYMBOL_MAP = { 'BTC': 'bitcoin', 'ETH': 'ethereum', 'USDT': 'tether', 'BNB': 'binancecoin', 'SOL': 'solana', 'XRP': 'ripple', 'USDC': 'usd-coin', 'ADA': 'cardano', 'DOGE': 'dogecoin', 'TRX': 'tron', 'DOT': 'polkadot', 'MATIC': 'matic-network', 'LTC': 'litecoin', 'SHIB': 'shiba-inu', 'AVAX': 'avalanche-2', 'LINK': 'chainlink', 'UNI': 'uniswap', 'XLM': 'stellar', 'ATOM': 'cosmos', 'XMR': 'monero' } def get_crypto_price(self, symbol: str) -> Optional[Dict[str, Any]]: """ Retrieve cryptocurrency price data from CoinGecko. Args: symbol: Crypto symbol (e.g., BTC, ETH, DOGE) Returns: Dictionary with crypto data or None if unavailable """ try: # Convert symbol to CoinGecko ID symbol = symbol.upper() coin_id = self.SYMBOL_MAP.get(symbol, symbol.lower()) # Fetch price data url = f"{self.BASE_URL}/simple/price" params = { 'ids': coin_id, 'vs_currencies': 'usd', 'include_24hr_change': 'true', 'include_24hr_vol': 'true', 'include_last_updated_at': 'true' } response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() if coin_id not in data: logger.warning(f"No data available for crypto: {symbol}") return None crypto_data = data[coin_id] current_price = crypto_data.get('usd', 0) change_percent = crypto_data.get('usd_24h_change', 0) if current_price == 0: logger.warning(f"Invalid price data for crypto: {symbol}") return None # Calculate previous price from 24h change change_decimal = change_percent / 100 previous_price = current_price / (1 + change_decimal) change_dollar = current_price - previous_price return { 'symbol': symbol, 'coin_id': coin_id, 'current_price': round(current_price, 2), 'previous_price': round(previous_price, 2), 'change_dollar': round(change_dollar, 2), 'change_percent': round(change_percent, 2), 'volume_24h': crypto_data.get('usd_24h_vol', 0) } except requests.exceptions.RequestException as e: logger.error(f"Error fetching data for {symbol}: {e}") return None except Exception as e: logger.error(f"Unexpected error fetching data for {symbol}: {e}") return None def is_available(self) -> bool: """ Check if CoinGecko API is accessible. Returns: True if accessible, False otherwise """ try: url = f"{self.BASE_URL}/ping" response = requests.get(url, timeout=5) return response.status_code == 200 except Exception as e: logger.error(f"CoinGecko API unavailable: {e}") return False