| |
| """ |
| Data Hub Complete API Router |
| ============================= |
| ✅ تمام endpoint های دادههای کریپتو |
| ✅ استفاده از کلیدهای API جدید |
| ✅ سیستم Fallback خودکار |
| ✅ WebSocket Support |
| """ |
|
|
| 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.data_hub_complete import get_data_hub |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter( |
| prefix="/api/v2/data-hub", |
| tags=["Data Hub Complete"] |
| ) |
|
|
| |
| data_hub = get_data_hub() |
|
|
|
|
| |
| |
| |
|
|
| class MarketRequest(BaseModel): |
| """درخواست دادههای بازار""" |
| symbols: Optional[List[str]] = None |
| limit: int = 100 |
| source: str = "auto" |
|
|
|
|
| class OHLCVRequest(BaseModel): |
| """درخواست دادههای OHLCV""" |
| symbol: str |
| interval: str = "1h" |
| limit: int = 100 |
| source: str = "auto" |
|
|
|
|
| class SentimentRequest(BaseModel): |
| """درخواست تحلیل احساسات""" |
| text: str |
| source: str = "huggingface" |
|
|
|
|
| class NewsRequest(BaseModel): |
| """درخواست اخبار""" |
| query: str = "cryptocurrency" |
| limit: int = 20 |
| source: str = "auto" |
|
|
|
|
| class BlockchainRequest(BaseModel): |
| """درخواست دادههای بلاکچین""" |
| chain: str |
| data_type: str = "transactions" |
| address: Optional[str] = None |
| limit: int = 20 |
|
|
|
|
| class WhaleRequest(BaseModel): |
| """درخواست فعالیت نهنگها""" |
| chain: str = "all" |
| min_value_usd: float = 1000000 |
| limit: int = 50 |
|
|
|
|
| class SocialMediaRequest(BaseModel): |
| """درخواست دادههای شبکههای اجتماعی""" |
| platform: str = "reddit" |
| query: str = "cryptocurrency" |
| limit: int = 20 |
|
|
|
|
| class AIRequest(BaseModel): |
| """درخواست پیشبینی AI""" |
| symbol: str |
| model_type: str = "price" |
| timeframe: str = "24h" |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/market/prices") |
| async def get_market_prices( |
| symbols: Optional[str] = Query(None, description="Comma-separated symbols (e.g., BTC,ETH)"), |
| limit: int = Query(100, description="Number of results"), |
| source: str = Query("auto", description="Data source: auto, coinmarketcap, coingecko, binance") |
| ): |
| """ |
| دریافت قیمتهای لحظهای بازار |
| |
| Sources: |
| - CoinMarketCap (with new API key) |
| - CoinGecko (free) |
| - Binance (free) |
| - HuggingFace |
| |
| Returns: قیمت، تغییرات 24 ساعته، حجم معاملات، Market Cap |
| """ |
| try: |
| symbol_list = None |
| if symbols: |
| symbol_list = [s.strip().upper() for s in symbols.split(',')] |
| |
| result = await data_hub.get_market_prices( |
| symbols=symbol_list, |
| limit=limit, |
| source=source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch market data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Market prices error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/market/prices") |
| async def post_market_prices(request: MarketRequest): |
| """ |
| دریافت قیمتهای بازار (POST method) |
| """ |
| try: |
| result = await data_hub.get_market_prices( |
| symbols=request.symbols, |
| limit=request.limit, |
| source=request.source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch market data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Market prices error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/market/top") |
| async def get_top_coins( |
| limit: int = Query(10, description="Number of top coins") |
| ): |
| """ |
| دریافت Top N ارزهای برتر بر اساس Market Cap |
| """ |
| try: |
| result = await data_hub.get_market_prices(limit=limit, source="auto") |
| |
| if result.get("success") and result.get("data"): |
| |
| data = sorted(result["data"], key=lambda x: x.get("market_cap", 0), reverse=True) |
| result["data"] = data[:limit] |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Top coins error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/market/ohlcv") |
| async def get_ohlcv_data( |
| symbol: str = Query(..., description="Symbol (e.g., BTC, ETH)"), |
| interval: str = Query("1h", description="Interval: 1m, 5m, 15m, 1h, 4h, 1d"), |
| limit: int = Query(100, description="Number of candles"), |
| source: str = Query("auto", description="Data source: auto, binance, huggingface") |
| ): |
| """ |
| دریافت دادههای OHLCV (کندل استیک) |
| |
| Sources: |
| - Binance (best for OHLCV) |
| - HuggingFace |
| |
| Returns: Open, High, Low, Close, Volume for each candle |
| """ |
| try: |
| result = await data_hub.get_ohlcv_data( |
| symbol=symbol.upper(), |
| interval=interval, |
| limit=limit, |
| source=source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch OHLCV data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ OHLCV error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/market/ohlcv") |
| async def post_ohlcv_data(request: OHLCVRequest): |
| """ |
| دریافت دادههای OHLCV (POST method) |
| """ |
| try: |
| result = await data_hub.get_ohlcv_data( |
| symbol=request.symbol.upper(), |
| interval=request.interval, |
| limit=request.limit, |
| source=request.source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch OHLCV data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ OHLCV error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/sentiment/fear-greed") |
| async def get_fear_greed_index(): |
| """ |
| دریافت شاخص ترس و طمع (Fear & Greed Index) |
| |
| Source: Alternative.me |
| |
| Returns: |
| - مقدار شاخص (0-100) |
| - طبقهبندی (Extreme Fear, Fear, Neutral, Greed, Extreme Greed) |
| - تاریخچه 30 روزه |
| """ |
| try: |
| result = await data_hub.get_fear_greed_index() |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Fear & Greed error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/sentiment/analyze") |
| async def analyze_sentiment(request: SentimentRequest): |
| """ |
| تحلیل احساسات متن با AI |
| |
| Source: HuggingFace Models |
| |
| Returns: |
| - Label: POSITIVE, NEGATIVE, NEUTRAL |
| - Score (0-1) |
| - Confidence |
| """ |
| try: |
| result = await data_hub.analyze_sentiment( |
| text=request.text, |
| source=request.source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Sentiment analysis failed")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Sentiment analysis error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/sentiment/batch") |
| async def batch_sentiment_analysis(texts: List[str] = Body(...)): |
| """ |
| تحلیل احساسات دستهای برای چندین متن |
| """ |
| try: |
| results = [] |
| for text in texts[:50]: |
| result = await data_hub.analyze_sentiment(text=text) |
| results.append({ |
| "text": text[:100], |
| "sentiment": result.get("data", {}) if result.get("success") else None, |
| "error": result.get("error") if not result.get("success") else None |
| }) |
| |
| return { |
| "success": True, |
| "total": len(results), |
| "results": results, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Batch sentiment error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/news") |
| async def get_crypto_news( |
| query: str = Query("cryptocurrency", description="Search query"), |
| limit: int = Query(20, description="Number of articles"), |
| source: str = Query("auto", description="Source: auto, newsapi, reddit") |
| ): |
| """ |
| دریافت اخبار ارزهای دیجیتال |
| |
| Sources: |
| - NewsAPI (with new API key) |
| - Reddit (r/CryptoCurrency, r/Bitcoin, etc.) |
| - HuggingFace |
| |
| Returns: Title, Description, URL, Source, Published Date |
| """ |
| try: |
| result = await data_hub.get_crypto_news( |
| query=query, |
| limit=limit, |
| source=source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch news")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ News error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/news") |
| async def post_crypto_news(request: NewsRequest): |
| """ |
| دریافت اخبار (POST method) |
| """ |
| try: |
| result = await data_hub.get_crypto_news( |
| query=request.query, |
| limit=request.limit, |
| source=request.source |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch news")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ News error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/news/latest/{symbol}") |
| async def get_latest_news_for_symbol( |
| symbol: str, |
| limit: int = Query(10, description="Number of articles") |
| ): |
| """ |
| دریافت آخرین اخبار برای یک سمبل خاص |
| """ |
| try: |
| query = f"{symbol} cryptocurrency" |
| result = await data_hub.get_crypto_news(query=query, limit=limit) |
| |
| if result.get("success"): |
| result["symbol"] = symbol.upper() |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Symbol news error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/trending") |
| async def get_trending_coins(): |
| """ |
| دریافت ارزهای ترند روز |
| |
| Source: CoinGecko |
| |
| Returns: لیست ارزهای ترند با رتبه و امتیاز |
| """ |
| try: |
| result = await data_hub.get_trending_coins() |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Trending error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/trending/search") |
| async def search_trending( |
| query: str = Query(..., description="Search query") |
| ): |
| """ |
| جستجو در ارزهای ترند |
| """ |
| try: |
| result = await data_hub.get_trending_coins() |
| |
| if result.get("success") and result.get("trending"): |
| |
| filtered = [ |
| coin for coin in result["trending"] |
| if query.lower() in coin.get("name", "").lower() or |
| query.lower() in coin.get("symbol", "").lower() |
| ] |
| result["trending"] = filtered |
| result["filtered_by"] = query |
| |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Trending search error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/blockchain/{chain}") |
| async def get_blockchain_data( |
| chain: str, |
| data_type: str = Query("transactions", description="Type: transactions, balance, gas"), |
| address: Optional[str] = Query(None, description="Wallet address"), |
| limit: int = Query(20, description="Number of results") |
| ): |
| """ |
| دریافت دادههای بلاکچین |
| |
| Chains: ethereum, bsc, tron |
| |
| Sources: |
| - Etherscan (with new API key) |
| - BSCScan (with new API key) |
| - TronScan (with new API key) |
| |
| Types: |
| - transactions: لیست تراکنشها |
| - balance: موجودی آدرس |
| - gas: قیمت گس |
| """ |
| try: |
| result = await data_hub.get_blockchain_data( |
| chain=chain.lower(), |
| data_type=data_type, |
| address=address, |
| limit=limit |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch blockchain data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Blockchain data error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/blockchain") |
| async def post_blockchain_data(request: BlockchainRequest): |
| """ |
| دریافت دادههای بلاکچین (POST method) |
| """ |
| try: |
| result = await data_hub.get_blockchain_data( |
| chain=request.chain.lower(), |
| data_type=request.data_type, |
| address=request.address, |
| limit=request.limit |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch blockchain data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Blockchain data error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/blockchain/{chain}/gas") |
| async def get_gas_prices(chain: str): |
| """ |
| دریافت قیمت گس برای بلاکچین مشخص |
| """ |
| try: |
| result = await data_hub.get_blockchain_data( |
| chain=chain.lower(), |
| data_type="gas" |
| ) |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Gas prices error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/whales") |
| async def get_whale_activity( |
| chain: str = Query("all", description="Blockchain: all, ethereum, bsc, tron"), |
| min_value_usd: float = Query(1000000, description="Minimum transaction value in USD"), |
| limit: int = Query(50, description="Number of transactions") |
| ): |
| """ |
| دریافت فعالیت نهنگها (تراکنشهای بزرگ) |
| |
| Returns: |
| - تراکنشهای بالای $1M |
| - جهت حرکت (IN/OUT از صرافیها) |
| - آدرسهای مبدا و مقصد |
| """ |
| try: |
| result = await data_hub.get_whale_activity( |
| chain=chain, |
| min_value_usd=min_value_usd, |
| limit=limit |
| ) |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Whale activity error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/whales") |
| async def post_whale_activity(request: WhaleRequest): |
| """ |
| دریافت فعالیت نهنگها (POST method) |
| """ |
| try: |
| result = await data_hub.get_whale_activity( |
| chain=request.chain, |
| min_value_usd=request.min_value_usd, |
| limit=request.limit |
| ) |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ Whale activity error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/social/{platform}") |
| async def get_social_media_data( |
| platform: str, |
| query: str = Query("cryptocurrency", description="Search query"), |
| limit: int = Query(20, description="Number of posts") |
| ): |
| """ |
| دریافت دادههای شبکههای اجتماعی |
| |
| Platforms: reddit |
| |
| Returns: |
| - پستهای Reddit از subreddit های کریپتو |
| - امتیاز، تعداد کامنت، تاریخ |
| """ |
| try: |
| result = await data_hub.get_social_media_data( |
| platform=platform.lower(), |
| query=query, |
| limit=limit |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch social data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Social media error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/social") |
| async def post_social_media_data(request: SocialMediaRequest): |
| """ |
| دریافت دادههای شبکههای اجتماعی (POST method) |
| """ |
| try: |
| result = await data_hub.get_social_media_data( |
| platform=request.platform.lower(), |
| query=request.query, |
| limit=request.limit |
| ) |
| |
| if not result.get("success"): |
| raise HTTPException(status_code=503, detail=result.get("error", "Failed to fetch social data")) |
| |
| return result |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"❌ Social media error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/ai/predict/{symbol}") |
| async def get_ai_prediction( |
| symbol: str, |
| model_type: str = Query("price", description="Type: price, trend, signal"), |
| timeframe: str = Query("24h", description="Timeframe: 1h, 4h, 24h, 7d") |
| ): |
| """ |
| دریافت پیشبینی از مدلهای AI |
| |
| Source: HuggingFace Models |
| |
| Types: |
| - price: پیشبینی قیمت |
| - trend: پیشبینی روند |
| - signal: سیگنال خرید/فروش |
| """ |
| try: |
| result = await data_hub.get_ai_prediction( |
| symbol=symbol.upper(), |
| model_type=model_type, |
| timeframe=timeframe |
| ) |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ AI prediction error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/ai/predict") |
| async def post_ai_prediction(request: AIRequest): |
| """ |
| دریافت پیشبینی AI (POST method) |
| """ |
| try: |
| result = await data_hub.get_ai_prediction( |
| symbol=request.symbol.upper(), |
| model_type=request.model_type, |
| timeframe=request.timeframe |
| ) |
| return result |
| |
| except Exception as e: |
| logger.error(f"❌ AI prediction error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/overview/{symbol}") |
| async def get_symbol_overview(symbol: str): |
| """ |
| دریافت نمای کلی یک سمبل (ترکیبی از همه دادهها) |
| |
| Returns: |
| - قیمت و آمار بازار |
| - آخرین اخبار |
| - تحلیل احساسات |
| - پیشبینی AI |
| """ |
| try: |
| overview = {} |
| |
| |
| market = await data_hub.get_market_prices(symbols=[symbol.upper()], limit=1) |
| if market.get("success") and market.get("data"): |
| overview["market"] = market["data"][0] if market["data"] else None |
| |
| |
| news = await data_hub.get_crypto_news(query=f"{symbol} cryptocurrency", limit=5) |
| if news.get("success"): |
| overview["news"] = news.get("articles", []) |
| |
| |
| prediction = await data_hub.get_ai_prediction(symbol=symbol.upper()) |
| if prediction.get("success"): |
| overview["prediction"] = prediction.get("prediction") |
| |
| |
| ohlcv = await data_hub.get_ohlcv_data(symbol=symbol.upper(), interval="1h", limit=24) |
| if ohlcv.get("success"): |
| overview["chart_data"] = ohlcv.get("data", []) |
| |
| return { |
| "success": True, |
| "symbol": symbol.upper(), |
| "overview": overview, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Symbol overview error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/dashboard") |
| async def get_dashboard_data(): |
| """ |
| دریافت دادههای داشبورد کامل |
| |
| Returns: |
| - Top 10 coins |
| - Fear & Greed Index |
| - Latest news |
| - Trending coins |
| - Whale activities |
| """ |
| try: |
| dashboard = {} |
| |
| |
| market = await data_hub.get_market_prices(limit=10) |
| if market.get("success"): |
| dashboard["top_coins"] = market.get("data", []) |
| |
| |
| fg = await data_hub.get_fear_greed_index() |
| if fg.get("success"): |
| dashboard["fear_greed"] = fg.get("current", {}) |
| |
| |
| news = await data_hub.get_crypto_news(limit=10) |
| if news.get("success"): |
| dashboard["latest_news"] = news.get("articles", []) |
| |
| |
| trending = await data_hub.get_trending_coins() |
| if trending.get("success"): |
| dashboard["trending"] = trending.get("trending", [])[:5] |
| |
| |
| whales = await data_hub.get_whale_activity(limit=10) |
| if whales.get("success"): |
| dashboard["whale_activity"] = whales.get("data", {}) |
| |
| return { |
| "success": True, |
| "dashboard": dashboard, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Dashboard error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
| @router.get("/health") |
| async def health_check(): |
| """ |
| بررسی سلامت Data Hub |
| """ |
| try: |
| health = await data_hub.check_all_sources_health() |
| return health |
| |
| except Exception as e: |
| logger.error(f"❌ Health check error: {e}") |
| return { |
| "success": False, |
| "error": str(e), |
| "timestamp": datetime.utcnow().isoformat() |
| } |
|
|
|
|
| @router.get("/status") |
| async def get_status(): |
| """ |
| دریافت وضعیت کامل سیستم |
| """ |
| try: |
| health = await data_hub.check_all_sources_health() |
| |
| return { |
| "success": True, |
| "status": "operational" if health.get("operational_count", 0) > 5 else "degraded", |
| "sources": health.get("status", {}), |
| "statistics": { |
| "operational": health.get("operational_count", 0), |
| "total": health.get("total_sources", 0), |
| "uptime_percentage": (health.get("operational_count", 0) / health.get("total_sources", 1)) * 100 |
| }, |
| "api_keys": { |
| "coinmarketcap": "✅ Configured", |
| "newsapi": "✅ Configured", |
| "etherscan": "✅ Configured", |
| "bscscan": "✅ Configured", |
| "tronscan": "✅ Configured", |
| "huggingface": "✅ Configured" |
| }, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Status error: {e}") |
| return { |
| "success": False, |
| "status": "error", |
| "error": str(e), |
| "timestamp": datetime.utcnow().isoformat() |
| } |
|
|
|
|
| @router.get("/sources") |
| async def get_data_sources(): |
| """ |
| لیست منابع داده و قابلیتهای آنها |
| """ |
| sources = { |
| "market_data": [ |
| {"name": "CoinMarketCap", "capabilities": ["prices", "market_cap", "volume"], "status": "active"}, |
| {"name": "CoinGecko", "capabilities": ["prices", "trending"], "status": "active"}, |
| {"name": "Binance", "capabilities": ["prices", "ohlcv", "24hr_tickers"], "status": "active"} |
| ], |
| "blockchain": [ |
| {"name": "Etherscan", "capabilities": ["eth_transactions", "gas_prices", "balances"], "status": "active"}, |
| {"name": "BSCScan", "capabilities": ["bsc_transactions", "token_info"], "status": "active"}, |
| {"name": "TronScan", "capabilities": ["tron_transactions", "tron_blocks"], "status": "active"} |
| ], |
| "news": [ |
| {"name": "NewsAPI", "capabilities": ["crypto_news", "headlines"], "status": "active"}, |
| {"name": "Reddit", "capabilities": ["posts", "sentiment"], "status": "active"} |
| ], |
| "sentiment": [ |
| {"name": "Alternative.me", "capabilities": ["fear_greed_index"], "status": "active"}, |
| {"name": "HuggingFace", "capabilities": ["text_sentiment", "ai_analysis"], "status": "active"} |
| ], |
| "ai": [ |
| {"name": "HuggingFace", "capabilities": ["price_prediction", "trend_analysis", "signals"], "status": "active"} |
| ] |
| } |
| |
| return { |
| "success": True, |
| "sources": sources, |
| "total_sources": sum(len(v) for v in sources.values()), |
| "timestamp": datetime.utcnow().isoformat() |
| } |
|
|
|
|
| |
| |
| |
|
|
| class ConnectionManager: |
| def __init__(self): |
| self.active_connections: Dict[str, WebSocket] = {} |
| self.subscriptions: Dict[str, List[str]] = {} |
| |
| async def connect(self, websocket: WebSocket, client_id: str): |
| await websocket.accept() |
| self.active_connections[client_id] = websocket |
| self.subscriptions[client_id] = [] |
| logger.info(f"✅ WebSocket connected: {client_id}") |
| |
| async def disconnect(self, client_id: str): |
| if client_id in self.active_connections: |
| del self.active_connections[client_id] |
| if client_id in self.subscriptions: |
| del self.subscriptions[client_id] |
| logger.info(f"❌ WebSocket disconnected: {client_id}") |
| |
| async def send_message(self, client_id: str, message: dict): |
| if client_id in self.active_connections: |
| websocket = self.active_connections[client_id] |
| await websocket.send_json(message) |
| |
| async def broadcast(self, message: dict, channel: str = None): |
| for client_id, websocket in self.active_connections.items(): |
| if channel is None or channel in self.subscriptions.get(client_id, []): |
| try: |
| await websocket.send_json(message) |
| except: |
| await self.disconnect(client_id) |
|
|
|
|
| manager = ConnectionManager() |
|
|
|
|
| @router.websocket("/ws") |
| async def websocket_endpoint(websocket: WebSocket): |
| """ |
| WebSocket برای دریافت دادههای Real-time |
| |
| Channels: |
| - prices: قیمتهای لحظهای |
| - news: اخبار جدید |
| - whales: فعالیت نهنگها |
| - sentiment: تحلیل احساسات |
| """ |
| client_id = str(uuid.uuid4()) |
| |
| try: |
| await manager.connect(websocket, client_id) |
| |
| |
| await manager.send_message(client_id, { |
| "type": "connected", |
| "client_id": client_id, |
| "timestamp": datetime.utcnow().isoformat() |
| }) |
| |
| while True: |
| |
| data = await websocket.receive_text() |
| message = json.loads(data) |
| |
| action = message.get("action") |
| |
| if action == "subscribe": |
| channels = message.get("channels", []) |
| manager.subscriptions[client_id] = channels |
| |
| await manager.send_message(client_id, { |
| "type": "subscribed", |
| "channels": channels, |
| "timestamp": datetime.utcnow().isoformat() |
| }) |
| |
| |
| if "prices" in channels: |
| |
| prices = await data_hub.get_market_prices(limit=10) |
| await manager.send_message(client_id, { |
| "type": "price_update", |
| "data": prices, |
| "timestamp": datetime.utcnow().isoformat() |
| }) |
| |
| elif action == "unsubscribe": |
| manager.subscriptions[client_id] = [] |
| |
| await manager.send_message(client_id, { |
| "type": "unsubscribed", |
| "timestamp": datetime.utcnow().isoformat() |
| }) |
| |
| elif action == "ping": |
| await manager.send_message(client_id, { |
| "type": "pong", |
| "timestamp": datetime.utcnow().isoformat() |
| }) |
| |
| except WebSocketDisconnect: |
| await manager.disconnect(client_id) |
| logger.info(f"WebSocket client {client_id} disconnected") |
| |
| except Exception as e: |
| logger.error(f"WebSocket error: {e}") |
| await manager.disconnect(client_id) |
|
|
|
|
| |
| __all__ = ["router"] |