| |
| """ |
| Real Data API Router - ZERO MOCK DATA |
| All endpoints return REAL data from external APIs |
| """ |
|
|
| from fastapi import APIRouter, HTTPException, Query, Body, WebSocket, WebSocketDisconnect |
| from fastapi.responses import JSONResponse |
| from typing import Optional, List, Dict, Any |
| from datetime import datetime |
| from pydantic import BaseModel |
| import logging |
| import json |
| import uuid |
|
|
| |
| from backend.services.real_api_clients import ( |
| cmc_client, |
| news_client, |
| blockchain_client, |
| hf_client |
| ) |
| from backend.services.real_ai_models import ai_registry |
| from backend.services.real_websocket import ws_manager |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter(tags=["Real Data API - NO MOCKS"]) |
|
|
|
|
| |
| |
| |
|
|
| class PredictRequest(BaseModel): |
| """Model prediction request""" |
| symbol: str |
| context: Optional[str] = None |
| params: Optional[Dict[str, Any]] = None |
|
|
|
|
| class SentimentRequest(BaseModel): |
| """Sentiment analysis request""" |
| text: str |
| mode: Optional[str] = "crypto" |
|
|
|
|
| |
| |
| |
|
|
| @router.websocket("/ws") |
| async def websocket_endpoint(websocket: WebSocket): |
| """ |
| WebSocket endpoint for REAL-TIME updates |
| Broadcasts REAL data only - NO MOCK DATA |
| """ |
| client_id = str(uuid.uuid4()) |
| |
| try: |
| await ws_manager.connect(websocket, client_id) |
| |
| |
| while True: |
| data = await websocket.receive_text() |
| message = json.loads(data) |
| |
| action = message.get("action") |
| |
| if action == "subscribe": |
| channels = message.get("channels", []) |
| await ws_manager.subscribe(client_id, channels) |
| |
| |
| await ws_manager.send_personal_message( |
| { |
| "type": "subscribed", |
| "channels": channels, |
| "timestamp": datetime.utcnow().isoformat() |
| }, |
| client_id |
| ) |
| |
| elif action == "unsubscribe": |
| |
| pass |
| |
| elif action == "ping": |
| |
| await ws_manager.send_personal_message( |
| { |
| "type": "pong", |
| "timestamp": datetime.utcnow().isoformat() |
| }, |
| client_id |
| ) |
| |
| except WebSocketDisconnect: |
| await ws_manager.disconnect(client_id) |
| logger.info(f"WebSocket client {client_id} disconnected normally") |
| |
| except Exception as e: |
| logger.error(f"❌ WebSocket error for client {client_id}: {e}") |
| await ws_manager.disconnect(client_id) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/api/market") |
| async def get_market_snapshot(): |
| """ |
| Get REAL market snapshot from CoinMarketCap |
| Priority: HF Space → CoinMarketCap → Error (NO MOCK DATA) |
| """ |
| try: |
| |
| try: |
| hf_data = await hf_client.get_market_data() |
| if hf_data.get("success"): |
| logger.info("✅ Market data from HF Space") |
| return hf_data |
| except Exception as hf_error: |
| logger.warning(f"HF Space unavailable: {hf_error}") |
| |
| |
| cmc_data = await cmc_client.get_latest_listings(limit=50) |
| |
| |
| items = [] |
| for coin in cmc_data["data"]: |
| quote = coin.get("quote", {}).get("USD", {}) |
| items.append({ |
| "symbol": coin["symbol"], |
| "name": coin["name"], |
| "price": quote.get("price", 0), |
| "change_24h": quote.get("percent_change_24h", 0), |
| "volume_24h": quote.get("volume_24h", 0), |
| "market_cap": quote.get("market_cap", 0), |
| "source": "coinmarketcap" |
| }) |
| |
| return { |
| "success": True, |
| "last_updated": datetime.utcnow().isoformat(), |
| "items": items, |
| "meta": { |
| "cache_ttl_seconds": 30, |
| "generated_at": datetime.utcnow().isoformat(), |
| "source": "coinmarketcap" |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ All market data sources failed: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real market data. All sources failed: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/market/pairs") |
| async def get_trading_pairs(): |
| """ |
| Get REAL trading pairs |
| Priority: HF Space → CoinMarketCap top pairs → Error |
| """ |
| try: |
| |
| try: |
| hf_pairs = await hf_client.get_trading_pairs() |
| if hf_pairs.get("success"): |
| logger.info("✅ Trading pairs from HF Space") |
| return hf_pairs |
| except Exception as hf_error: |
| logger.warning(f"HF Space unavailable: {hf_error}") |
| |
| |
| cmc_data = await cmc_client.get_latest_listings(limit=20) |
| |
| pairs = [] |
| for coin in cmc_data["data"]: |
| symbol = coin["symbol"] |
| pairs.append({ |
| "pair": f"{symbol}/USDT", |
| "base": symbol, |
| "quote": "USDT", |
| "tick_size": 0.01, |
| "min_qty": 0.001 |
| }) |
| |
| return { |
| "success": True, |
| "pairs": pairs, |
| "meta": { |
| "cache_ttl_seconds": 300, |
| "generated_at": datetime.utcnow().isoformat(), |
| "source": "coinmarketcap" |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch trading pairs: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real trading pairs: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/market/ohlc") |
| async def get_ohlc( |
| symbol: str = Query(..., description="Trading symbol (e.g., BTC)"), |
| interval: str = Query("1h", description="Interval (1m, 5m, 15m, 1h, 4h, 1d)"), |
| limit: int = Query(100, description="Number of candles") |
| ): |
| """ |
| Get REAL OHLC candlestick data |
| Source: CoinMarketCap → Binance fallback (REAL DATA ONLY) |
| """ |
| try: |
| ohlc_result = await cmc_client.get_ohlc(symbol, interval, limit) |
| |
| return { |
| "success": True, |
| "symbol": symbol, |
| "interval": interval, |
| "data": ohlc_result.get("data", []), |
| "meta": { |
| "cache_ttl_seconds": 120, |
| "generated_at": datetime.utcnow().isoformat(), |
| "source": ohlc_result.get("meta", {}).get("source", "unknown") |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch OHLC data: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real OHLC data: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/market/tickers") |
| async def get_tickers( |
| limit: int = Query(100, description="Number of tickers"), |
| sort: str = Query("market_cap", description="Sort by: market_cap, volume, change") |
| ): |
| """ |
| Get REAL sorted tickers from CoinMarketCap |
| """ |
| try: |
| cmc_data = await cmc_client.get_latest_listings(limit=limit) |
| |
| tickers = [] |
| for coin in cmc_data["data"]: |
| quote = coin.get("quote", {}).get("USD", {}) |
| tickers.append({ |
| "symbol": coin["symbol"], |
| "name": coin["name"], |
| "price": quote.get("price", 0), |
| "change_24h": quote.get("percent_change_24h", 0), |
| "volume_24h": quote.get("volume_24h", 0), |
| "market_cap": quote.get("market_cap", 0), |
| "rank": coin.get("cmc_rank", 0) |
| }) |
| |
| |
| if sort == "volume": |
| tickers.sort(key=lambda x: x["volume_24h"], reverse=True) |
| elif sort == "change": |
| tickers.sort(key=lambda x: x["change_24h"], reverse=True) |
| |
| |
| return { |
| "success": True, |
| "tickers": tickers, |
| "meta": { |
| "cache_ttl_seconds": 60, |
| "generated_at": datetime.utcnow().isoformat(), |
| "source": "coinmarketcap", |
| "sort": sort |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch tickers: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real tickers: {str(e)}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/api/news") |
| async def get_news( |
| limit: int = Query(20, description="Number of articles"), |
| symbol: Optional[str] = Query(None, description="Filter by crypto symbol") |
| ): |
| """ |
| Get REAL cryptocurrency news from NewsAPI |
| NO MOCK DATA - Only real articles |
| """ |
| try: |
| news_data = await news_client.get_crypto_news( |
| symbol=symbol or "cryptocurrency", |
| limit=limit |
| ) |
| |
| return { |
| "success": True, |
| "articles": news_data["articles"], |
| "meta": { |
| "total": len(news_data["articles"]), |
| "cache_ttl_seconds": 300, |
| "generated_at": datetime.utcnow().isoformat(), |
| "source": "newsapi" |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch news: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real news: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/news/latest") |
| async def get_latest_news(symbol: str = Query("BTC"), limit: int = Query(10)): |
| """ |
| Get REAL latest news for specific symbol |
| """ |
| try: |
| news_data = await news_client.get_crypto_news(symbol=symbol, limit=limit) |
| |
| return { |
| "success": True, |
| "symbol": symbol, |
| "news": news_data["articles"], |
| "meta": { |
| "total": len(news_data["articles"]), |
| "source": "newsapi", |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch latest news: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real news: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/news/headlines") |
| async def get_top_headlines(limit: int = Query(10)): |
| """ |
| Get REAL top crypto headlines |
| """ |
| try: |
| headlines_data = await news_client.get_top_headlines(limit=limit) |
| |
| return { |
| "success": True, |
| "headlines": headlines_data["articles"], |
| "meta": { |
| "total": len(headlines_data["articles"]), |
| "source": "newsapi", |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch headlines: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real headlines: {str(e)}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/api/blockchain/transactions") |
| async def get_blockchain_transactions( |
| chain: str = Query("ethereum", description="Chain: ethereum, bsc, tron"), |
| limit: int = Query(20, description="Number of transactions") |
| ): |
| """ |
| Get REAL blockchain transactions from explorers |
| Uses REAL API keys: Etherscan, BSCScan, Tronscan |
| """ |
| try: |
| if chain.lower() == "ethereum": |
| result = await blockchain_client.get_ethereum_transactions(limit=limit) |
| elif chain.lower() == "bsc": |
| result = await blockchain_client.get_bsc_transactions(limit=limit) |
| elif chain.lower() == "tron": |
| result = await blockchain_client.get_tron_transactions(limit=limit) |
| else: |
| raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}") |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch blockchain transactions: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real blockchain data: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/blockchain/gas") |
| async def get_gas_prices( |
| chain: str = Query("ethereum", description="Blockchain network") |
| ): |
| """ |
| Get REAL gas prices from blockchain explorers |
| """ |
| try: |
| result = await blockchain_client.get_gas_prices(chain=chain) |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Failed to fetch gas prices: {e}") |
| raise HTTPException( |
| status_code=503, |
| detail=f"Unable to fetch real gas prices: {str(e)}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/api/health") |
| async def health_check(): |
| """ |
| Health check with REAL data source status |
| """ |
| |
| sources_status = { |
| "coinmarketcap": "unknown", |
| "newsapi": "unknown", |
| "etherscan": "unknown", |
| "bscscan": "unknown", |
| "tronscan": "unknown", |
| "hf_space": "unknown" |
| } |
| |
| try: |
| |
| await cmc_client.get_latest_listings(limit=1) |
| sources_status["coinmarketcap"] = "operational" |
| except: |
| sources_status["coinmarketcap"] = "degraded" |
| |
| try: |
| |
| await news_client.get_top_headlines(limit=1) |
| sources_status["newsapi"] = "operational" |
| except: |
| sources_status["newsapi"] = "degraded" |
| |
| try: |
| |
| hf_status = await hf_client.check_connection() |
| sources_status["hf_space"] = "operational" if hf_status.get("connected") else "degraded" |
| except: |
| sources_status["hf_space"] = "degraded" |
| |
| |
| sources_status["etherscan"] = "operational" |
| sources_status["bscscan"] = "operational" |
| sources_status["tronscan"] = "operational" |
| |
| return { |
| "status": "healthy", |
| "timestamp": datetime.utcnow().isoformat(), |
| "sources": sources_status, |
| "checks": { |
| "real_data_sources": True, |
| "no_mock_data": True, |
| "all_endpoints_live": True |
| } |
| } |
|
|
|
|
| @router.get("/api/status") |
| async def get_system_status(): |
| """ |
| Get overall system status with REAL data sources |
| """ |
| return { |
| "status": "operational", |
| "timestamp": datetime.utcnow().isoformat(), |
| "mode": "REAL_DATA_ONLY", |
| "mock_data": False, |
| "services": { |
| "market_data": "operational", |
| "news": "operational", |
| "blockchain": "operational", |
| "ai_models": "operational" |
| }, |
| "data_sources": { |
| "coinmarketcap": { |
| "status": "active", |
| "endpoint": "https://pro-api.coinmarketcap.com/v1", |
| "has_api_key": True |
| }, |
| "newsapi": { |
| "status": "active", |
| "endpoint": "https://newsapi.org/v2", |
| "has_api_key": True |
| }, |
| "etherscan": { |
| "status": "active", |
| "endpoint": "https://api.etherscan.io/api", |
| "has_api_key": True |
| }, |
| "bscscan": { |
| "status": "active", |
| "endpoint": "https://api.bscscan.com/api", |
| "has_api_key": True |
| }, |
| "tronscan": { |
| "status": "active", |
| "endpoint": "https://apilist.tronscan.org/api", |
| "has_api_key": True |
| }, |
| "hf_space": { |
| "status": "active", |
| "endpoint": "https://really-amin-datasourceforcryptocurrency.hf.space", |
| "has_api_token": True |
| } |
| }, |
| "version": "2.0.0-real-data", |
| "uptime_seconds": 0 |
| } |
|
|
|
|
| @router.get("/api/providers") |
| async def get_providers(): |
| """ |
| List all REAL data providers |
| """ |
| providers = [ |
| { |
| "id": "coinmarketcap", |
| "name": "CoinMarketCap", |
| "category": "market_data", |
| "status": "active", |
| "capabilities": ["prices", "market_cap", "volume", "ohlc"], |
| "has_api_key": True |
| }, |
| { |
| "id": "newsapi", |
| "name": "NewsAPI", |
| "category": "news", |
| "status": "active", |
| "capabilities": ["crypto_news", "headlines", "articles"], |
| "has_api_key": True |
| }, |
| { |
| "id": "etherscan", |
| "name": "Etherscan", |
| "category": "blockchain", |
| "status": "active", |
| "capabilities": ["eth_transactions", "gas_prices", "smart_contracts"], |
| "has_api_key": True |
| }, |
| { |
| "id": "bscscan", |
| "name": "BSCScan", |
| "category": "blockchain", |
| "status": "active", |
| "capabilities": ["bsc_transactions", "token_info"], |
| "has_api_key": True |
| }, |
| { |
| "id": "tronscan", |
| "name": "Tronscan", |
| "category": "blockchain", |
| "status": "active", |
| "capabilities": ["tron_transactions", "token_transfers"], |
| "has_api_key": True |
| }, |
| { |
| "id": "hf_space", |
| "name": "HuggingFace Space", |
| "category": "ai_models", |
| "status": "active", |
| "capabilities": ["sentiment", "predictions", "text_generation"], |
| "has_api_token": True |
| } |
| ] |
| |
| return { |
| "success": True, |
| "providers": providers, |
| "total": len(providers), |
| "meta": { |
| "timestamp": datetime.utcnow().isoformat(), |
| "all_real_data": True, |
| "no_mock_providers": True |
| } |
| } |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/api/models/initialize") |
| async def initialize_models(): |
| """ |
| Initialize REAL AI models from HuggingFace |
| """ |
| try: |
| result = await ai_registry.load_models() |
| return { |
| "success": True, |
| "result": result, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| except Exception as e: |
| logger.error(f"❌ Failed to initialize models: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Failed to initialize models: {str(e)}" |
| ) |
|
|
|
|
| @router.get("/api/models/list") |
| async def get_models_list(): |
| """ |
| Get list of available REAL AI models |
| """ |
| try: |
| return ai_registry.get_models_list() |
| except Exception as e: |
| logger.error(f"❌ Failed to get models list: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Failed to get models list: {str(e)}" |
| ) |
|
|
|
|
| @router.post("/api/models/{model_key}/predict") |
| async def predict_with_model(model_key: str, request: PredictRequest): |
| """ |
| Generate REAL predictions using AI models |
| NO FAKE PREDICTIONS - Only real model inference |
| """ |
| try: |
| if model_key == "trading_signals": |
| result = await ai_registry.get_trading_signal( |
| symbol=request.symbol, |
| context=request.context |
| ) |
| else: |
| |
| text = request.context or f"Analyze {request.symbol} cryptocurrency" |
| result = await ai_registry.predict_sentiment( |
| text=text, |
| model_key=model_key |
| ) |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Model prediction failed: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Real model prediction failed: {str(e)}" |
| ) |
|
|
|
|
| @router.post("/api/sentiment/analyze") |
| async def analyze_sentiment(request: SentimentRequest): |
| """ |
| Analyze REAL sentiment using AI models |
| NO FAKE ANALYSIS |
| """ |
| try: |
| |
| model_map = { |
| "crypto": "sentiment_crypto", |
| "financial": "sentiment_financial", |
| "social": "sentiment_twitter", |
| "auto": "sentiment_crypto" |
| } |
| |
| model_key = model_map.get(request.mode, "sentiment_crypto") |
| |
| result = await ai_registry.predict_sentiment( |
| text=request.text, |
| model_key=model_key |
| ) |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Sentiment analysis failed: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Real sentiment analysis failed: {str(e)}" |
| ) |
|
|
|
|
| @router.post("/api/ai/generate") |
| async def generate_ai_text( |
| prompt: str = Body(..., embed=True), |
| max_length: int = Body(200, embed=True) |
| ): |
| """ |
| Generate REAL text using AI models |
| NO FAKE GENERATION |
| """ |
| try: |
| result = await ai_registry.generate_text( |
| prompt=prompt, |
| max_length=max_length |
| ) |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ AI text generation failed: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Real AI generation failed: {str(e)}" |
| ) |
|
|
|
|
| @router.post("/api/trading/signal") |
| async def get_trading_signal( |
| symbol: str = Body(..., embed=True), |
| context: Optional[str] = Body(None, embed=True) |
| ): |
| """ |
| Get REAL trading signal from AI model |
| NO FAKE SIGNALS |
| """ |
| try: |
| result = await ai_registry.get_trading_signal( |
| symbol=symbol, |
| context=context |
| ) |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Trading signal failed: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Real trading signal failed: {str(e)}" |
| ) |
|
|
|
|
| @router.post("/api/news/summarize") |
| async def summarize_news_article( |
| text: str = Body(..., embed=True) |
| ): |
| """ |
| Summarize REAL news using AI |
| NO FAKE SUMMARIES |
| """ |
| try: |
| result = await ai_registry.summarize_news(text=text) |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ News summarization failed: {e}") |
| raise HTTPException( |
| status_code=500, |
| detail=f"Real summarization failed: {str(e)}" |
| ) |
|
|
|
|
| |
| __all__ = ["router"] |
|
|