Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
| """ | |
| Extended Market Data Collectors | |
| Fetches data from Coinpaprika, DefiLlama, Messari, CoinCap, and other market data sources | |
| """ | |
| import asyncio | |
| from datetime import datetime, timezone | |
| from typing import Dict, List, Optional, Any | |
| from utils.api_client import get_client | |
| from utils.logger import setup_logger, log_api_request, log_error | |
| logger = setup_logger("market_data_extended_collector") | |
| async def get_coinpaprika_tickers() -> Dict[str, Any]: | |
| """ | |
| Fetch ticker data from Coinpaprika (free, no key required) | |
| Returns: | |
| Dict with provider, category, data, timestamp, success, error | |
| """ | |
| provider = "Coinpaprika" | |
| category = "market_data" | |
| endpoint = "/tickers" | |
| logger.info(f"Fetching tickers from {provider}") | |
| try: | |
| client = get_client() | |
| # Coinpaprika API (free, no key needed) | |
| url = "https://api.coinpaprika.com/v1/tickers" | |
| params = { | |
| "quotes": "USD", | |
| "limit": 100 | |
| } | |
| # Make request | |
| response = await client.get(url, params=params, timeout=15) | |
| # Log request | |
| log_api_request( | |
| logger, | |
| provider, | |
| endpoint, | |
| response.get("response_time_ms", 0), | |
| "success" if response["success"] else "error", | |
| response.get("status_code") | |
| ) | |
| if not response["success"]: | |
| error_msg = response.get("error_message", "Unknown error") | |
| log_error(logger, provider, response.get("error_type", "unknown"), error_msg, endpoint) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": response.get("error_type") | |
| } | |
| # Extract data | |
| data = response["data"] | |
| # Process top coins | |
| market_data = None | |
| if isinstance(data, list): | |
| top_10 = data[:10] | |
| total_market_cap = sum(coin.get("quotes", {}).get("USD", {}).get("market_cap", 0) for coin in top_10) | |
| market_data = { | |
| "total_coins": len(data), | |
| "top_10_market_cap": round(total_market_cap, 2), | |
| "top_10_coins": [ | |
| { | |
| "symbol": coin.get("symbol"), | |
| "name": coin.get("name"), | |
| "price": coin.get("quotes", {}).get("USD", {}).get("price"), | |
| "market_cap": coin.get("quotes", {}).get("USD", {}).get("market_cap"), | |
| "volume_24h": coin.get("quotes", {}).get("USD", {}).get("volume_24h"), | |
| "percent_change_24h": coin.get("quotes", {}).get("USD", {}).get("percent_change_24h") | |
| } | |
| for coin in top_10 | |
| ] | |
| } | |
| logger.info(f"{provider} - {endpoint} - Retrieved {len(data) if isinstance(data, list) else 0} tickers") | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": market_data, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": True, | |
| "error": None, | |
| "response_time_ms": response.get("response_time_ms", 0) | |
| } | |
| except Exception as e: | |
| error_msg = f"Unexpected error: {str(e)}" | |
| log_error(logger, provider, "exception", error_msg, endpoint, exc_info=True) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": "exception" | |
| } | |
| async def get_defillama_tvl() -> Dict[str, Any]: | |
| """ | |
| Fetch DeFi Total Value Locked from DefiLlama (free, no key required) | |
| Returns: | |
| Dict with provider, category, data, timestamp, success, error | |
| """ | |
| provider = "DefiLlama" | |
| category = "defi_data" | |
| endpoint = "/tvl" | |
| logger.info(f"Fetching TVL data from {provider}") | |
| try: | |
| client = get_client() | |
| # DefiLlama API (free, no key needed) | |
| url = "https://api.llama.fi/v2/protocols" | |
| # Make request | |
| response = await client.get(url, timeout=15) | |
| # Log request | |
| log_api_request( | |
| logger, | |
| provider, | |
| endpoint, | |
| response.get("response_time_ms", 0), | |
| "success" if response["success"] else "error", | |
| response.get("status_code") | |
| ) | |
| if not response["success"]: | |
| error_msg = response.get("error_message", "Unknown error") | |
| log_error(logger, provider, response.get("error_type", "unknown"), error_msg, endpoint) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": response.get("error_type") | |
| } | |
| # Extract data | |
| data = response["data"] | |
| # Process protocols | |
| tvl_data = None | |
| if isinstance(data, list): | |
| # Sort by TVL | |
| sorted_protocols = sorted(data, key=lambda x: x.get("tvl", 0), reverse=True) | |
| top_20 = sorted_protocols[:20] | |
| total_tvl = sum(p.get("tvl", 0) for p in data) | |
| tvl_data = { | |
| "total_protocols": len(data), | |
| "total_tvl": round(total_tvl, 2), | |
| "top_20_protocols": [ | |
| { | |
| "name": p.get("name"), | |
| "symbol": p.get("symbol"), | |
| "tvl": round(p.get("tvl", 0), 2), | |
| "change_1d": p.get("change_1d"), | |
| "change_7d": p.get("change_7d"), | |
| "chains": p.get("chains", [])[:3] # Top 3 chains | |
| } | |
| for p in top_20 | |
| ] | |
| } | |
| logger.info( | |
| f"{provider} - {endpoint} - Total TVL: ${tvl_data.get('total_tvl', 0):,.0f}" | |
| if tvl_data else f"{provider} - {endpoint} - No data" | |
| ) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": tvl_data, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": True, | |
| "error": None, | |
| "response_time_ms": response.get("response_time_ms", 0) | |
| } | |
| except Exception as e: | |
| error_msg = f"Unexpected error: {str(e)}" | |
| log_error(logger, provider, "exception", error_msg, endpoint, exc_info=True) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": "exception" | |
| } | |
| async def get_coincap_assets() -> Dict[str, Any]: | |
| """ | |
| Fetch asset data from CoinCap (free, no key required) | |
| Returns: | |
| Dict with provider, category, data, timestamp, success, error | |
| """ | |
| provider = "CoinCap" | |
| category = "market_data" | |
| endpoint = "/assets" | |
| logger.info(f"Fetching assets from {provider}") | |
| try: | |
| client = get_client() | |
| # CoinCap API (free, no key needed) | |
| url = "https://api.coincap.io/v2/assets" | |
| params = {"limit": 50} | |
| # Make request | |
| response = await client.get(url, params=params, timeout=10) | |
| # Log request | |
| log_api_request( | |
| logger, | |
| provider, | |
| endpoint, | |
| response.get("response_time_ms", 0), | |
| "success" if response["success"] else "error", | |
| response.get("status_code") | |
| ) | |
| if not response["success"]: | |
| error_msg = response.get("error_message", "Unknown error") | |
| log_error(logger, provider, response.get("error_type", "unknown"), error_msg, endpoint) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": response.get("error_type") | |
| } | |
| # Extract data | |
| raw_data = response["data"] | |
| # Process assets | |
| asset_data = None | |
| if isinstance(raw_data, dict) and "data" in raw_data: | |
| assets = raw_data["data"] | |
| top_10 = assets[:10] if isinstance(assets, list) else [] | |
| asset_data = { | |
| "total_assets": len(assets) if isinstance(assets, list) else 0, | |
| "top_10_assets": [ | |
| { | |
| "symbol": asset.get("symbol"), | |
| "name": asset.get("name"), | |
| "price_usd": float(asset.get("priceUsd", 0)), | |
| "market_cap_usd": float(asset.get("marketCapUsd", 0)), | |
| "volume_24h_usd": float(asset.get("volumeUsd24Hr", 0)), | |
| "change_percent_24h": float(asset.get("changePercent24Hr", 0)) | |
| } | |
| for asset in top_10 | |
| ] | |
| } | |
| logger.info(f"{provider} - {endpoint} - Retrieved {asset_data.get('total_assets', 0)} assets") | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": asset_data, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": True, | |
| "error": None, | |
| "response_time_ms": response.get("response_time_ms", 0) | |
| } | |
| except Exception as e: | |
| error_msg = f"Unexpected error: {str(e)}" | |
| log_error(logger, provider, "exception", error_msg, endpoint, exc_info=True) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": "exception" | |
| } | |
| async def get_messari_assets(api_key: Optional[str] = None) -> Dict[str, Any]: | |
| """ | |
| Fetch asset data from Messari | |
| Args: | |
| api_key: Messari API key (optional, has free tier) | |
| Returns: | |
| Dict with provider, category, data, timestamp, success, error | |
| """ | |
| provider = "Messari" | |
| category = "market_data" | |
| endpoint = "/assets" | |
| logger.info(f"Fetching assets from {provider}") | |
| try: | |
| client = get_client() | |
| # Messari API | |
| url = "https://data.messari.io/api/v1/assets" | |
| params = {"limit": 20} | |
| headers = {} | |
| if api_key: | |
| headers["x-messari-api-key"] = api_key | |
| # Make request | |
| response = await client.get(url, params=params, headers=headers, timeout=15) | |
| # Log request | |
| log_api_request( | |
| logger, | |
| provider, | |
| endpoint, | |
| response.get("response_time_ms", 0), | |
| "success" if response["success"] else "error", | |
| response.get("status_code") | |
| ) | |
| if not response["success"]: | |
| error_msg = response.get("error_message", "Unknown error") | |
| log_error(logger, provider, response.get("error_type", "unknown"), error_msg, endpoint) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": response.get("error_type") | |
| } | |
| # Extract data | |
| raw_data = response["data"] | |
| # Process assets | |
| asset_data = None | |
| if isinstance(raw_data, dict) and "data" in raw_data: | |
| assets = raw_data["data"] | |
| asset_data = { | |
| "total_assets": len(assets) if isinstance(assets, list) else 0, | |
| "assets": [ | |
| { | |
| "symbol": asset.get("symbol"), | |
| "name": asset.get("name"), | |
| "slug": asset.get("slug"), | |
| "metrics": { | |
| "market_cap": asset.get("metrics", {}).get("marketcap", {}).get("current_marketcap_usd"), | |
| "volume_24h": asset.get("metrics", {}).get("market_data", {}).get("volume_last_24_hours"), | |
| "price": asset.get("metrics", {}).get("market_data", {}).get("price_usd") | |
| } | |
| } | |
| for asset in assets[:10] | |
| ] if isinstance(assets, list) else [] | |
| } | |
| logger.info(f"{provider} - {endpoint} - Retrieved {asset_data.get('total_assets', 0)} assets") | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": asset_data, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": True, | |
| "error": None, | |
| "response_time_ms": response.get("response_time_ms", 0) | |
| } | |
| except Exception as e: | |
| error_msg = f"Unexpected error: {str(e)}" | |
| log_error(logger, provider, "exception", error_msg, endpoint, exc_info=True) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": "exception" | |
| } | |
| async def get_cryptocompare_toplist() -> Dict[str, Any]: | |
| """ | |
| Fetch top cryptocurrencies from CryptoCompare (free tier available) | |
| Returns: | |
| Dict with provider, category, data, timestamp, success, error | |
| """ | |
| provider = "CryptoCompare" | |
| category = "market_data" | |
| endpoint = "/top/totalvolfull" | |
| logger.info(f"Fetching top list from {provider}") | |
| try: | |
| client = get_client() | |
| # CryptoCompare API | |
| url = "https://min-api.cryptocompare.com/data/top/totalvolfull" | |
| params = { | |
| "limit": 20, | |
| "tsym": "USD" | |
| } | |
| # Make request | |
| response = await client.get(url, params=params, timeout=10) | |
| # Log request | |
| log_api_request( | |
| logger, | |
| provider, | |
| endpoint, | |
| response.get("response_time_ms", 0), | |
| "success" if response["success"] else "error", | |
| response.get("status_code") | |
| ) | |
| if not response["success"]: | |
| error_msg = response.get("error_message", "Unknown error") | |
| log_error(logger, provider, response.get("error_type", "unknown"), error_msg, endpoint) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": response.get("error_type") | |
| } | |
| # Extract data | |
| raw_data = response["data"] | |
| # Process data | |
| toplist_data = None | |
| if isinstance(raw_data, dict) and "Data" in raw_data: | |
| coins = raw_data["Data"] | |
| toplist_data = { | |
| "total_coins": len(coins) if isinstance(coins, list) else 0, | |
| "top_coins": [ | |
| { | |
| "symbol": coin.get("CoinInfo", {}).get("Name"), | |
| "name": coin.get("CoinInfo", {}).get("FullName"), | |
| "price": coin.get("RAW", {}).get("USD", {}).get("PRICE"), | |
| "market_cap": coin.get("RAW", {}).get("USD", {}).get("MKTCAP"), | |
| "volume_24h": coin.get("RAW", {}).get("USD", {}).get("VOLUME24HOUR"), | |
| "change_24h": coin.get("RAW", {}).get("USD", {}).get("CHANGEPCT24HOUR") | |
| } | |
| for coin in (coins[:10] if isinstance(coins, list) else []) | |
| ] | |
| } | |
| logger.info(f"{provider} - {endpoint} - Retrieved {toplist_data.get('total_coins', 0)} coins") | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": toplist_data, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": True, | |
| "error": None, | |
| "response_time_ms": response.get("response_time_ms", 0) | |
| } | |
| except Exception as e: | |
| error_msg = f"Unexpected error: {str(e)}" | |
| log_error(logger, provider, "exception", error_msg, endpoint, exc_info=True) | |
| return { | |
| "provider": provider, | |
| "category": category, | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": error_msg, | |
| "error_type": "exception" | |
| } | |
| async def collect_extended_market_data(messari_key: Optional[str] = None) -> List[Dict[str, Any]]: | |
| """ | |
| Main function to collect extended market data from all sources | |
| Args: | |
| messari_key: Optional Messari API key | |
| Returns: | |
| List of results from all extended market data collectors | |
| """ | |
| logger.info("Starting extended market data collection from all sources") | |
| # Run all collectors concurrently | |
| results = await asyncio.gather( | |
| get_coinpaprika_tickers(), | |
| get_defillama_tvl(), | |
| get_coincap_assets(), | |
| get_messari_assets(messari_key), | |
| get_cryptocompare_toplist(), | |
| return_exceptions=True | |
| ) | |
| # Process results | |
| processed_results = [] | |
| for result in results: | |
| if isinstance(result, Exception): | |
| logger.error(f"Collector failed with exception: {str(result)}") | |
| processed_results.append({ | |
| "provider": "Unknown", | |
| "category": "market_data", | |
| "data": None, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "success": False, | |
| "error": str(result), | |
| "error_type": "exception" | |
| }) | |
| else: | |
| processed_results.append(result) | |
| # Log summary | |
| successful = sum(1 for r in processed_results if r.get("success", False)) | |
| logger.info(f"Extended market data collection complete: {successful}/{len(processed_results)} successful") | |
| return processed_results | |
| # Example usage | |
| if __name__ == "__main__": | |
| async def main(): | |
| import os | |
| messari_key = os.getenv("MESSARI_API_KEY") | |
| results = await collect_extended_market_data(messari_key) | |
| print("\n=== Extended Market Data Collection Results ===") | |
| for result in results: | |
| print(f"\nProvider: {result['provider']}") | |
| print(f"Category: {result['category']}") | |
| print(f"Success: {result['success']}") | |
| if result['success']: | |
| print(f"Response Time: {result.get('response_time_ms', 0):.2f}ms") | |
| data = result.get('data', {}) | |
| if data: | |
| if 'total_tvl' in data: | |
| print(f"Total TVL: ${data['total_tvl']:,.0f}") | |
| elif 'total_assets' in data: | |
| print(f"Total Assets: {data['total_assets']}") | |
| elif 'total_coins' in data: | |
| print(f"Total Coins: {data['total_coins']}") | |
| else: | |
| print(f"Error: {result.get('error', 'Unknown')}") | |
| asyncio.run(main()) | |