Riy777 commited on
Commit
2cd3835
·
1 Parent(s): 172753f

Update sentiment_news.py

Browse files
Files changed (1) hide show
  1. sentiment_news.py +22 -45
sentiment_news.py CHANGED
@@ -1,10 +1,11 @@
1
- # sentiment_news.py (محدث V3.2 - توسيع نافذة البحث إلى 12 ساعة)
2
  import os, asyncio
3
  import httpx
4
  from gnews import GNews
5
  import feedparser
6
  from datetime import datetime, timedelta, timezone
7
  import time
 
8
 
9
  #
10
  # 🔴 تم التعديل: توسيع قائمة مصادر RSS لتشمل تغطية أوسع للعملات البديلة
@@ -33,10 +34,8 @@ class NewsFetcher:
33
  'Cache-Control': 'no-cache'
34
  }
35
  )
36
- # 🔴 --- START OF CHANGE (V3.2) --- 🔴
37
- # (توسيع نافذة البحث إلى 12 ساعة وزيادة النتائج)
38
  self.gnews = GNews(language='en', country='US', period='12h', max_results=15)
39
- # 🔴 --- END OF CHANGE --- 🔴
40
 
41
  self.rss_cache = {}
42
  self.cache_lock = asyncio.Lock()
@@ -69,7 +68,6 @@ class NewsFetcher:
69
 
70
  async def _get_cached_rss_feed(self, feed_url: str, source_name: str):
71
  """
72
- (جديد V3.1)
73
  دالة مساعدة لجلب ملف RSS مع تخزين مؤقت (لمدة 5 دقائق).
74
  هذا يمنع خطأ 429 عند طلب نفس المصدر لعدة عملات.
75
  """
@@ -80,11 +78,9 @@ class NewsFetcher:
80
  if feed_url in self.rss_cache:
81
  cached_data, cache_time = self.rss_cache[feed_url]
82
  if (current_time - cache_time) < self.cache_duration:
83
- # print(f" [NewsCache] Using cached data for {source_name}")
84
- return cached_data # (إرجاع البيانات المخزنة)
85
 
86
  # 2. (إذا لم يكن في الذاكرة أو كان قديماً) الجلب من المصدر
87
- # print(f" [NewsCache] Fetching fresh data for {source_name}...")
88
  max_redirects = 2
89
  current_url = feed_url
90
  response_text = None
@@ -101,10 +97,8 @@ class NewsFetcher:
101
  current_url = e.response.headers['Location']
102
  continue
103
  else:
104
- # (التعامل مع 429 هنا أيضاً، لا تخزن الخطأ)
105
  if e.response.status_code == 429:
106
  print(f" ⚠️ [NewsCache] Rate limited (429) by {source_name}. Skipping for this cycle.")
107
- # (سنخزن "فارغ" لمنع إعادة المحاولة الفورية)
108
  self.rss_cache[feed_url] = ([], current_time)
109
  return []
110
  raise
@@ -121,7 +115,6 @@ class NewsFetcher:
121
 
122
  except Exception as e:
123
  print(f" ❌ [NewsCache] Failed to fetch/parse {source_name}: {e}")
124
- # (لا تخزن الخطأ، للسماح بإعادة المحاولة في الدورة القادمة)
125
  if feed_url in self.rss_cache:
126
  del self.rss_cache[feed_url]
127
  return []
@@ -131,7 +124,6 @@ class NewsFetcher:
131
  try:
132
  base_symbol = symbol.split('/')[0]
133
 
134
- # (الجلب من الذاكرة المؤقتة أو المصدر)
135
  feed_entries = await self._get_cached_rss_feed(feed_url, source_name)
136
  if not feed_entries:
137
  return []
@@ -139,36 +131,27 @@ class NewsFetcher:
139
  news_items = []
140
  search_term = base_symbol.lower()
141
 
