|
|
|
|
|
""" |
|
|
Real Data API Router - UNIFIED HUGGINGFACE ONLY |
|
|
================================================= |
|
|
✅ تمام دادهها از HuggingFace Space |
|
|
✅ بدون WebSocket (فقط HTTP REST API) |
|
|
✅ بدون استفاده مستقیم از CoinMarketCap, NewsAPI, etc. |
|
|
✅ تمام درخواستها از طریق HuggingFaceUnifiedClient |
|
|
|
|
|
Reference: crypto_resources_unified_2025-11-11.json |
|
|
""" |
|
|
|
|
|
from fastapi import APIRouter, HTTPException, Query, Body |
|
|
from fastapi.responses import JSONResponse |
|
|
from typing import Optional, List, Dict, Any |
|
|
from datetime import datetime |
|
|
from pydantic import BaseModel |
|
|
import logging |
|
|
|
|
|
|
|
|
from backend.services.hf_unified_client import get_hf_client |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
router = APIRouter(tags=["Unified HuggingFace API"]) |
|
|
|
|
|
|
|
|
hf_client = get_hf_client() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.get("/api/market") |
|
|
async def get_market_snapshot( |
|
|
limit: int = Query(100, description="Number of symbols"), |
|
|
symbols: Optional[str] = Query(None, description="Comma-separated symbols (e.g., BTC,ETH)") |
|
|
): |
|
|
""" |
|
|
دریافت دادههای بازار از HuggingFace Space |
|
|
|
|
|
✅ فقط از HuggingFace |
|
|
❌ بدون CoinMarketCap |
|
|
❌ بدون API های دیگر |
|
|
""" |
|
|
try: |
|
|
symbol_list = None |
|
|
if symbols: |
|
|
symbol_list = [s.strip() for s in symbols.split(',')] |
|
|
|
|
|
result = await hf_client.get_market_prices( |
|
|
symbols=symbol_list, |
|
|
limit=limit |
|
|
) |
|
|
|
|
|
if not result.get("success"): |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=result.get("error", "HuggingFace Space returned error") |
|
|
) |
|
|
|
|
|
logger.info(f"✅ Market data from HF: {len(result.get('data', []))} symbols") |
|
|
return result |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Market data failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch market data from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/api/market/history") |
|
|
async def get_market_history( |
|
|
symbol: str = Query(..., description="Symbol (e.g., BTCUSDT)"), |
|
|
timeframe: str = Query("1h", description="Timeframe (1m, 5m, 15m, 1h, 4h, 1d)"), |
|
|
limit: int = Query(1000, description="Number of candles") |
|
|
): |
|
|
""" |
|
|
دریافت دادههای OHLCV از HuggingFace Space |
|
|
|
|
|
✅ فقط از HuggingFace |
|
|
❌ بدون CoinMarketCap یا Binance |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_market_history( |
|
|
symbol=symbol, |
|
|
timeframe=timeframe, |
|
|
limit=limit |
|
|
) |
|
|
|
|
|
if not result.get("success"): |
|
|
raise HTTPException( |
|
|
status_code=404, |
|
|
detail=result.get("error", "OHLCV data not available") |
|
|
) |
|
|
|
|
|
logger.info(f"✅ OHLCV from HF: {symbol} {timeframe} ({len(result.get('data', []))} candles)") |
|
|
return result |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ OHLCV data failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch OHLCV data from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/api/market/pairs") |
|
|
async def get_trading_pairs(): |
|
|
""" |
|
|
دریافت لیست جفتهای معاملاتی |
|
|
|
|
|
در صورت عدم وجود endpoint در HuggingFace، از اطلاعات market data استفاده میشود |
|
|
""" |
|
|
try: |
|
|
|
|
|
|
|
|
market_data = await hf_client.get_market_prices(limit=50) |
|
|
|
|
|
if not market_data.get("success"): |
|
|
raise HTTPException(status_code=503, detail="Failed to fetch market data") |
|
|
|
|
|
pairs = [] |
|
|
for item in market_data.get("data", []): |
|
|
symbol = item.get("symbol", "") |
|
|
if 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": "hf_engine" |
|
|
} |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Trading pairs failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch trading pairs: {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") |
|
|
): |
|
|
""" |
|
|
دریافت tickers مرتبشده از HuggingFace |
|
|
""" |
|
|
try: |
|
|
market_data = await hf_client.get_market_prices(limit=limit) |
|
|
|
|
|
if not market_data.get("success"): |
|
|
raise HTTPException(status_code=503, detail="Failed to fetch market data") |
|
|
|
|
|
tickers = [] |
|
|
for item in market_data.get("data", []): |
|
|
tickers.append({ |
|
|
"symbol": item.get("symbol", ""), |
|
|
"price": item.get("price", 0), |
|
|
"change_24h": item.get("change_24h", 0), |
|
|
"volume_24h": item.get("volume_24h", 0), |
|
|
"market_cap": item.get("market_cap", 0) |
|
|
}) |
|
|
|
|
|
|
|
|
if sort == "volume": |
|
|
tickers.sort(key=lambda x: x.get("volume_24h", 0), reverse=True) |
|
|
elif sort == "change": |
|
|
tickers.sort(key=lambda x: x.get("change_24h", 0), reverse=True) |
|
|
elif sort == "market_cap": |
|
|
tickers.sort(key=lambda x: x.get("market_cap", 0), reverse=True) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"tickers": tickers, |
|
|
"meta": { |
|
|
"cache_ttl_seconds": 60, |
|
|
"generated_at": datetime.utcnow().isoformat(), |
|
|
"source": "hf_engine", |
|
|
"sort": sort |
|
|
} |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Tickers failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch tickers: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/sentiment/analyze") |
|
|
async def analyze_sentiment(request: SentimentRequest): |
|
|
""" |
|
|
تحلیل احساسات با مدلهای AI در HuggingFace |
|
|
|
|
|
✅ فقط از HuggingFace AI Models |
|
|
❌ بدون مدلهای محلی |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.analyze_sentiment(text=request.text) |
|
|
|
|
|
if not result.get("success"): |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=result.get("error", "Sentiment analysis failed") |
|
|
) |
|
|
|
|
|
logger.info(f"✅ Sentiment from HF: {result.get('data', {}).get('sentiment')}") |
|
|
return result |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Sentiment analysis failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Failed to analyze sentiment: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/news") |
|
|
async def get_news( |
|
|
limit: int = Query(20, description="Number of articles"), |
|
|
source: Optional[str] = Query(None, description="Filter by source") |
|
|
): |
|
|
""" |
|
|
دریافت اخبار از HuggingFace Space |
|
|
|
|
|
✅ فقط از HuggingFace |
|
|
❌ بدون NewsAPI مستقیم |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_news(limit=limit, source=source) |
|
|
|
|
|
logger.info(f"✅ News from HF: {len(result.get('articles', []))} articles") |
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ News failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch news from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/api/news/latest") |
|
|
async def get_latest_news( |
|
|
symbol: str = Query("BTC", description="Crypto symbol"), |
|
|
limit: int = Query(10, description="Number of articles") |
|
|
): |
|
|
""" |
|
|
دریافت آخرین اخبار برای سمبل خاص |
|
|
""" |
|
|
try: |
|
|
|
|
|
result = await hf_client.get_news(limit=limit) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"symbol": symbol, |
|
|
"news": result.get("articles", []), |
|
|
"meta": { |
|
|
"total": len(result.get("articles", [])), |
|
|
"source": "hf_engine", |
|
|
"timestamp": datetime.utcnow().isoformat() |
|
|
} |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Latest news failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch latest news: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/blockchain/gas") |
|
|
async def get_gas_prices( |
|
|
chain: str = Query("ethereum", description="Blockchain network") |
|
|
): |
|
|
""" |
|
|
دریافت قیمت گس از HuggingFace Space |
|
|
|
|
|
✅ فقط از HuggingFace |
|
|
❌ بدون Etherscan/BSCScan مستقیم |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_blockchain_gas_prices(chain=chain) |
|
|
|
|
|
logger.info(f"✅ Gas prices from HF: {chain}") |
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Gas prices failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch gas prices from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/api/blockchain/stats") |
|
|
async def get_blockchain_stats( |
|
|
chain: str = Query("ethereum", description="Blockchain network"), |
|
|
hours: int = Query(24, description="Time window in hours") |
|
|
): |
|
|
""" |
|
|
دریافت آمار بلاکچین از HuggingFace Space |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_blockchain_stats(chain=chain, hours=hours) |
|
|
|
|
|
logger.info(f"✅ Blockchain stats from HF: {chain}") |
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Blockchain stats failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch blockchain stats from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/whales/transactions") |
|
|
async def get_whale_transactions( |
|
|
limit: int = Query(50, description="Number of transactions"), |
|
|
chain: Optional[str] = Query(None, description="Filter by blockchain"), |
|
|
min_amount_usd: float = Query(100000, description="Minimum amount in USD") |
|
|
): |
|
|
""" |
|
|
دریافت تراکنشهای نهنگها از HuggingFace Space |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_whale_transactions( |
|
|
limit=limit, |
|
|
chain=chain, |
|
|
min_amount_usd=min_amount_usd |
|
|
) |
|
|
|
|
|
logger.info(f"✅ Whale transactions from HF: {len(result.get('transactions', []))}") |
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Whale transactions failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch whale transactions from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/api/whales/stats") |
|
|
async def get_whale_stats( |
|
|
hours: int = Query(24, description="Time window in hours") |
|
|
): |
|
|
""" |
|
|
دریافت آمار نهنگها از HuggingFace Space |
|
|
""" |
|
|
try: |
|
|
result = await hf_client.get_whale_stats(hours=hours) |
|
|
|
|
|
logger.info(f"✅ Whale stats from HF") |
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Whale stats failed: {e}") |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Failed to fetch whale stats from HuggingFace: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/health") |
|
|
async def health_check(): |
|
|
""" |
|
|
بررسی سلامت سیستم با چک HuggingFace Space |
|
|
""" |
|
|
try: |
|
|
hf_health = await hf_client.health_check() |
|
|
|
|
|
return { |
|
|
"status": "healthy" if hf_health.get("success") else "degraded", |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"huggingface_space": hf_health, |
|
|
"checks": { |
|
|
"hf_space_connection": hf_health.get("success", False), |
|
|
"hf_database": hf_health.get("database", "unknown"), |
|
|
"hf_ai_models": hf_health.get("ai_models", {}) |
|
|
} |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Health check failed: {e}") |
|
|
return { |
|
|
"status": "unhealthy", |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"error": str(e), |
|
|
"checks": { |
|
|
"hf_space_connection": False |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.get("/api/status") |
|
|
async def get_system_status(): |
|
|
""" |
|
|
دریافت وضعیت کلی سیستم |
|
|
""" |
|
|
try: |
|
|
hf_status = await hf_client.get_system_status() |
|
|
|
|
|
return { |
|
|
"status": "operational", |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"mode": "UNIFIED_HUGGINGFACE_ONLY", |
|
|
"mock_data": False, |
|
|
"direct_api_calls": False, |
|
|
"all_via_huggingface": True, |
|
|
"huggingface_space": hf_status, |
|
|
"version": "3.0.0-unified-hf" |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Status check failed: {e}") |
|
|
return { |
|
|
"status": "degraded", |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"error": str(e), |
|
|
"mode": "UNIFIED_HUGGINGFACE_ONLY" |
|
|
} |
|
|
|
|
|
|
|
|
@router.get("/api/providers") |
|
|
async def get_providers(): |
|
|
""" |
|
|
لیست ارائهدهندگان - فقط HuggingFace |
|
|
""" |
|
|
providers = [ |
|
|
{ |
|
|
"id": "huggingface_space", |
|
|
"name": "HuggingFace Space", |
|
|
"category": "all", |
|
|
"status": "active", |
|
|
"capabilities": [ |
|
|
"market_data", |
|
|
"ohlcv", |
|
|
"sentiment_analysis", |
|
|
"news", |
|
|
"blockchain_stats", |
|
|
"whale_tracking", |
|
|
"ai_models" |
|
|
], |
|
|
"has_api_token": True, |
|
|
"endpoint": hf_client.base_url |
|
|
} |
|
|
] |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"providers": providers, |
|
|
"total": len(providers), |
|
|
"meta": { |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"unified_source": "huggingface_space", |
|
|
"no_direct_api_calls": True |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
__all__ = ["router"] |
|
|
|