|
|
| """
|
| 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"]
|
|
|