142
- # 🔴 --- START OF CHANGE (V3.2) --- 🔴
143
- # (حساب الوقت قبل 12 ساعة (باستخدام التوقيت العالمي UTC))
144
  twelve_hours_ago = datetime.now(timezone.utc) - timedelta(hours=12)
145
- # 🔴 --- END OF CHANGE --- 🔴
146
 
147
  for entry in feed_entries:
148
  title = entry.title.lower() if hasattr(entry, 'title') else ''
149
  summary = entry.summary.lower() if hasattr(entry, 'summary') else entry.description.lower() if hasattr(entry, 'description') else ''
150
 
151
- # التحقق من تاريخ النشر
152
  published_tuple = entry.get('published_parsed')
153
  if not published_tuple:
154
- continue # تخطي الخبر إذا لم يكن له تاريخ نشر
155
 
156
- # تحويل تاريخ النشر إلى كائن datetime مع دعم التوقيت العالمي (UTC)
157
  try:
158
  published_time = datetime.fromtimestamp(time.mktime(published_tuple), timezone.utc)
159
  except Exception:
160
- # في حال فشل التحويل، نفترض أنه قديم
161
  continue
162
 
163
- # 🔴 --- START OF CHANGE (V3.2) --- 🔴
164
- # (تطبيق الفلتر الزمني (آخر 12 ساعة) وفلتر اسم العملة)
165
  if (search_term in title or search_term in summary) and (published_time >= twelve_hours_ago):
166
- # 🔴 --- END OF CHANGE --- 🔴
167
  news_items.append({
168
  'title': entry.title,
169
  'description': summary,
170
  'source': source_name,
171
- 'published': published_time.isoformat() # إرسال التاريخ بتنسيق ISO
172
  })
173
 
174
  return news_items
@@ -176,7 +159,14 @@ class NewsFetcher:
176
  print(f"Failed to fetch specific news from {source_name} RSS for {symbol}: {e}")
177
  return []
178
 
179
- async def get_news_for_symbol(self, symbol: str) -> str:
 
 
 
 
 
 
 
180
  base_symbol = symbol.split("/")[0]
181
 
182
  # إنشاء قائمة المهام (GNews + جميع مصادر RSS)
@@ -192,33 +182,30 @@ class NewsFetcher:
192
  continue
193
 
194
  for item in result:
195
- # نستخدم الفلتر الثانوي للتأكد من أن الخبر يركز فعلاً على العملة
196
  if self._is_directly_relevant_to_symbol(item, base_symbol):
197
  title = item.get('title', 'No Title')
198
  description = item.get('description', 'No Description')
199
  source = item.get('source', 'Unknown Source')
200
- published = item.get('published', '') # الحصول على التاريخ/الوقت
201
 
202
  news_entry = f"[{source}] {title}. {description}"
203
-
204
  if published:
205
  news_entry += f" (Published: {published})"
206
 
207
  all_news_text.append(news_entry)
208
 
209
  if not all_news_text:
210
- # 🔴 --- START OF CHANGE (V3.2) --- 🔴
211
- return f"No specific news found for {base_symbol} in the last 12 hours."
212
- # 🔴 --- END OF CHANGE --- 🔴
213
 
214
- # أخذ أهم 5 أخبار (الأكثر حداثة أو صلة)
215
  important_news = all_news_text[:5]
216
- return " | ".join(important_news)
 
 
217
 
218
  def _is_directly_relevant_to_symbol(self, news_item, base_symbol):
219
  """
220
- فلتر ثانوي للتأكد من أن الخبر ليس مجرد ذكر عابر للعملة،
221
- بل يتعلق فعلاً بجوانب التداول أو السوق.
222
  """
223
  title = news_item.get('title', '').lower()
224
  description = news_item.get('description', '').lower()
@@ -228,7 +215,6 @@ class NewsFetcher:
228
  if symbol_lower not in title and symbol_lower not in description:
229
  return False
230
 
