|
|
|
|
|
""" |
|
|
ادغام مدلهای HuggingFace برای تحلیل هوش مصنوعی |
|
|
HuggingFace Models Integration for AI Analysis |
|
|
""" |
|
|
|
|
|
import asyncio |
|
|
from typing import List, Dict, Optional, Any |
|
|
from datetime import datetime |
|
|
import logging |
|
|
|
|
|
try: |
|
|
from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification |
|
|
TRANSFORMERS_AVAILABLE = True |
|
|
except ImportError: |
|
|
TRANSFORMERS_AVAILABLE = False |
|
|
logging.warning("⚠️ transformers not installed. AI features will be limited.") |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class HuggingFaceAnalyzer: |
|
|
""" |
|
|
تحلیلگر هوش مصنوعی با استفاده از مدلهای HuggingFace |
|
|
AI Analyzer using HuggingFace models |
|
|
""" |
|
|
|
|
|
def __init__(self): |
|
|
self.models_loaded = False |
|
|
self.sentiment_analyzer = None |
|
|
self.zero_shot_classifier = None |
|
|
|
|
|
if TRANSFORMERS_AVAILABLE: |
|
|
self._load_models() |
|
|
|
|
|
def _load_models(self): |
|
|
"""بارگذاری مدلهای HuggingFace""" |
|
|
try: |
|
|
logger.info("🤗 Loading HuggingFace models...") |
|
|
|
|
|
|
|
|
try: |
|
|
self.sentiment_analyzer = pipeline( |
|
|
"sentiment-analysis", |
|
|
model="ProsusAI/finbert", |
|
|
tokenizer="ProsusAI/finbert" |
|
|
) |
|
|
logger.info("✅ Loaded FinBERT for sentiment analysis") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠️ Could not load FinBERT: {e}") |
|
|
|
|
|
try: |
|
|
self.sentiment_analyzer = pipeline( |
|
|
"sentiment-analysis", |
|
|
model="distilbert-base-uncased-finetuned-sst-2-english" |
|
|
) |
|
|
logger.info("✅ Loaded DistilBERT for sentiment analysis (fallback)") |
|
|
except Exception as e2: |
|
|
logger.error(f"❌ Could not load sentiment model: {e2}") |
|
|
|
|
|
|
|
|
try: |
|
|
self.zero_shot_classifier = pipeline( |
|
|
"zero-shot-classification", |
|
|
model="facebook/bart-large-mnli" |
|
|
) |
|
|
logger.info("✅ Loaded BART for zero-shot classification") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠️ Could not load zero-shot classifier: {e}") |
|
|
|
|
|
self.models_loaded = True |
|
|
logger.info("🎉 HuggingFace models loaded successfully!") |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Error loading models: {e}") |
|
|
self.models_loaded = False |
|
|
|
|
|
async def analyze_news_sentiment(self, news_text: str) -> Dict[str, Any]: |
|
|
""" |
|
|
تحلیل احساسات یک خبر |
|
|
Analyze sentiment of a news article |
|
|
""" |
|
|
if not self.models_loaded or not self.sentiment_analyzer: |
|
|
return { |
|
|
"sentiment": "neutral", |
|
|
"confidence": 0.0, |
|
|
"error": "Model not available" |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
max_length = 512 |
|
|
text = news_text[:max_length] |
|
|
|
|
|
|
|
|
result = self.sentiment_analyzer(text)[0] |
|
|
|
|
|
|
|
|
label_map = { |
|
|
"positive": "bullish", |
|
|
"negative": "bearish", |
|
|
"neutral": "neutral" |
|
|
} |
|
|
|
|
|
sentiment = label_map.get(result['label'].lower(), result['label'].lower()) |
|
|
|
|
|
return { |
|
|
"sentiment": sentiment, |
|
|
"confidence": round(result['score'], 4), |
|
|
"raw_label": result['label'], |
|
|
"text_analyzed": text[:100] + "...", |
|
|
"model": "finbert", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Sentiment analysis error: {e}") |
|
|
return { |
|
|
"sentiment": "neutral", |
|
|
"confidence": 0.0, |
|
|
"error": str(e) |
|
|
} |
|
|
|
|
|
async def analyze_news_batch(self, news_list: List[Dict]) -> List[Dict]: |
|
|
""" |
|
|
تحلیل دستهای احساسات اخبار |
|
|
Batch sentiment analysis for news |
|
|
""" |
|
|
results = [] |
|
|
|
|
|
for news in news_list: |
|
|
text = f"{news.get('title', '')} {news.get('description', '')}" |
|
|
|
|
|
sentiment_result = await self.analyze_news_sentiment(text) |
|
|
|
|
|
results.append({ |
|
|
**news, |
|
|
"ai_sentiment": sentiment_result['sentiment'], |
|
|
"ai_confidence": sentiment_result['confidence'], |
|
|
"ai_analysis": sentiment_result |
|
|
}) |
|
|
|
|
|
|
|
|
await asyncio.sleep(0.1) |
|
|
|
|
|
return results |
|
|
|
|
|
async def categorize_news(self, news_text: str) -> Dict[str, Any]: |
|
|
""" |
|
|
دستهبندی اخبار با zero-shot classification |
|
|
Categorize news using zero-shot classification |
|
|
""" |
|
|
if not self.models_loaded or not self.zero_shot_classifier: |
|
|
return { |
|
|
"category": "general", |
|
|
"confidence": 0.0, |
|
|
"error": "Model not available" |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
categories = [ |
|
|
"price_movement", |
|
|
"regulation", |
|
|
"technology", |
|
|
"adoption", |
|
|
"security", |
|
|
"defi", |
|
|
"nft", |
|
|
"exchange", |
|
|
"mining", |
|
|
"general" |
|
|
] |
|
|
|
|
|
|
|
|
text = news_text[:512] |
|
|
|
|
|
|
|
|
result = self.zero_shot_classifier(text, categories) |
|
|
|
|
|
return { |
|
|
"category": result['labels'][0], |
|
|
"confidence": round(result['scores'][0], 4), |
|
|
"all_categories": [ |
|
|
{"label": label, "score": round(score, 4)} |
|
|
for label, score in zip(result['labels'][:3], result['scores'][:3]) |
|
|
], |
|
|
"model": "bart-mnli", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Categorization error: {e}") |
|
|
return { |
|
|
"category": "general", |
|
|
"confidence": 0.0, |
|
|
"error": str(e) |
|
|
} |
|
|
|
|
|
async def calculate_aggregated_sentiment( |
|
|
self, |
|
|
news_list: List[Dict], |
|
|
symbol: Optional[str] = None |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
محاسبه احساسات جمعی از چندین خبر |
|
|
Calculate aggregated sentiment from multiple news items |
|
|
""" |
|
|
if not news_list: |
|
|
return { |
|
|
"overall_sentiment": "neutral", |
|
|
"sentiment_score": 0.0, |
|
|
"confidence": 0.0, |
|
|
"news_count": 0 |
|
|
} |
|
|
|
|
|
|
|
|
if symbol: |
|
|
news_list = [ |
|
|
n for n in news_list |
|
|
if symbol.upper() in [c.upper() for c in n.get('coins', [])] |
|
|
] |
|
|
|
|
|
if not news_list: |
|
|
return { |
|
|
"overall_sentiment": "neutral", |
|
|
"sentiment_score": 0.0, |
|
|
"confidence": 0.0, |
|
|
"news_count": 0, |
|
|
"note": f"No news found for {symbol}" |
|
|
} |
|
|
|
|
|
|
|
|
analyzed_news = await self.analyze_news_batch(news_list[:20]) |
|
|
|
|
|
|
|
|
bullish_count = 0 |
|
|
bearish_count = 0 |
|
|
neutral_count = 0 |
|
|
total_confidence = 0.0 |
|
|
|
|
|
for news in analyzed_news: |
|
|
sentiment = news.get('ai_sentiment', 'neutral') |
|
|
confidence = news.get('ai_confidence', 0.0) |
|
|
|
|
|
if sentiment == 'bullish': |
|
|
bullish_count += confidence |
|
|
elif sentiment == 'bearish': |
|
|
bearish_count += confidence |
|
|
else: |
|
|
neutral_count += confidence |
|
|
|
|
|
total_confidence += confidence |
|
|
|
|
|
|
|
|
if total_confidence > 0: |
|
|
sentiment_score = ((bullish_count - bearish_count) / total_confidence) * 100 |
|
|
else: |
|
|
sentiment_score = 0.0 |
|
|
|
|
|
|
|
|
if sentiment_score > 30: |
|
|
overall = "bullish" |
|
|
elif sentiment_score < -30: |
|
|
overall = "bearish" |
|
|
else: |
|
|
overall = "neutral" |
|
|
|
|
|
return { |
|
|
"overall_sentiment": overall, |
|
|
"sentiment_score": round(sentiment_score, 2), |
|
|
"confidence": round(total_confidence / len(analyzed_news), 2) if analyzed_news else 0.0, |
|
|
"news_count": len(analyzed_news), |
|
|
"bullish_weight": round(bullish_count, 2), |
|
|
"bearish_weight": round(bearish_count, 2), |
|
|
"neutral_weight": round(neutral_count, 2), |
|
|
"symbol": symbol, |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
async def predict_price_direction( |
|
|
self, |
|
|
symbol: str, |
|
|
recent_news: List[Dict], |
|
|
current_price: float, |
|
|
historical_prices: List[float] |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
پیشبینی جهت قیمت بر اساس اخبار و روند قیمت |
|
|
Predict price direction based on news sentiment and price trend |
|
|
""" |
|
|
|
|
|
news_sentiment = await self.calculate_aggregated_sentiment(recent_news, symbol) |
|
|
|
|
|
|
|
|
if len(historical_prices) >= 2: |
|
|
price_change = ((current_price - historical_prices[0]) / historical_prices[0]) * 100 |
|
|
else: |
|
|
price_change = 0.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
news_score = news_sentiment['sentiment_score'] * 0.6 |
|
|
momentum_score = min(50, max(-50, price_change * 10)) * 0.4 |
|
|
|
|
|
combined_score = news_score + momentum_score |
|
|
|
|
|
|
|
|
if combined_score > 20: |
|
|
prediction = "bullish" |
|
|
direction = "up" |
|
|
elif combined_score < -20: |
|
|
prediction = "bearish" |
|
|
direction = "down" |
|
|
else: |
|
|
prediction = "neutral" |
|
|
direction = "sideways" |
|
|
|
|
|
|
|
|
confidence = min(1.0, abs(combined_score) / 100) |
|
|
|
|
|
return { |
|
|
"symbol": symbol, |
|
|
"prediction": prediction, |
|
|
"direction": direction, |
|
|
"confidence": round(confidence, 2), |
|
|
"combined_score": round(combined_score, 2), |
|
|
"news_sentiment_score": round(news_score / 0.6, 2), |
|
|
"price_momentum_score": round(momentum_score / 0.4, 2), |
|
|
"current_price": current_price, |
|
|
"price_change_pct": round(price_change, 2), |
|
|
"news_analyzed": news_sentiment['news_count'], |
|
|
"timestamp": datetime.now().isoformat(), |
|
|
"model": "combined_analysis" |
|
|
} |
|
|
|
|
|
|
|
|
class SimpleHuggingFaceAnalyzer: |
|
|
""" |
|
|
نسخه ساده برای زمانی که transformers نصب نیست |
|
|
Simplified version when transformers is not available |
|
|
Uses simple keyword-based sentiment |
|
|
""" |
|
|
|
|
|
async def analyze_news_sentiment(self, news_text: str) -> Dict[str, Any]: |
|
|
"""Simple keyword-based sentiment""" |
|
|
text_lower = news_text.lower() |
|
|
|
|
|
|
|
|
bullish_keywords = [ |
|
|
'bullish', 'surge', 'rally', 'gain', 'rise', 'soar', |
|
|
'adoption', 'breakthrough', 'positive', 'growth', 'boom' |
|
|
] |
|
|
|
|
|
|
|
|
bearish_keywords = [ |
|
|
'bearish', 'crash', 'plunge', 'drop', 'fall', 'decline', |
|
|
'regulation', 'ban', 'hack', 'scam', 'negative', 'crisis' |
|
|
] |
|
|
|
|
|
bullish_count = sum(1 for word in bullish_keywords if word in text_lower) |
|
|
bearish_count = sum(1 for word in bearish_keywords if word in text_lower) |
|
|
|
|
|
if bullish_count > bearish_count: |
|
|
sentiment = "bullish" |
|
|
confidence = min(0.8, bullish_count * 0.2) |
|
|
elif bearish_count > bullish_count: |
|
|
sentiment = "bearish" |
|
|
confidence = min(0.8, bearish_count * 0.2) |
|
|
else: |
|
|
sentiment = "neutral" |
|
|
confidence = 0.5 |
|
|
|
|
|
return { |
|
|
"sentiment": sentiment, |
|
|
"confidence": confidence, |
|
|
"method": "keyword_based", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def get_analyzer() -> Any: |
|
|
"""Get appropriate analyzer based on availability""" |
|
|
if TRANSFORMERS_AVAILABLE: |
|
|
return HuggingFaceAnalyzer() |
|
|
else: |
|
|
logger.warning("⚠️ Using simple analyzer (transformers not available)") |
|
|
return SimpleHuggingFaceAnalyzer() |
|
|
|
|
|
|
|
|
async def main(): |
|
|
"""Test HuggingFace models""" |
|
|
print("\n" + "="*70) |
|
|
print("🤗 Testing HuggingFace AI Models") |
|
|
print("="*70) |
|
|
|
|
|
analyzer = get_analyzer() |
|
|
|
|
|
|
|
|
test_news = [ |
|
|
"Bitcoin surges past $50,000 as institutional adoption accelerates", |
|
|
"SEC delays decision on crypto ETF, causing market uncertainty", |
|
|
"Ethereum network upgrade successfully completed without issues" |
|
|
] |
|
|
|
|
|
print("\n📊 Testing Sentiment Analysis:") |
|
|
for i, news in enumerate(test_news, 1): |
|
|
result = await analyzer.analyze_news_sentiment(news) |
|
|
print(f"\n{i}. {news[:60]}...") |
|
|
print(f" Sentiment: {result['sentiment']}") |
|
|
print(f" Confidence: {result['confidence']:.2%}") |
|
|
|
|
|
|
|
|
if isinstance(analyzer, HuggingFaceAnalyzer) and analyzer.models_loaded: |
|
|
print("\n\n🎯 Testing News Categorization:") |
|
|
categorization = await analyzer.categorize_news(test_news[0]) |
|
|
print(f" Category: {categorization['category']}") |
|
|
print(f" Confidence: {categorization['confidence']:.2%}") |
|
|
|
|
|
print("\n\n📈 Testing Aggregated Sentiment:") |
|
|
mock_news = [ |
|
|
{"title": news, "description": "", "coins": ["BTC"]} |
|
|
for news in test_news |
|
|
] |
|
|
agg_sentiment = await analyzer.calculate_aggregated_sentiment(mock_news, "BTC") |
|
|
print(f" Overall: {agg_sentiment['overall_sentiment']}") |
|
|
print(f" Score: {agg_sentiment['sentiment_score']}/100") |
|
|
print(f" Confidence: {agg_sentiment['confidence']:.2%}") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
asyncio.run(main()) |
|
|
|