231
- # يجب أن يحتوي الخبر على كلمات مفتاحية تدل على أنه خبر مالي/تداولي
232
  crypto_keywords = [
233
  'crypto', 'cryptocurrency', 'token', 'blockchain',
234
  'price', 'market', 'trading', 'exchange', 'defi',
@@ -245,11 +231,7 @@ class SentimentAnalyzer:
245
  self.data_manager = data_manager
246
 
247
  async def get_market_sentiment(self):
248
- """
249
- جلب سياق السوق العام (يعتمد بالكامل على DataManager).
250
- """
251
  try:
252
- # هذه الدالة تجلب (BTC/ETH/Fear&Greed) فقط
253
  market_context = await self.data_manager.get_market_context_async()
254
  if not market_context:
255
  return await self.get_fallback_market_context()
@@ -259,9 +241,6 @@ class SentimentAnalyzer:
259
  return await self.get_fallback_market_context()
260
 
261
  async def get_fallback_market_context(self):
262
- """
263
- (محدث) سياق احتياطي مبسط يعكس الواقع (بدون بيانات حيتان عامة).
264
- """
265
  return {
266
  'timestamp': datetime.now().isoformat(),
267
  'btc_sentiment': 'NEUTRAL',
@@ -271,6 +250,4 @@ class SentimentAnalyzer:
271
  'data_quality': 'LOW'
272
  }
273
 
274
- # 🔴 --- START OF CHANGE (V3.2) --- 🔴
275
- print("✅ Sentiment News loaded - V3.2 (Expanded 12h Window)")
276
- # 🔴 --- END OF CHANGE --- 🔴
 
1
+ # sentiment_news.py (محدث V3.3 - Fix API Name & Return Type)
2
  import os, asyncio
3
  import httpx
4
  from gnews import GNews
5
  import feedparser
6
  from datetime import datetime, timedelta, timezone
7
  import time
8
+ from typing import Dict, Any
9
 
10
  #
11
  # 🔴 تم التعديل: توسيع قائمة مصادر RSS لتشمل تغطية أوسع للعملات البديلة
 
34
  'Cache-Control': 'no-cache'
35
  }
36
  )
37
+ # 🔴 (توسيع نافذة البحث إلى 12 ساعة وزيادة النتائج)
 
38
  self.gnews = GNews(language='en', country='US', period='12h', max_results=15)
 
39
 
40
  self.rss_cache = {}
41
  self.cache_lock = asyncio.Lock()
 
68
 
69
  async def _get_cached_rss_feed(self, feed_url: str, source_name: str):
70
  """
 
71
  دالة مساعدة لجلب ملف RSS مع تخزين مؤقت (لمدة 5 دقائق).
72
  هذا يمنع خطأ 429 عند طلب نفس المصدر لعدة عملات.
73
  """
 
78
  if feed_url in self.rss_cache:
79
  cached_data, cache_time = self.rss_cache[feed_url]
80
  if (current_time - cache_time) < self.cache_duration:
81
+ return cached_data
 
82
 
83
  # 2. (إذا لم يكن في الذاكرة أو كان قديماً) الجلب من المصدر
 
84
  max_redirects = 2
85
  current_url = feed_url
86
  response_text = None
 
97
  current_url = e.response.headers['Location']
98
  continue
99
  else:
 
100
  if e.response.status_code == 429:
101
  print(f" ⚠️ [NewsCache] Rate limited (429) by {source_name}. Skipping for this cycle.")
 
102
  self.rss_cache[feed_url] = ([], current_time)
103
  return []
104
  raise
 
115
 
116
  except Exception as e:
117
  print(f" ❌ [NewsCache] Failed to fetch/parse {source_name}: {e}")
 
118
  if feed_url in self.rss_cache:
119
  del self.rss_cache[feed_url]
120
  return []
 
124
  try:
125
  base_symbol = symbol.split('/')[0]
126
 
 
127
  feed_entries = await self._get_cached_rss_feed(feed_url, source_name)
128
  if not feed_entries:
129
  return []
 
131
  news_items = []
132
  search_term = base_symbol.lower()
133
 
 
 
134
  twelve_hours_ago = datetime.now(timezone.utc) - timedelta(hours=12)
 
135
 
136
  for entry in feed_entries:
137
  title = entry.title.lower() if hasattr(entry, 'title') else ''
138
  summary = entry.summary.lower() if hasattr(entry, 'summary') else entry.description.lower() if hasattr(entry, 'description') else ''
139
 
 
140
  published_tuple = entry.get('published_parsed')
141
  if not published_tuple:
142
+ continue
143
 
 
144
  try:
145
  published_time = datetime.fromtimestamp(time.mktime(published_tuple), timezone.utc)
146
  except Exception:
 
147
  continue
148
 
 
 
149
  if (search_term in title or search_term in summary) and (published_time >= twelve_hours_ago):
 
150
  news_items.append({
151
  'title': entry.title,
152
  'description': summary,
153
  'source': source_name,
154
+ 'published': published_time.isoformat()
155
  })
156
 
157
  return news_items
 
159
  print(f"Failed to fetch specific news from {source_name} RSS for {symbol}: {e}")
160
  return []
161
 
162
+ # [ 🚀 🚀 🚀 ]
163
+ # [ 💡 💡 💡 ] تم التعديل: تغيير الاسم إلى get_news وإرجاع قاموس
164
+ # [ 🚀 🚀 🚀 ]
165
+ async def get_news(self, symbol: str) -> Dict[str, Any]:
166
+ """
167
+ جلب الأخبار المجمعة لعملة محددة.
168
+ الإرجاع: قاموس يحتوي على 'summary' (نص) ليناسب app.py.
169
+ """
170
  base_symbol = symbol.split("/")[0]
171
 
172
  # إنشاء قائمة المهام (GNews + جميع مصادر RSS)
 
182
  continue
183
 
184
  for item in result:
 
185
  if self._is_directly_relevant_to_symbol(item, base_symbol):
186
  title = item.get('title', 'No Title')
187
  description = item.get('description', 'No Description')
188
  source = item.get('source', 'Unknown Source')
189
+ published = item.get('published', '')
190
 
191
  news_entry = f"[{source}] {title}. {description}"
 
192
  if published:
193
  news_entry += f" (Published: {published})"
194
 
195
  all_news_text.append(news_entry)
196
 
197
  if not all_news_text:
198
+ return {'summary': f"No specific news found for {base_symbol} in the last 12 hours."}
 
 
199
 
200
+ # أخذ أهم 5 أخبار
201
  important_news = all_news_text[:5]
202
+
203
+ # [ 🚀 ] إرجاع قاموس كما يتوقع app.py
204
+ return {'summary': " | ".join(important_news)}
205
 
206
  def _is_directly_relevant_to_symbol(self, news_item, base_symbol):
207
  """
208
+ فلتر ثانوي للتأكد من أن الخبر ليس مجرد ذكر عابر للعملة.
 
209
  """
210
  title = news_item.get('title', '').lower()
211
  description = news_item.get('description', '').lower()
 
215
  if symbol_lower not in title and symbol_lower not in description:
216
  return False
217
 
 
218
  crypto_keywords = [
219
  'crypto', 'cryptocurrency', 'token', 'blockchain',
220
  'price', 'market', 'trading', 'exchange', 'defi',
 
231
  self.data_manager = data_manager
232
 
233
  async def get_market_sentiment(self):
 
 
 
234
  try:
 
235
  market_context = await self.data_manager.get_market_context_async()
236
  if not market_context:
237
  return await self.get_fallback_market_context()
 
241
  return await self.get_fallback_market_context()
242
 
243
  async def get_fallback_market_context(self):
 
 
 
244
  return {
245
  'timestamp': datetime.now().isoformat(),
246
  'btc_sentiment': 'NEUTRAL',
 
250
  'data_quality': 'LOW'
251
  }
252
 
253
+ print("✅ Sentiment News loaded - V3.3 (Fixed API Mismatch)")